Check-in [d0305b305a]
Not logged in

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

Overview
Comment:Merged mainline into my branch to get the newest application.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: d0305b305ad0a15a1aa38dbd1830bc9b7654eb69
User & Date: aku 2007-12-05 08:07:46.000
Context
2007-12-06
03:48
Fixed handling of empty revisions. check-in: bf0b70d5e0 user: aku tags: trunk
2007-12-05
08:07
Merged mainline into my branch to get the newest application. check-in: d0305b305a user: aku tags: trunk
07:58
Bugfix. Translation implies encoding, not the reverse. This caused problems when parsing files with mixed-mode line-endings. The generated char offsets and lengths were off. Found during expansion. check-in: 6f1c4424b4 user: aku tags: trunk
2007-12-04
02:47
Add the timeline display preferences page with the ability to turn on and off block markup in timeline comments and to limit the length of timeline comments. check-in: ebb2765954 user: drh tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Added Makefile.w32.
































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
#!/usr/bin/make
#
#### The toplevel directory of the source tree.  Fossil can be built
#    in a directory that is separate from the source tree.  Just change
#    the following to point from the build directory to the src/ folder.
#
SRCDIR = ../src

#### C Compiler and options for use in building executables that
#    will run on the platform that is doing the build.  This is used
#    to compile code-generator programs as part of the build process.
#    See TCC below for the C compiler for building the finished binary.
#
BCC = gcc -g -O2

#### The suffix to add to executable files.  ".exe" for windows.
#    Nothing for unix.
#
E =

#### C Compile and options for use in building executables that 
#    will run on the target platform.  This is usually the same
#    as BCC, unless you are cross-compiling.  This C compiler builds
#    the finished binary for fossil.  The BCC compiler above is used
#    for building intermediate code-generator tools.
#
#TCC = gcc -O6
#TCC = gcc -g -O0 -Wall -fprofile-arcs -ftest-coverage
#TCC = gcc -g -Os -Wall
TCC = gcc -g -Os -Wall -DFOSSIL_I18N=0 -L/usr/local/lib -I/usr/local/include

#### Extra arguments for linking the finished binary.  Fossil needs
#    to link against the Z-Lib compression library.  There are no
#    other dependencies.  We sometimes add the -static option here
#    so that we can build a static executable that will run in a
#    chroot jail.
#
#LIB = -lz
LIB = -lz -lws2_32


#### Tcl shell for use in running the fossil testsuite.
#
TCLSH = tclsh

# You should not need to change anything below this line
###############################################################################
include $(SRCDIR)/main.mk
Changes to ideas.txt.

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

Possible ticket file format:

   "Ticket"
   title: TEXT
   ticketid: TEXT
   exists-in: BASELINE   -- 0 or more
   fixed-in: BASELINE    -- 0 or more
   tag: TAG              -- 0 or more
   created: DATETIME
   attachment: FILENAME DESCRIPTION

   parent: UUID*
   derived-from: TICKET-FILENAME

   description: MULTILINE-TEXT
   remarks: MULTILINE-TEXT

   * Things handles with tags:

     created-by
     assigned-to




     priority
     severity
     target-release





     status
     resolution
     type
     subsystem

Wiki header format:
   "WikiPage"
   parent: UUID*
   title: TEXT
   pagename: TEXT
   mode: (readonly|appendonly|readwrite)
   attachment: UUID name description






   * Header ends with a blank line.  wiki content follows.

Cluster format:

       M+ uuid
       Z manifest-cksum



   * Cluster generated in server mode only.
   * Embargo cluster that reference phantoms or other embargoed clusters.
   * Never send or ihave an embargoed cluster

New sync algorithm based on clusters:





   * Keep a table of unclustered artifacts.  Strive to keep this table
     less than 100 entries.
   * Client sends content of unclustered table as ihaves to server
   * Server builds a new cluster if size of cluster table >100.
   * Server sends unclustered table to client
   * Server sends gimme for all unknown ihave received from client
   * Client sends gimme for all unknown ihave received from server
   * Previous two steps repeat until no more gimmes








Details of new push algorithm:


   * Table "unsent" contains all files never pushed




   * TEMP table "wanted" contains files the server does not have
   Loop:
     * Client sends login and "push" record
     * Client sends file message for all files in unsent and removes

       those files from the table.
     * Client sends file message for all files in wanted.

     * Client sends ihave messages for each entry in unclustered
     ------
     * Server receives file message

     * Server creates phantoms for unknown ihaves
     * Server sends gimme messages for all phantoms
     ------
     * Client clears its unsent table

     * For each gimme message add an entry to wanted
     * Halt if the wanted table is empty

Details on new pull algorithm:



   Loop:
     * Client sends login and "pull" record
     * Client sends "prior" message with repository id and max record number
     * Client sends "gimme" for each phantom
     --------

     * Server creates new clusters to get unclustered size below 100
     * If there is "prior" message with repository id that matches this
       server, then send file messages for all record ids greater than
       prior
     * Server sends ihave messages for each entry in unclustered
     * Server sends maxrid message
     --------
     * Client receives file records

     * Client creates phantoms for unknown ihaves
     * If no phantoms exist, record maxrid for the server and halt


Need a dephantomize algorithm









Auxiliary tables needed for new sync algorithm:

   * unsent:  files that have never been sent to another repository
   * unclustered: non-phantom files not mentioned by a cluster


Random thoughts:

  *  Changes to manifest to support:
     +  Trees of wiki pages and tickets
     +  The ability to cap or close a branch
     +  See "Extended Manifests" below

  *  Add the concept of "clusters" to speed the transfer of "tips"
     on a sync.

  *  Auxiliary tables:
     +  tip
     +  phantom
     +  mlink
     +  plink
     +  branch
     +  tree

  * Plink.isprim changed to record:
     +  child is the principal descendent of parent. (1)
     +  child is a branch from parent (2)
     +  child uses parent as a merge (0)

  * tree records
     + type  (code, wiki, ticket)
     + name  (for wiki and ticket only)
     + treeid

  * branch records
     + treeid
     + origin_rid
     + origin_time
     + tip_rid
     + tip_time
     + color

  * website can toggle isprim between principal and branch.
     + How to preserve across rebuild.  A new record type?
     + How to share with other repositories
  * isprim guessed using userid of parent and child.  Change
    in id suggests a branch.  Same id suggests principal.
    For a tie, go with the earliest check-in as the principal'

  * Autosync mode
     + Set a preferred remote repository to use as a server
        =  Clone repository is the default


     + On commit, first pull.  If commit baseline is not a tip
       prompt user to cancel or branch.  Default is cancel.




     + Push after commit
     + Automatically pull prior to update.
     + Need an "undo" capability
     + Designed to avoid branching in highly collaborative 
       environments.

  * Archeological webpage improvements:
     + Use a small amount of CSS+javascript on timelines so that
       branching structure is displayed on mouseover.  On mouseover
       of a checkin, highlight other checkins that are direct (non-merge)
       descendents and ancestors of the mouseover checkin.
     + Timeline showing individual branches
>


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

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

|
<
<
|
<
<
<

>
>
>
>
>
|

|

|
|
>
>

|
<
<

|
>
>
>
>

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

<
>

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

<
>
>

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

<
>

>
>
>
>
>
>

<

<
<
>



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





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








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







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

Possible ticket file format:


   A uuid name description
   D datetime





   J field value
   K uuid
   U user
   Z md5sum
   
FIELDs:


   comment         cumulative text
   title           text
   assignedto      text
   status          enum
   resolution      enum
   subsystem       enum
   type            enum
   priority        enum
   severity        enum
   deferuntil      datetime
   duedate         datetime
   derivedfrom     add or subtract uuid
   relatedversions add or subtract associate with manifest
   presentin       add or subtract uuid
   fixedin         add or subtract uuid

Other table columns:
   origintime
   lastchange




Field Types:




   text                 width height
   enum                 width valuelist
   datetime             width
   cumulative-text      width height
   set-of-uuid          width
   set-of-checkin       width

Tables:

   tktrid(rid, tkid, mtime);  index(tkid, mtime);
   ticket(tkid, tkuuid UNIQUE, starttime, lastmod, ...);
   tktfield(fieldname UNIQUE, type, width, other);
   tktxref(tkid, mid); index(tkid); index(mid);

Tktformat in the config table.



   * Three pages:  creation, display, and edit
   * Separate global and local versions of each page.  Local overwrites
     global if it exists.
   * HTML
   * [[field]] to substitute the appropriate form or display element





Ticket Configuraiton File:

   * Format:

      ticket-configuration
      field <fieldname> <type> <param> ...
      template <type> <delimiter>
      <text>
   * Each repository selects a single ticket config for its own use.
   * Rescan all tickets following any config change
   * Use the ticket-configuration tag must be on the file


Todo:


   * Configuration file parser
       + tkt-new-template
       + tkt-view-template
       + tkt-edit-template
       + Reconstruct the ticket table schema
       + Repopulate tktfield

   * Ticket control file parser
       + Create ticket entry if necessary
       + Update fields.  Ignore unrecognized fields.

   * Transfer tkt control info to ticket table
   * Setup pages for selecting and editing ticket configuration


   * Ticket display
   * New ticket creation display


   * Ticket change screen
   * Ticket query screens




------------------------------------------------------------------------
Change to wiki:


   A uuid filename description
   D datetime
   P uuid ...

   U user
   W size \n text \n
   Z cksum

Hyperlinks:

   [lowercasehex]       /info/lowercasehex

   [attachment.gif]     inline image
   [tagname]            /info/tagname
   [wikipagename]       /wiki/wikipagename
   [/internal/page]     /internal/page
   [http:...]           external link


Markup:

   blank-line           paragraph break
   _*__                 bullet
   __                   indentation
   _#.__                enumeration
   *text*               bold
   _text_               italic





------------------------------------------------------------------------

Random thoughts:

















  * Plink.isprim changed to record:
     +  child is the principal descendent of parent. (1)
     +  child is a branch from parent (2)
     +  child uses parent as a merge (0)














  * website can toggle isprim between principal and branch.
     + How to preserve across rebuild.  A new record type?
     + How to share with other repositories
  * isprim guessed using userid of parent and child.  Change
    in id suggests a branch.  Same id suggests principal.
    For a tie, go with the earliest check-in as the principal'

  * Autosync mode
     * Notes:
       + Designed to avoid branching in highly collaborative 
         environments.
     * Outstanding:
       + On commit, first pull.  If commit baseline is not a tip
         prompt user to cancel or branch.  Default is cancel.
       + Need an "undo" capability
     * Done:
       * Set a preferred remote repository to use as a server
          =  Clone repository is the default
       * Push after commit
       * Automatically pull prior to update.




  * Archeological webpage improvements:
     + Use a small amount of CSS+javascript on timelines so that
       branching structure is displayed on mouseover.  On mouseover
       of a checkin, highlight other checkins that are direct (non-merge)
       descendents and ancestors of the mouseover checkin.
     + Timeline showing individual branches
182
183
184
185
186
187
188
189
190
191
192
193

194
195
196
197
198
199
200
201
202
       P uuid ...           -- omitted for first manifest
       R repository-md5sum
       U user-login
       Z manifest-checksum

  * Accessory:
       A uuid|* attachment-uuid description
       B (+|-)branchtag uuid
       D date-time
       E uuid new-comment
       G uuid appended-remark
       S repositoryid serial-number

       U userid
       V (+|-)versiontag uuid
       X uuid-to-surpress
       Z this-file-checksum

  * Change the comment on a version:   -- always a leaf except in cluster
       D date-time
       E new-comment
       P uuid              -- baseline whose comment is changed







<




>

<







173
174
175
176
177
178
179

180
181
182
183
184
185

186
187
188
189
190
191
192
       P uuid ...           -- omitted for first manifest
       R repository-md5sum
       U user-login
       Z manifest-checksum

  * Accessory:
       A uuid|* attachment-uuid description

       D date-time
       E uuid new-comment
       G uuid appended-remark
       S repositoryid serial-number
       T (+|-)tag uuid
       U userid

       X uuid-to-surpress
       Z this-file-checksum

  * Change the comment on a version:   -- always a leaf except in cluster
       D date-time
       E new-comment
       P uuid              -- baseline whose comment is changed
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
  * Set or erase a tag    -- most recent date wins
       B* (+|-)tag uuid
       C? comment
       D date-time
       V* (+|-) tag uuid    -- + to set, - to clear.  
       Z manifest-cksum
       -- Must have at least one B or V.
       -- Tag "hidden" means do not sync
       -- Tag "closed" means do not display as a leaf
  * A cluster
       M+ uuid
       Z manifest-cksum

  * Complete set of cards in a manifest files:
       A filename uuid
       B (+|-)branch-tag uuid
       C comment
       D date-time
       E edited-comment
       F filename uuid

       N name
       P uuid ...
       R repository-md5sum
       T uuid
       U user-login
       V (+|-)version-tag uuid
       W uuid
       Z manifest-checksum







|
|



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

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
  * Set or erase a tag    -- most recent date wins
       B* (+|-)tag uuid
       C? comment
       D date-time
       V* (+|-) tag uuid    -- + to set, - to clear.  
       Z manifest-cksum
       -- Must have at least one B or V.
       -- Branch tag "hidden" means do not sync
       -- Version tag "closed" means do not display as a leaf
  * A cluster
       M+ uuid
       Z manifest-cksum

  * Complete set of cards in a control file:
       A filename uuid             
       B (+|-)branch-tag uuid      
       C comment                   
       D date-time                 
       E uuid edited-comment       
       F filename uuid             
       M uuid                      
       N name                      
       P uuid ...                  
       R repository-md5sum         
       T uuid                      
       U user-login                
       V (+|-)version-tag uuid     
       W uuid                      
       Z manifest-checksum
Changes to src/add.c.
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
  db_begin_transaction();
  for(i=2; i<g.argc; i++){
    char *zName;
    char *zPath;
    Blob pathname;
    int isDir;

    zName = mprintf("%s", g.argv[i]);
    isDir = file_isdir(zName);
    if( isDir==1 ) continue;
    if( isDir==0 ){
      fossil_fatal("not found: %s", zName);
    }
    if( isDir==2 && access(zName, R_OK) ){
      fossil_fatal("cannot open %s", zName);
    }
    file_tree_name(zName, &pathname);
    zPath = blob_str(&pathname);
    if( strcmp(zPath, "manifest")==0 || strcmp(zPath, "_FOSSIL_")==0 ){
      fossil_fatal("cannot add %s", zPath);
    }



    if( db_exists("SELECT 1 FROM vfile WHERE pathname=%Q", zPath) ){
      db_multi_exec("UPDATE vfile SET deleted=0 WHERE pathname=%Q", zPath);
    }else{
      db_multi_exec(
        "INSERT INTO vfile(vid,deleted,rid,mrid,pathname)"
        "VALUES(%d,0,0,0,%Q)", vid, zPath);
    }







|













>
>
>







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
  db_begin_transaction();
  for(i=2; i<g.argc; i++){
    char *zName;
    char *zPath;
    Blob pathname;
    int isDir;

    zName = mprintf("%/", g.argv[i]);
    isDir = file_isdir(zName);
    if( isDir==1 ) continue;
    if( isDir==0 ){
      fossil_fatal("not found: %s", zName);
    }
    if( isDir==2 && access(zName, R_OK) ){
      fossil_fatal("cannot open %s", zName);
    }
    file_tree_name(zName, &pathname);
    zPath = blob_str(&pathname);
    if( strcmp(zPath, "manifest")==0 || strcmp(zPath, "_FOSSIL_")==0 ){
      fossil_fatal("cannot add %s", zPath);
    }
    if( !file_is_simple_pathname(zPath) ){
      fossil_fatal("filename contains illegal characters: %s", zPath);
    }
    if( db_exists("SELECT 1 FROM vfile WHERE pathname=%Q", zPath) ){
      db_multi_exec("UPDATE vfile SET deleted=0 WHERE pathname=%Q", zPath);
    }else{
      db_multi_exec(
        "INSERT INTO vfile(vid,deleted,rid,mrid,pathname)"
        "VALUES(%d,0,0,0,%Q)", vid, zPath);
    }
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
  }
  db_begin_transaction();
  for(i=2; i<g.argc; i++){
    char *zName;
    char *zPath;
    Blob pathname;

    zName = mprintf("%s", g.argv[i]);
    file_tree_name(zName, &pathname);
    zPath = blob_str(&pathname);
    if( !db_exists(
             "SELECT 1 FROM vfile WHERE pathname=%Q AND NOT deleted", zPath) ){
      fossil_fatal("not in the repository: %s", zName);
    }else{
      db_multi_exec("UPDATE vfile SET deleted=1 WHERE pathname=%Q", zPath);







|







101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
  }
  db_begin_transaction();
  for(i=2; i<g.argc; i++){
    char *zName;
    char *zPath;
    Blob pathname;

    zName = mprintf("%/", g.argv[i]);
    file_tree_name(zName, &pathname);
    zPath = blob_str(&pathname);
    if( !db_exists(
             "SELECT 1 FROM vfile WHERE pathname=%Q AND NOT deleted", zPath) ){
      fossil_fatal("not in the repository: %s", zName);
    }else{
      db_multi_exec("UPDATE vfile SET deleted=1 WHERE pathname=%Q", zPath);
Changes to src/blob.c.
47
48
49
50
51
52
53







54
55
56
57
58
59
60
#define blob_size(X)  ((X)->nUsed)

/*
** The buffer holding the blob data
*/
#define blob_buffer(X)  ((X)->aData)








#endif /* INTERFACE */

/*
** Make sure a blob is initialized
*/
#define blob_is_init(x) \
  assert((x)->xRealloc==blobReallocMalloc || (x)->xRealloc==blobReallocStatic)







>
>
>
>
>
>
>







47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#define blob_size(X)  ((X)->nUsed)

/*
** The buffer holding the blob data
*/
#define blob_buffer(X)  ((X)->aData)

/*
** Seek whence parameter values
*/
#define BLOB_SEEK_SET 1
#define BLOB_SEEK_CUR 2
#define BLOB_SEEK_END 3

#endif /* INTERFACE */

/*
** Make sure a blob is initialized
*/
#define blob_is_init(x) \
  assert((x)->xRealloc==blobReallocMalloc || (x)->xRealloc==blobReallocStatic)
337
338
339
340
341
342
343



























344
345
346
347
348
349
350

/*
** Rewind the cursor on a blob back to the beginning.
*/
void blob_rewind(Blob *p){
  p->iCursor = 0;
}




























/*
** Extract a single line of text from pFrom beginning at the current 
** cursor location and use that line of text to initialize pTo.
** pTo will include the terminating \n.  Return the number of bytes
** in the line including the \n at the end.  0 is returned at
** end-of-file.







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







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

/*
** Rewind the cursor on a blob back to the beginning.
*/
void blob_rewind(Blob *p){
  p->iCursor = 0;
}

/*
** Seek the cursor in a blob to the indicated offset.
*/
int blob_seek(Blob *p, int offset, int whence){
  if( whence==BLOB_SEEK_SET ){
    p->iCursor = offset;
  }else if( whence==BLOB_SEEK_CUR ){
    p->iCursor += offset;
  }else if( whence==BLOB_SEEK_END ){
    p->iCursor = p->nUsed + offset - 1;
  }
  if( p->iCursor<0 ){
    p->iCursor = 0;
  }
  if( p->iCursor>p->nUsed ){
    p->iCursor = p->nUsed;
  }
  return p->iCursor;
}

/*
** Return the current offset into the blob
*/
int blob_tell(Blob *p){
  return p->iCursor;
}

/*
** Extract a single line of text from pFrom beginning at the current 
** cursor location and use that line of text to initialize pTo.
** pTo will include the terminating \n.  Return the number of bytes
** in the line including the \n at the end.  0 is returned at
** end-of-file.
364
365
366
367
368
369
370















371
372
373
374
375



376
377
378
379
380
381
382
  if( i<n ){
    assert( aData[i]=='\n' );
    i++;
  }
  blob_extract(pFrom, i-pFrom->iCursor, pTo);
  return pTo->nUsed;
}
















/*
** Extract a single token from pFrom and use it to initialize pTo.
** Return the number of bytes in the token.  If no token is found,
** return 0.



**
** The cursor of pFrom is left pointing at the first character past
** the end of the token.
**
** pTo will be an ephermeral blob.  If pFrom changes, it might alter
** pTo as well.
*/







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





>
>
>







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
  if( i<n ){
    assert( aData[i]=='\n' );
    i++;
  }
  blob_extract(pFrom, i-pFrom->iCursor, pTo);
  return pTo->nUsed;
}

/*
** Trim whitespace off of the end of a blob.  Return the number
** of characters remaining.
**
** All this does is reduce the length counter.  This routine does
** not insert a new zero terminator.
*/
int blob_trim(Blob *p){
  char *z = p->aData;
  int n = p->nUsed;
  while( n>0 && isspace(z[n-1]) ){ n--; }
  p->nUsed = n;
  return n;
}

/*
** Extract a single token from pFrom and use it to initialize pTo.
** Return the number of bytes in the token.  If no token is found,
** return 0.
**
** A token consists of one or more non-space characters.  Leading
** whitespace is ignored.
**
** The cursor of pFrom is left pointing at the first character past
** the end of the token.
**
** pTo will be an ephermeral blob.  If pFrom changes, it might alter
** pTo as well.
*/
400
401
402
403
404
405
406






























407
408
409
410
411
412
413
*/
int blob_tail(Blob *pFrom, Blob *pTo){
  int iCursor = pFrom->iCursor;
  blob_extract(pFrom, pFrom->nUsed-pFrom->iCursor, pTo);
  pFrom->iCursor = iCursor;
  return pTo->nUsed;
}































/*
** Return true if the blob contains a valid UUID_SIZE-digit base16 identifier.
*/
int blob_is_uuid(Blob *pBlob){
  return blob_size(pBlob)==UUID_SIZE
         && validate16(blob_buffer(pBlob), UUID_SIZE);







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







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
*/
int blob_tail(Blob *pFrom, Blob *pTo){
  int iCursor = pFrom->iCursor;
  blob_extract(pFrom, pFrom->nUsed-pFrom->iCursor, pTo);
  pFrom->iCursor = iCursor;
  return pTo->nUsed;
}

/*
** Copy N lines of text from pFrom into pTo.  The copy begins at the
** current cursor position of pIn.  The pIn cursor is left pointing
** at the first character past the last \n copied.
**
** If pTo==NULL then this routine simply skips over N lines.
*/
void blob_copy_lines(Blob *pTo, Blob *pFrom, int N){
  char *z = pFrom->aData;
  int i = pFrom->iCursor;
  int n = pFrom->nUsed;
  int cnt = 0;

  if( N==0 ) return;
  while( i<n ){
    if( z[i]=='\n' ){
      cnt++;
      if( cnt==N ){
        i++;
        break;
      }
    }
    i++;
  }
  if( pTo ){
    blob_append(pTo, &pFrom->aData[pFrom->iCursor], i - pFrom->iCursor);
  }
  pFrom->iCursor = i;
}

/*
** Return true if the blob contains a valid UUID_SIZE-digit base16 identifier.
*/
int blob_is_uuid(Blob *pBlob){
  return blob_size(pBlob)==UUID_SIZE
         && validate16(blob_buffer(pBlob), UUID_SIZE);
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
*/
int blob_tokenize(Blob *pIn, Blob *aToken, int nToken){
  int i;
  for(i=0; i<nToken && blob_token(pIn, &aToken[i]); i++){}
  return i;
}

/* 
** This function implements the callback from vxprintf. 
**
** This routine add nNewChar characters of text in zNewText to
** the Blob structure pointed to by "arg".
*/
static void bout(void *arg, const char *zNewText, int nNewChar){
  Blob *pBlob = (Blob*)arg;
  blob_append(pBlob, zNewText, nNewChar);
}

/*
** Do printf-style string rendering and append the results to a blob.
*/
void blob_appendf(Blob *pBlob, const char *zFormat, ...){
  va_list ap;
  va_start(ap, zFormat);
  vxprintf(bout, pBlob, zFormat, ap);
  va_end(ap);
}
void blob_vappendf(Blob *pBlob, const char *zFormat, va_list ap){
  vxprintf(bout, pBlob, zFormat, ap);
}

/*
** Initalize a blob to the data on an input channel.  Return 
** the number of bytes read into the blob.  Any prior content
** of the blob is discarded, not freed.
*/







<
<
<
<
<
<
<
<
<
<
<






|



|







537
538
539
540
541
542
543











544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
*/
int blob_tokenize(Blob *pIn, Blob *aToken, int nToken){
  int i;
  for(i=0; i<nToken && blob_token(pIn, &aToken[i]); i++){}
  return i;
}












/*
** Do printf-style string rendering and append the results to a blob.
*/
void blob_appendf(Blob *pBlob, const char *zFormat, ...){
  va_list ap;
  va_start(ap, zFormat);
  vxprintf(pBlob, zFormat, ap);
  va_end(ap);
}
void blob_vappendf(Blob *pBlob, const char *zFormat, va_list ap){
  vxprintf(pBlob, zFormat, ap);
}

/*
** Initalize a blob to the data on an input channel.  Return 
** the number of bytes read into the blob.  Any prior content
** of the blob is discarded, not freed.
*/
569
570
571
572
573
574
575








576
577
578



579
580
581
582
583
584
585
      zName = zBuf;
      strcpy(zName, zFilename);
    }
    nName = file_simplify_name(zName, nName);
    for(i=1; i<nName; i++){
      if( zName[i]=='/' ){
        zName[i] = 0;








        if( file_mkdir(zName, 1) ){
          fossil_panic("unable to create directory %s", zName);
        }



        zName[i] = '/';
      }
    }
    out = fopen(zName, "wb");
    if( out==0 ){
      fossil_panic("unable to open file \"%s\" for writing", zName);
    }







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







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
      zName = zBuf;
      strcpy(zName, zFilename);
    }
    nName = file_simplify_name(zName, nName);
    for(i=1; i<nName; i++){
      if( zName[i]=='/' ){
        zName[i] = 0;
#ifdef __MINGW32__
        /*
        ** On Windows, local path looks like: C:/develop/project/file.txt
        ** The if stops us from trying to create a directory of a drive letter
        ** C: in this example.
        */
        if( !(i==2 && zName[1]==':') ){
#endif
          if( file_mkdir(zName, 1) ){
            fossil_panic("unable to create directory %s", zName);
          }
#ifdef __MINGW32__
        }
#endif
        zName[i] = '/';
      }
    }
    out = fopen(zName, "wb");
    if( out==0 ){
      fossil_panic("unable to open file \"%s\" for writing", zName);
    }
Added src/branch.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
/*
** Copyright (c) 2007 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 used to create new branches within a repository.
*/
#include "config.h"
#include "branch.h"
#include <assert.h>

void branch_new(void){
  int vid, nvid, noSign;
  Stmt q;
  char *zBranch, *zUuid, *zDate, *zComment;
  const char *zColor;
  Blob manifest;
  Blob mcksum;           /* Self-checksum on the manifest */
  Blob cksum1, cksum2;   /* Before and after commit checksums */
  Blob cksum1b;          /* Checksum recorded in the manifest */
 
  noSign = find_option("nosign","",0)!=0;
  db_must_be_within_tree();
  noSign = db_get_int("omitsign", 0)|noSign;
  zColor = find_option("bgcolor","c",1);
  
  verify_all_options();
  
  /* fossil branch new name */
  if( g.argc<3 ){
    usage("branch new ?-bgcolor COLOR BRANCH-NAME");
  }
  zBranch = g.argv[3];
  if( zBranch==0 || zBranch[0]==0 ){
    fossil_panic("branch name cannot be empty");
  }

  user_select();
  db_begin_transaction();
  if( unsaved_changes() ){
    fossil_panic("there are uncommitted changes. please commit first");
  }

  vid = db_lget_int("checkout", 0);
  vfile_aggregate_checksum_disk(vid, &cksum1);
  
  /* Create our new manifest */
  blob_zero(&manifest);
  zComment = mprintf("Branch created %s", zBranch);
  blob_appendf(&manifest, "C %F\n", zComment);
  zDate = db_text(0, "SELECT datetime('now')");
  zDate[10] = 'T';
  blob_appendf(&manifest, "D %s\n", zDate);

  db_prepare(&q,
    "SELECT pathname, uuid FROM vfile JOIN blob ON vfile.mrid=blob.rid"
    " WHERE NOT deleted AND vfile.vid=%d"
    " ORDER BY 1", vid);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    const char *zUuid = db_column_text(&q, 1);
    blob_appendf(&manifest, "F %F %s\n", zName, zUuid);
  }
  db_finalize(&q);
  
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
  blob_appendf(&manifest, "P %s\n", zUuid);
  blob_appendf(&manifest, "R %b\n", &cksum1);
  
  if( zColor!=0 ){
    if( strcmp("bgcolor",zBranch)>=0 ){
      blob_appendf(&manifest, "T *%F *\n", zBranch);
      blob_appendf(&manifest, "T *bgcolor * %F\n", zColor);
    }else{
      blob_appendf(&manifest, "T *bgcolor * %F\n", zColor);
      blob_appendf(&manifest, "T *%F *\n", zBranch);
    }
  }else{
    blob_appendf(&manifest, "T *%F *\n", zBranch);
  }

  /* Cancel any tags that propagate */
  db_prepare(&q, 
      "SELECT tagname"
      "  FROM tagxref JOIN tag ON tagxref.tagid=tag.tagid"
      " WHERE rid=%d AND tagtype=2", vid);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zTagname = db_column_text(&q, 0);
    blob_appendf(&manifest, "T -%s *\n", zTagname);
  }
  db_finalize(&q);
  
  blob_appendf(&manifest, "U %F\n", g.zLogin);
  md5sum_blob(&manifest, &mcksum);
  blob_appendf(&manifest, "Z %b\n", &mcksum);
  if( !noSign && clearsign(&manifest, &manifest) ){
    Blob ans;
    blob_zero(&ans);
    prompt_user("unable to sign manifest.  continue [y/N]? ", &ans);
    if( blob_str(&ans)[0]!='y' ){
      db_end_transaction(1);
      exit(1);
    }
  }
  
  /*blob_write_to_file(&manifest, "manifest.new");*/

  nvid = content_put(&manifest, 0, 0);
  if( nvid==0 ){
    fossil_panic("trouble committing manifest: %s", g.zErrMsg);
  }
  db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid);
  manifest_crosslink(nvid, &manifest);
  content_deltify(vid, nvid, 0);
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid);
  printf("Branch Version: %s\n", zUuid);
  printf("\n");
  printf("Notice: working copy not updated to the new branch. If\n");
  printf("        you wish to work on the new branch, update to\n");
  printf("        that branch first:\n");
  printf("\n");
  printf("        fossil update %s\n", zBranch);

  /* Verify that the manifest checksum matches the expected checksum */
  vfile_aggregate_checksum_repository(nvid, &cksum2);
  vfile_aggregate_checksum_manifest(nvid, &cksum2, &cksum1b);
  if( blob_compare(&cksum1, &cksum1b) ){
    fossil_panic("manifest checksum does not agree with manifest: "
                 "%b versus %b", &cksum1, &cksum1b);
  }
  
  /* Verify that the commit did not modify any disk images. */
  vfile_aggregate_checksum_disk(vid, &cksum2);
  if( blob_compare(&cksum1, &cksum2) ){
    fossil_panic("tree checksums before and after commit do not match");
  }

  /* Clear the undo/redo stack */
  undo_reset();

  /* Commit */
  db_end_transaction(0);
  
  /* Do an autosync push, if requested */
  autosync(0);
}

/*
** COMMAND: branch
**
** Usage: %fossil branch SUBCOMMAND ... ?-R|--repository FILE?
**
** Run various subcommands on the branches of the open repository o
** of the repository identified by the -R or --repository option.
**
**    %fossil branch new ?-bgcolor COLOR BRANCH-NAME
**
**        Create a new branch BRANCH-NAME. You can optionally give
**        a commit message and branch color.
**
**    %fossil branch list
**
**        List all branches
**
*/
void branch_cmd(void){
  int n;
  db_find_and_open_repository();
  if( g.argc<3 ){
    usage("new|list ...");
  }
  n = strlen(g.argv[2]);
  if( n>=2 && strncmp(g.argv[2],"new",n)==0 ){
    branch_new();
  }else if( n>=2 && strncmp(g.argv[2],"list",n)==0 ){
    fossil_panic("branch list is not yet completed");
  }else{
    fossil_panic("branch subcommand should be one of: "
                 "new list");
  }
}
Changes to src/cgi.c.
24
25
26
27
28
29
30






31
32
33
34
35
36
37


38
39
40
41
42
43
44
45
46
47
** This file contains C functions and procedures that provide useful
** services to CGI programs.  There are procedures for parsing and
** dispensing QUERY_STRING parameters and cookies, the "mprintf()"
** formatting function and its cousins, and routines to encode and
** decode strings in HTML or HTTP.
*/
#include "config.h"






#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/times.h>
#include <sys/time.h>
#include <sys/wait.h>


#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <unistd.h>
#include "cgi.h"

#if INTERFACE
/*
** Shortcuts for cgi_parameter.  P("x") returns the value of query parameter
** or cookie "x", or NULL if there is no such parameter or cookie.  PD("x","y")







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







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
** This file contains C functions and procedures that provide useful
** services to CGI programs.  There are procedures for parsing and
** dispensing QUERY_STRING parameters and cookies, the "mprintf()"
** formatting function and its cousins, and routines to encode and
** decode strings in HTML or HTTP.
*/
#include "config.h"
#ifdef __MINGW32__
#  include <windows.h>           /* for Sleep once server works again */
#  include <winsock2.h>          /* socket operations */
#  define sleep Sleep            /* windows does not have sleep, but Sleep */
#  include <ws2tcpip.h>          
#else
#  include <sys/socket.h>
#  include <netinet/in.h>
#  include <arpa/inet.h>

#  include <sys/times.h>
#  include <sys/time.h>
#  include <sys/wait.h>
#  include <sys/select.h>
#endif
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "cgi.h"

#if INTERFACE
/*
** Shortcuts for cgi_parameter.  P("x") returns the value of query parameter
** or cookie "x", or NULL if there is no such parameter or cookie.  PD("x","y")
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
*/
void cgi_set_cookie(
  const char *zName,    /* Name of the cookie */
  const char *zValue,   /* Value of the cookie.  Automatically escaped */
  const char *zPath,    /* Path cookie applies to.  NULL means "/" */
  int lifetime          /* Expiration of the cookie in seconds from now */
){
  if( zPath==0 ) zPath = "/";
  if( lifetime>0 ){
    lifetime += (int)time(0);
    blob_appendf(&extraHeader,
       "Set-Cookie: %s=%t; Path=%s; expires=%s; Version=1\r\n",
        zName, zValue, zPath, cgi_rfc822_datestamp(lifetime));
  }else{
    blob_appendf(&extraHeader,







|







148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
*/
void cgi_set_cookie(
  const char *zName,    /* Name of the cookie */
  const char *zValue,   /* Value of the cookie.  Automatically escaped */
  const char *zPath,    /* Path cookie applies to.  NULL means "/" */
  int lifetime          /* Expiration of the cookie in seconds from now */
){
  if( zPath==0 ) zPath = g.zTop;
  if( lifetime>0 ){
    lifetime += (int)time(0);
    blob_appendf(&extraHeader,
       "Set-Cookie: %s=%t; Path=%s; expires=%s; Version=1\r\n",
        zName, zValue, zPath, cgi_rfc822_datestamp(lifetime));
  }else{
    blob_appendf(&extraHeader,
286
287
288
289
290
291
292
293
294
295

296
297
298
299
300
301
302






303
304
305
306
307
308
309
**
** The URL must be relative to the base of the fossil server.
*/
void cgi_redirect(const char *zURL){
  char *zLocation;
  CGIDEBUG(("redirect to %s\n", zURL));
  if( strncmp(zURL,"http:",5)==0 || strncmp(zURL,"https:",6)==0 || *zURL=='/' ){
    cgi_panic("invalid redirect URL: %s", zURL);
  }
  zLocation = mprintf("Location: %s/%s\r\n", g.zBaseURL, zURL);

  cgi_append_header(zLocation);
  cgi_reset_content();
  cgi_printf("<html>\n<p>Redirect to %h</p>\n</html>\n", zURL);
  cgi_set_status(302, "Moved Temporarily");
  free(zLocation);
  cgi_reply();
  exit(0);






}

/*
** Information about all query parameters and cookies are stored
** in these variables.
*/
static int nAllocQP = 0; /* Space allocated for aParamQP[] */







|
|
|
>







>
>
>
>
>
>







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
**
** The URL must be relative to the base of the fossil server.
*/
void cgi_redirect(const char *zURL){
  char *zLocation;
  CGIDEBUG(("redirect to %s\n", zURL));
  if( strncmp(zURL,"http:",5)==0 || strncmp(zURL,"https:",6)==0 || *zURL=='/' ){
    zLocation = mprintf("Location: %s\r\n", zURL);
  }else{
    zLocation = mprintf("Location: %s/%s\r\n", g.zBaseURL, zURL);
  }
  cgi_append_header(zLocation);
  cgi_reset_content();
  cgi_printf("<html>\n<p>Redirect to %h</p>\n</html>\n", zURL);
  cgi_set_status(302, "Moved Temporarily");
  free(zLocation);
  cgi_reply();
  exit(0);
}
void cgi_redirectf(const char *zFormat, ...){
  va_list ap;
  va_start(ap, zFormat);
  cgi_redirect(vmprintf(zFormat, ap));
  va_end(ap);
}

/*
** Information about all query parameters and cookies are stored
** in these variables.
*/
static int nAllocQP = 0; /* Space allocated for aParamQP[] */
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
** Add another query parameter or cookie to the parameter set.
** zName is the name of the query parameter or cookie and zValue
** is its fully decoded value.
**
** zName and zValue are not copied and must not change or be
** deallocated after this routine returns.
*/
static void cgi_set_parameter_nocopy(const char *zName, const char *zValue){
  if( nAllocQP<=nUsedQP ){
    nAllocQP = nAllocQP*2 + 10;
    aParamQP = realloc( aParamQP, nAllocQP*sizeof(aParamQP[0]) );
    if( aParamQP==0 ) exit(1);
  }
  aParamQP[nUsedQP].zName = zName;
  aParamQP[nUsedQP].zValue = zValue;







|







334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
** Add another query parameter or cookie to the parameter set.
** zName is the name of the query parameter or cookie and zValue
** is its fully decoded value.
**
** zName and zValue are not copied and must not change or be
** deallocated after this routine returns.
*/
void cgi_set_parameter_nocopy(const char *zName, const char *zValue){
  if( nAllocQP<=nUsedQP ){
    nAllocQP = nAllocQP*2 + 10;
    aParamQP = realloc( aParamQP, nAllocQP*sizeof(aParamQP[0]) );
    if( aParamQP==0 ) exit(1);
  }
  aParamQP[nUsedQP].zName = zName;
  aParamQP[nUsedQP].zValue = zValue;
343
344
345
346
347
348
349












350
351
352
353
354
355
356
** is its fully decoded value.
**
** Copies are made of both the zName and zValue parameters.
*/
void cgi_set_parameter(const char *zName, const char *zValue){
  cgi_set_parameter_nocopy(mprintf("%s",zName), mprintf("%s",zValue));
}













/*
** Add a query parameter.  The zName portion is fixed but a copy
** must be made of zValue.
*/
void cgi_setenv(const char *zName, const char *zValue){
  cgi_set_parameter_nocopy(zName, mprintf("%s",zValue));







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







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
** is its fully decoded value.
**
** Copies are made of both the zName and zValue parameters.
*/
void cgi_set_parameter(const char *zName, const char *zValue){
  cgi_set_parameter_nocopy(mprintf("%s",zName), mprintf("%s",zValue));
}

/*
** Replace a parameter with a new value.
*/
void cgi_replace_parameter(const char *zName, const char *zValue){
  int i;
  for(i=0; i<nUsedQP; i++){
    if( strcmp(aParamQP[i].zName,zName)==0 ){
      aParamQP[i].zValue = zValue;
    }
  }
}

/*
** Add a query parameter.  The zName portion is fixed but a copy
** must be made of zValue.
*/
void cgi_setenv(const char *zName, const char *zValue){
  cgi_set_parameter_nocopy(zName, mprintf("%s",zValue));
698
699
700
701
702
703
704












705
706
707
708
709
710
711
      CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
      return zValue;
    }
  }
  CGIDEBUG(("no-match [%s]\n", zName));
  return zDefault;
}













/*
** Print CGI debugging messages.
*/
void cgi_debug(const char *zFormat, ...){
  va_list ap;
  if( g.fDebug ){







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







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
      CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
      return zValue;
    }
  }
  CGIDEBUG(("no-match [%s]\n", zName));
  return zDefault;
}

/*
** Return the name of the i-th CGI parameter.  Return NULL if there
** are fewer than i registered CGI parmaeters.
*/
const char *cgi_parameter_name(int i){
  if( i>=0 && i<nUsedQP ){
    return aParamQP[i].zName;
  }else{
    return 0;
  }
}

/*
** Print CGI debugging messages.
*/
void cgi_debug(const char *zFormat, ...){
  va_list ap;
  if( g.fDebug ){
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
        strcmp(zVal, zD) ? "" : " selected"
      );
    }
  }
  cgi_printf("%*s</select>\n", in, "");
}

/* 
** This function implements the callback from vxprintf. 
**
** This routine sends nNewChar characters of text in zNewText to
** CGI reply content buffer.
*/
static void sout(void *NotUsed, const char *zNewText, int nNewChar){
  cgi_append_content(zNewText, nNewChar);
}

/*
** This routine works like "printf" except that it has the
** extra formatting capabilities such as %h and %t.
*/
void cgi_printf(const char *zFormat, ...){
  va_list ap;
  va_start(ap,zFormat);
  vxprintf(sout,0,zFormat,ap);
  va_end(ap);
}

/*
** This routine works like "vprintf" except that it has the
** extra formatting capabilities such as %h and %t.
*/
void cgi_vprintf(const char *zFormat, va_list ap){
  vxprintf(sout,0,zFormat,ap);
}


/*
** Send a reply indicating that the HTTP request was malformed
*/
static void malformed_request(void){







<
<
<
<
<
<
<
<
<
<







|








|







930
931
932
933
934
935
936










937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
        strcmp(zVal, zD) ? "" : " selected"
      );
    }
  }
  cgi_printf("%*s</select>\n", in, "");
}











/*
** This routine works like "printf" except that it has the
** extra formatting capabilities such as %h and %t.
*/
void cgi_printf(const char *zFormat, ...){
  va_list ap;
  va_start(ap,zFormat);
  vxprintf(&cgiContent,zFormat,ap);
  va_end(ap);
}

/*
** This routine works like "vprintf" except that it has the
** extra formatting capabilities such as %h and %t.
*/
void cgi_vprintf(const char *zFormat, va_list ap){
  vxprintf(&cgiContent,zFormat,ap);
}


/*
** Send a reply indicating that the HTTP request was malformed
*/
static void malformed_request(void){
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
  cgi_reset_content();
  cgi_set_status(500, "Internal Server Error");
  cgi_printf(
    "<html><body><h1>Internal Server Error</h1>\n"
    "<plaintext>"
  );
  va_start(ap, zFormat);
  vxprintf(sout,0,zFormat,ap);
  va_end(ap);
  cgi_reply();
  exit(1);
}

/*
** Remove the first space-delimited token from a string and return







|







974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
  cgi_reset_content();
  cgi_set_status(500, "Internal Server Error");
  cgi_printf(
    "<html><body><h1>Internal Server Error</h1>\n"
    "<plaintext>"
  );
  va_start(ap, zFormat);
  vxprintf(&cgiContent,zFormat,ap);
  va_end(ap);
  cgi_reply();
  exit(1);
}

/*
** Remove the first space-delimited token from a string and return
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
    malformed_request();
  }
  cgi_setenv("REQUEST_URI", zToken);
  for(i=0; zToken[i] && zToken[i]!='?'; i++){}
  if( zToken[i] ) zToken[i++] = 0;
  cgi_setenv("PATH_INFO", zToken);
  cgi_setenv("QUERY_STRING", &zToken[i]);
  if( getpeername(fileno(stdin), (struct sockaddr*)&remoteName, &size)>=0 ){
    char *zIpAddr = inet_ntoa(remoteName.sin_addr);
    cgi_setenv("REMOTE_ADDR", zIpAddr);

    /* Set the Global.zIpAddr variable to the server we are talking to.
    ** This is used to populate the ipaddr column of the rcvfrom table,
    ** if any files are received from the connected client.
    */







|







1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
    malformed_request();
  }
  cgi_setenv("REQUEST_URI", zToken);
  for(i=0; zToken[i] && zToken[i]!='?'; i++){}
  if( zToken[i] ) zToken[i++] = 0;
  cgi_setenv("PATH_INFO", zToken);
  cgi_setenv("QUERY_STRING", &zToken[i]);
  if( getpeername(fileno(stdin), (struct sockaddr*)&remoteName, (socklen_t*)&size)>=0 ){
    char *zIpAddr = inet_ntoa(remoteName.sin_addr);
    cgi_setenv("REMOTE_ADDR", zIpAddr);

    /* Set the Global.zIpAddr variable to the server we are talking to.
    ** This is used to populate the ipaddr column of the rcvfrom table,
    ** if any files are received from the connected client.
    */
1074
1075
1076
1077
1078
1079
1080




1081
1082
1083
1084
1085
1086
1087
** Implement an HTTP server daemon listening on port iPort.
**
** As new connections arrive, fork a child and let child return
** out of this procedure call.  The child will handle the request.
** The parent never returns from this procedure.
*/
void cgi_http_server(int iPort){




  int listener;                /* The server socket */
  int connection;              /* A socket for each individual connection */
  fd_set readfds;              /* Set of file descriptors for select() */
  size_t lenaddr;              /* Length of the inaddr structure */
  int child;                   /* PID of the child process */
  int nchildren = 0;           /* Number of child processes */
  struct timeval delay;        /* How long to wait inside select() */







>
>
>
>







1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
** Implement an HTTP server daemon listening on port iPort.
**
** As new connections arrive, fork a child and let child return
** out of this procedure call.  The child will handle the request.
** The parent never returns from this procedure.
*/
void cgi_http_server(int iPort){
#ifdef __MINGW32__
  fprintf(stderr,"server not yet available in windows version of fossil\n");
  exit(1);
#else
  int listener;                /* The server socket */
  int connection;              /* A socket for each individual connection */
  fd_set readfds;              /* Set of file descriptors for select() */
  size_t lenaddr;              /* Length of the inaddr structure */
  int child;                   /* PID of the child process */
  int nchildren = 0;           /* Number of child processes */
  struct timeval delay;        /* How long to wait inside select() */
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
    }
    delay.tv_sec = 60;
    delay.tv_usec = 0;
    FD_ZERO(&readfds);
    FD_SET( listener, &readfds);
    if( select( listener+1, &readfds, 0, 0, &delay) ){
      lenaddr = sizeof(inaddr);
      connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr);
      if( connection>=0 ){
        child = fork();
        if( child!=0 ){
          if( child>0 ) nchildren++;
          close(connection);
        }else{
          close(0);







|







1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
    }
    delay.tv_sec = 60;
    delay.tv_usec = 0;
    FD_ZERO(&readfds);
    FD_SET( listener, &readfds);
    if( select( listener+1, &readfds, 0, 0, &delay) ){
      lenaddr = sizeof(inaddr);
      connection = accept(listener, (struct sockaddr*)&inaddr, (socklen_t*) &lenaddr);
      if( connection>=0 ){
        child = fork();
        if( child!=0 ){
          if( child>0 ) nchildren++;
          close(connection);
        }else{
          close(0);
1140
1141
1142
1143
1144
1145
1146

1147
1148
1149
1150
1151
1152
1153
    /* Bury dead children */
    while( waitpid(0, 0, WNOHANG)>0 ){
      nchildren--;
    }
  }
  /* NOT REACHED */  
  exit(1);

}

/*
** Name of days and months.
*/
static const char *azDays[] =
    {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 0};







>







1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
    /* Bury dead children */
    while( waitpid(0, 0, WNOHANG)>0 ){
      nchildren--;
    }
  }
  /* NOT REACHED */  
  exit(1);
#endif
}

/*
** Name of days and months.
*/
static const char *azDays[] =
    {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 0};
Changes to src/checkin.c.
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
    "WHERE file_is_selected(id) AND (chnged OR deleted OR rid=0) ORDER BY 1"
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zPathname = db_column_text(&q,0);
    int isDeleted = db_column_int(&q, 1);
    int isChnged = db_column_int(&q,2);
    int isNew = db_column_int(&q,3)==0;

    blob_append(report, zPrefix, nPrefix);


    if( isNew ){
      blob_appendf(report, "ADDED    %s\n", zPathname);
    }else if( isDeleted ){
      blob_appendf(report, "DELETED  %s\n", zPathname);
    }else if( isChnged==2 ){
      blob_appendf(report, "UPDATED_BY_MERGE %s\n", zPathname);
    }else if( isChnged==3 ){
      blob_appendf(report, "ADDED_BY_MERGE %s\n", zPathname);
    }else{
      blob_appendf(report, "EDITED   %s\n", zPathname);
    }

  }
  db_finalize(&q);
  db_prepare(&q, "SELECT uuid FROM vmerge JOIN blob ON merge=rid"
                 " WHERE id=0");
  while( db_step(&q)==SQLITE_ROW ){
    blob_append(report, zPrefix, nPrefix);
    blob_appendf(report, "MERGED_WITH %s\n", db_column_text(&q, 0));







>

>
>
|










>







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
    "WHERE file_is_selected(id) AND (chnged OR deleted OR rid=0) ORDER BY 1"
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zPathname = db_column_text(&q,0);
    int isDeleted = db_column_int(&q, 1);
    int isChnged = db_column_int(&q,2);
    int isNew = db_column_int(&q,3)==0;
    char *zFullName = mprintf("%s/%s", g.zLocalRoot, zPathname);
    blob_append(report, zPrefix, nPrefix);
    if( access(zFullName, 0) ){
      blob_appendf(report, "MISSING  %s\n", zPathname);
    }else if( isNew ){
      blob_appendf(report, "ADDED    %s\n", zPathname);
    }else if( isDeleted ){
      blob_appendf(report, "DELETED  %s\n", zPathname);
    }else if( isChnged==2 ){
      blob_appendf(report, "UPDATED_BY_MERGE %s\n", zPathname);
    }else if( isChnged==3 ){
      blob_appendf(report, "ADDED_BY_MERGE %s\n", zPathname);
    }else{
      blob_appendf(report, "EDITED   %s\n", zPathname);
    }
    free(zFullName);
  }
  db_finalize(&q);
  db_prepare(&q, "SELECT uuid FROM vmerge JOIN blob ON merge=rid"
                 " WHERE id=0");
  while( db_step(&q)==SQLITE_ROW ){
    blob_append(report, zPrefix, nPrefix);
    blob_appendf(report, "MERGED_WITH %s\n", db_column_text(&q, 0));
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
    printf("%s\n", db_column_text(&q, 0));
  }
  db_finalize(&q);
}

/*
** COMMAND: clean
** Usage: %fossil clean
** Delete all "extra" files in the source tree.  "Extra" files are
** files that are not officially part of the checkout.  See also
** the "extra" command.




*/
void clean_cmd(void){

  Blob path;
  Stmt q;

  db_must_be_within_tree();
  db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)");
  chdir(g.zLocalRoot);
  blob_zero(&path);
  vfile_scan(0, &path);
  db_prepare(&q, 
      "SELECT %Q || x FROM sfile"
      " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_')"
      " ORDER BY 1", g.zLocalRoot);
  while( db_step(&q)==SQLITE_ROW ){

    unlink(db_column_text(&q, 0));










  }
  db_finalize(&q);
}

/*
** Prepare a commit comment.  Let the user modify it using the
** editor specified in the global_config table or either







|


|
>
>
>
>


>


>










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







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
    printf("%s\n", db_column_text(&q, 0));
  }
  db_finalize(&q);
}

/*
** COMMAND: clean
** Usage: %fossil clean ?-all
** Delete all "extra" files in the source tree.  "Extra" files are
** files that are not officially part of the checkout.  See also
** the "extra" command. This operation cannot be undone. 
**
** You will be prompted before removing each file. If you are
** sure you wish to remove all "extra" files you can specify the
** optional -all flag.
*/
void clean_cmd(void){
  int allFlag;
  Blob path;
  Stmt q;
  allFlag = find_option("all","a",0)!=0;
  db_must_be_within_tree();
  db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)");
  chdir(g.zLocalRoot);
  blob_zero(&path);
  vfile_scan(0, &path);
  db_prepare(&q, 
      "SELECT %Q || x FROM sfile"
      " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_')"
      " ORDER BY 1", g.zLocalRoot);
  while( db_step(&q)==SQLITE_ROW ){
    if( allFlag ){
      unlink(db_column_text(&q, 0));
    }else{
      Blob ans;
      char *prompt = mprintf("remove unmanaged file \"%s\" [y/N]? ",
                              db_column_text(&q, 0));
      blob_zero(&ans);
      prompt_user(prompt, &ans);
      if( blob_str(&ans)[0]=='y' ){
        unlink(db_column_text(&q, 0));
      }
    }
  }
  db_finalize(&q);
}

/*
** Prepare a commit comment.  Let the user modify it using the
** editor specified in the global_config table or either
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
  char *zComment;
  int i;
  blob_set(&text,
    "\n# Enter comments on this commit.  Lines beginning with # are ignored\n"
    "#\n"
  );
  status_report(&text, "# ");
  zEditor = db_global_get("editor", 0);
  if( zEditor==0 ){
    zEditor = getenv("VISUAL");
  }
  if( zEditor==0 ){
    zEditor = getenv("EDITOR");
  }
  if( zEditor==0 ){
    zEditor = "ed";
  }
  zFile = db_text(0, "SELECT '%qci-comment-' || hex(randomblob(6)) || '.txt'",
                   g.zLocalRoot);
  blob_write_to_file(&text, zFile);
  zCmd = mprintf("%s %s", zEditor, zFile);
  printf("%s\n", zCmd);
  if( system(zCmd) ){
    fossil_panic("editor aborted");
  }
  blob_reset(&text);
  blob_read_from_file(&text, zFile);
  unlink(zFile);







|












|







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
  char *zComment;
  int i;
  blob_set(&text,
    "\n# Enter comments on this commit.  Lines beginning with # are ignored\n"
    "#\n"
  );
  status_report(&text, "# ");
  zEditor = db_get("editor", 0);
  if( zEditor==0 ){
    zEditor = getenv("VISUAL");
  }
  if( zEditor==0 ){
    zEditor = getenv("EDITOR");
  }
  if( zEditor==0 ){
    zEditor = "ed";
  }
  zFile = db_text(0, "SELECT '%qci-comment-' || hex(randomblob(6)) || '.txt'",
                   g.zLocalRoot);
  blob_write_to_file(&text, zFile);
  zCmd = mprintf("%s \"%s\"", zEditor, zFile);
  printf("%s\n", zCmd);
  if( system(zCmd) ){
    fossil_panic("editor aborted");
  }
  blob_reset(&text);
  blob_read_from_file(&text, zFile);
  unlink(zFile);
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
** prompted for your GPG passphrase in order to sign the new manifest
** unless the "--nosign" options is used.  All files that have
** changed will be committed unless some subset of files is specified
** on the command line.
*/
void commit_cmd(void){
  int rc;
  int vid, nrid, nvid;
  Blob comment;
  const char *zComment;
  Stmt q;
  Stmt q2;
  char *zUuid, *zDate;
  int noSign = 0;        /* True to omit signing the manifest using GPG */
  int isAMerge = 0;      /* True if checking in a merge */

  char *zManifestFile;   /* Name of the manifest file */
  Blob manifest;
  Blob muuid;            /* Manifest uuid */
  Blob mcksum;           /* Self-checksum on the manifest */
  Blob cksum1, cksum2;   /* Before and after commit checksums */
  Blob cksum1b;          /* Checksum recorded in the manifest */
 
  noSign = find_option("nosign","",0)!=0;
  zComment = find_option("comment","m",1);

  db_must_be_within_tree();
  noSign = db_get_int("omit-ci-sig", 0)|noSign;
  verify_all_options();






  /* There are two ways this command may be executed. If there are
  ** no arguments following the word "commit", then all modified files
  ** in the checked out directory are committed. If one or more arguments
  ** follows "commit", then only those files are committed.
  **
  ** After the following function call has returned, the Global.aCommitFile[]
  ** array is allocated to contain the "id" field from the vfile table
  ** for each file to be committed. Or, if aCommitFile is NULL, all files
  ** should be committed.
  */
  select_commit_files();
  isAMerge = db_exists("SELECT 1 FROM vmerge");
  if( g.aCommitFile && isAMerge ){
    fossil_fatal("cannot do a partial commit of a merge");
  }

  user_select();
  db_begin_transaction();
  rc = unsaved_changes();
  if( rc==0 && !isAMerge ){
    fossil_panic("nothing has changed");
  }

  /* If one or more files that were named on the command line have not
  ** been modified, bail out now.
  */
  if( g.aCommitFile ){
    Blob unmodified;
    memset(&unmodified, 0, sizeof(Blob));
    blob_init(&unmodified, 0, 0);
    db_blob(&unmodified, 
      "SELECT pathname FROM vfile WHERE chnged = 0 AND file_is_selected(id)"
    );
    if( strlen(blob_str(&unmodified)) ){
      fossil_panic("file %s has not changed", blob_str(&unmodified));
    }
  }

  vid = db_lget_int("checkout", 0);






  vfile_aggregate_checksum_disk(vid, &cksum1);
  if( zComment ){
    blob_zero(&comment);
    blob_append(&comment, zComment, -1);
  }else{
    prepare_commit_comment(&comment);
  }







|







>









>

|

|
>
>
>
>
>



















|



















>
>
>
>
>
>







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
** prompted for your GPG passphrase in order to sign the new manifest
** unless the "--nosign" options is used.  All files that have
** changed will be committed unless some subset of files is specified
** on the command line.
*/
void commit_cmd(void){
  int rc;
  int vid, nrid, nvid, wouldFork=0;
  Blob comment;
  const char *zComment;
  Stmt q;
  Stmt q2;
  char *zUuid, *zDate;
  int noSign = 0;        /* True to omit signing the manifest using GPG */
  int isAMerge = 0;      /* True if checking in a merge */
  int forceFlag = 0;     /* Force a fork */
  char *zManifestFile;   /* Name of the manifest file */
  Blob manifest;
  Blob muuid;            /* Manifest uuid */
  Blob mcksum;           /* Self-checksum on the manifest */
  Blob cksum1, cksum2;   /* Before and after commit checksums */
  Blob cksum1b;          /* Checksum recorded in the manifest */
 
  noSign = find_option("nosign","",0)!=0;
  zComment = find_option("comment","m",1);
  forceFlag = find_option("force", "r", 0)!=0;
  db_must_be_within_tree();
  noSign = db_get_int("omitsign", 0)|noSign;
  verify_all_options();
  
  /*
  ** Autosync if requested.
  */
  autosync(1);
  
  /* There are two ways this command may be executed. If there are
  ** no arguments following the word "commit", then all modified files
  ** in the checked out directory are committed. If one or more arguments
  ** follows "commit", then only those files are committed.
  **
  ** After the following function call has returned, the Global.aCommitFile[]
  ** array is allocated to contain the "id" field from the vfile table
  ** for each file to be committed. Or, if aCommitFile is NULL, all files
  ** should be committed.
  */
  select_commit_files();
  isAMerge = db_exists("SELECT 1 FROM vmerge");
  if( g.aCommitFile && isAMerge ){
    fossil_fatal("cannot do a partial commit of a merge");
  }

  user_select();
  db_begin_transaction();
  rc = unsaved_changes();
  if( rc==0 && !isAMerge && !forceFlag ){
    fossil_panic("nothing has changed");
  }

  /* If one or more files that were named on the command line have not
  ** been modified, bail out now.
  */
  if( g.aCommitFile ){
    Blob unmodified;
    memset(&unmodified, 0, sizeof(Blob));
    blob_init(&unmodified, 0, 0);
    db_blob(&unmodified, 
      "SELECT pathname FROM vfile WHERE chnged = 0 AND file_is_selected(id)"
    );
    if( strlen(blob_str(&unmodified)) ){
      fossil_panic("file %s has not changed", blob_str(&unmodified));
    }
  }

  vid = db_lget_int("checkout", 0);
  if( db_exists("SELECT 1 FROM plink WHERE pid=%d", vid) ){
    wouldFork=1;
    if( forceFlag==0 && db_get_int("safemerge", 0)==0 ){
      fossil_fatal("would fork.  use -f or --force");
    }
  }
  vfile_aggregate_checksum_disk(vid, &cksum1);
  if( zComment ){
    blob_zero(&comment);
    blob_append(&comment, zComment, -1);
  }else{
    prepare_commit_comment(&comment);
  }
503
504
505
506
507
508
509
510
511












    fossil_panic("tree checksums before and after commit do not match");
  }

  /* Clear the undo/redo stack */
  undo_reset();

  /* Commit */
  db_end_transaction(0);  
}



















|
|
>
>
>
>
>
>
>
>
>
>
>
>
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
    fossil_panic("tree checksums before and after commit do not match");
  }

  /* Clear the undo/redo stack */
  undo_reset();

  /* Commit */
  db_end_transaction(0);
  
  if( wouldFork==0 ){
    /* Do an autosync push if requested. If wouldFork == 1, then they either
    ** forced this commit or safe merge is on, and this commit did indeed
    ** create a fork. In this case, we want the user to merge before sending
    ** their new commit back to the rest of the world, so do not auto-push.
    */
    autosync(0);
  }else{
    printf("Warning: commit caused a fork to occur. Please merge and push\n");
    printf("         your changes as soon as possible.\n");
  }
}
Changes to src/clearsign.c.
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
** Clearsign the given blob.  Put the signed version in
** pOut.
*/
int clearsign(Blob *pIn, Blob *pOut){
  char *zRand;
  char *zIn;
  char *zOut;
  char *zBase = db_global_get("clear-sign", "gpg --clearsign -o ");
  char *zCmd;
  int rc;
  zRand = db_text(0, "SELECT hex(randomblob(10))");
  zOut = mprintf("out-%s", zRand);
  zIn = mprintf("in-%z", zRand);
  blob_write_to_file(pIn, zOut);
  zCmd = mprintf("%s %s %s", zBase, zIn, zOut);







|







32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
** Clearsign the given blob.  Put the signed version in
** pOut.
*/
int clearsign(Blob *pIn, Blob *pOut){
  char *zRand;
  char *zIn;
  char *zOut;
  char *zBase = db_get("clear-sign", "gpg --clearsign -o ");
  char *zCmd;
  int rc;
  zRand = db_text(0, "SELECT hex(randomblob(10))");
  zOut = mprintf("out-%s", zRand);
  zIn = mprintf("in-%z", zRand);
  blob_write_to_file(pIn, zOut);
  zCmd = mprintf("%s %s %s", zBase, zIn, zOut);
Changes to src/clone.c.
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
** Make a clone of a repository specified by URL in the local
** file named FILENAME.  
*/
void clone_cmd(void){
  if( g.argc!=4 ){
    usage("FILE-OR-URL NEW-REPOSITORY");
  }

  if( file_size(g.argv[3])>0 ){
    fossil_panic("file already exists: %s", g.argv[3]);
  }
  url_parse(g.argv[2]);
  db_create_repository(g.argv[3]);
  db_open_repository(g.argv[3]);


  user_select();
  db_set("content-schema", CONTENT_SCHEMA);
  db_set("aux-schema", AUX_SCHEMA);
  if( !g.urlIsFile ){
    db_set("last-sync-url", g.argv[2]);
  }
  db_multi_exec(
    "INSERT INTO config(name,value) VALUES('server-code', hex(randomblob(20)));"

  );
  if( g.urlIsFile ){
    Stmt q;
    db_multi_exec("ATTACH DATABASE %Q AS orig", g.urlName);
    db_begin_transaction();
    db_prepare(&q, 
      "SELECT name FROM orig.sqlite_master"
      " WHERE type='table'"
    );
    while( db_step(&q)==SQLITE_ROW ){
      const char *zTab = db_column_text(&q, 0);
      db_multi_exec("INSERT OR IGNORE INTO %Q SELECT * FROM orig.%Q",
                    zTab, zTab);
    }
    db_finalize(&q);
    db_end_transaction(0);
  }else{
    client_sync(0,0,1);
  }

}







>






>
>

|
|

|


|
>




<










<



>

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
** Make a clone of a repository specified by URL in the local
** file named FILENAME.  
*/
void clone_cmd(void){
  if( g.argc!=4 ){
    usage("FILE-OR-URL NEW-REPOSITORY");
  }
  db_open_config();
  if( file_size(g.argv[3])>0 ){
    fossil_panic("file already exists: %s", g.argv[3]);
  }
  url_parse(g.argv[2]);
  db_create_repository(g.argv[3]);
  db_open_repository(g.argv[3]);
  db_begin_transaction();
  db_initial_setup(0, 0);
  user_select();
  db_set("content-schema", CONTENT_SCHEMA, 0);
  db_set("aux-schema", AUX_SCHEMA, 0);
  if( !g.urlIsFile ){
    db_set("last-sync-url", g.argv[2], 0);
  }
  db_multi_exec(
    "INSERT INTO config(name,value)"
    " VALUES('server-code', lower(hex(randomblob(20))));"
  );
  if( g.urlIsFile ){
    Stmt q;
    db_multi_exec("ATTACH DATABASE %Q AS orig", g.urlName);

    db_prepare(&q, 
      "SELECT name FROM orig.sqlite_master"
      " WHERE type='table'"
    );
    while( db_step(&q)==SQLITE_ROW ){
      const char *zTab = db_column_text(&q, 0);
      db_multi_exec("INSERT OR IGNORE INTO %Q SELECT * FROM orig.%Q",
                    zTab, zTab);
    }
    db_finalize(&q);

  }else{
    client_sync(0,0,1);
  }
  db_end_transaction(0);
}
Changes to src/construct.c.
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

  /* Create the foundation */
  db_create_repository(zRepository);
  db_open_repository(zRepository);
  db_open_config();
  db_begin_transaction();

  db_initial_setup(0);

  printf("project-id: %s\n", db_get("project-code", 0));
  printf("server-id:  %s\n", db_get("server-code", 0));
  printf("admin-user: %s (no password set yet!)\n", g.zLogin);
  printf("baseline:   %s\n", db_text(0, "SELECT uuid FROM blob"));

  /* Scan origin and insert all files found inside */
  fileCnt = import_origin (zOrigin);

  printf("imported:   %d %s\n", fileCnt, fileCnt == 1 ?
	 "file" : "files");

  /* Finalize the repository, rebuild the derived tables */
  errCnt = rebuild_db ();

  if( errCnt ){
    printf("%d %s. Rolling back changes.\n", errCnt, errCnt == 1 ?
	   "error" : "errors");
    db_end_transaction(1);
  }else{
    db_end_transaction(0);







|













|







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

  /* Create the foundation */
  db_create_repository(zRepository);
  db_open_repository(zRepository);
  db_open_config();
  db_begin_transaction();

  db_initial_setup(0, 1);

  printf("project-id: %s\n", db_get("project-code", 0));
  printf("server-id:  %s\n", db_get("server-code", 0));
  printf("admin-user: %s (no password set yet!)\n", g.zLogin);
  printf("baseline:   %s\n", db_text(0, "SELECT uuid FROM blob"));

  /* Scan origin and insert all files found inside */
  fileCnt = import_origin (zOrigin);

  printf("imported:   %d %s\n", fileCnt, fileCnt == 1 ?
	 "file" : "files");

  /* Finalize the repository, rebuild the derived tables */
  errCnt = rebuild_db(0, 0);

  if( errCnt ){
    printf("%d %s. Rolling back changes.\n", errCnt, errCnt == 1 ?
	   "error" : "errors");
    db_end_transaction(1);
  }else{
    db_end_transaction(0);
Changes to src/content.c.
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
** is a phantom, zero pBlob and return 0.
*/
int content_get(int rid, Blob *pBlob){
  Stmt q;
  Blob src;
  int srcid;
  int rc = 0;


  assert( g.repositoryOpen );
  srcid = findSrcid(rid);
  blob_zero(pBlob);
  if( srcid ){











    if( content_get(srcid, &src) ){
      db_prepare(&q, "SELECT content FROM blob WHERE rid=%d AND size>=0", rid);
      if( db_step(&q)==SQLITE_ROW ){
        Blob delta;
        db_ephemeral_blob(&q, 0, &delta);
        blob_uncompress(&delta, &delta);
        blob_init(pBlob,0,0);
        blob_delta_apply(&src, &delta, pBlob);
        blob_reset(&delta);
        rc = 1;
      }
      db_finalize(&q);
      blob_reset(&src);
    }

  }else{
    db_prepare(&q, "SELECT content FROM blob WHERE rid=%d AND size>=0", rid);
    if( db_step(&q)==SQLITE_ROW ){
      db_ephemeral_blob(&q, 0, pBlob);
      blob_uncompress(pBlob, pBlob);
      rc = 1;
    }
    db_finalize(&q);
  }
  return rc;
}



























/*
** COMMAND:  test-content-get
**
** Extract a blob from the database and write it into a file.
*/
void test_content_get_cmd(void){







>





>
>
>
>
>
>
>
>
>
>
>














>











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







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
** is a phantom, zero pBlob and return 0.
*/
int content_get(int rid, Blob *pBlob){
  Stmt q;
  Blob src;
  int srcid;
  int rc = 0;
  static Bag inProcess;

  assert( g.repositoryOpen );
  srcid = findSrcid(rid);
  blob_zero(pBlob);
  if( srcid ){
    if( bag_find(&inProcess, srcid) ){
      db_multi_exec(
        "UPDATE blob SET content=NULL, size=-1 WHERE rid=%d;"
        "DELETE FROM delta WHERE rid=%d;"
        "INSERT OR IGNORE INTO phantom VALUES(%d);",
        srcid, srcid, srcid
      );
      blob_zero(pBlob);
      return 0;
    }
    bag_insert(&inProcess, srcid);
    if( content_get(srcid, &src) ){
      db_prepare(&q, "SELECT content FROM blob WHERE rid=%d AND size>=0", rid);
      if( db_step(&q)==SQLITE_ROW ){
        Blob delta;
        db_ephemeral_blob(&q, 0, &delta);
        blob_uncompress(&delta, &delta);
        blob_init(pBlob,0,0);
        blob_delta_apply(&src, &delta, pBlob);
        blob_reset(&delta);
        rc = 1;
      }
      db_finalize(&q);
      blob_reset(&src);
    }
    bag_remove(&inProcess, srcid);
  }else{
    db_prepare(&q, "SELECT content FROM blob WHERE rid=%d AND size>=0", rid);
    if( db_step(&q)==SQLITE_ROW ){
      db_ephemeral_blob(&q, 0, pBlob);
      blob_uncompress(pBlob, pBlob);
      rc = 1;
    }
    db_finalize(&q);
  }
  return rc;
}

/*
** Get the contents of a file within a given revision.
*/
int content_get_historical_file(const char *revision, const char *file, Blob *content){
  Blob mfile;
  Manifest m;
  int i, rid=0;
  
  rid = name_to_rid(revision);
  content_get(rid, &mfile);
  
  if( manifest_parse(&m, &mfile) ){
    for(i=0; i<m.nFile; i++){
      if( strcmp(m.aFile[i].zName, file)==0 ){
        rid = uuid_to_rid(m.aFile[i].zUuid, 0);
        return content_get(rid, content);
      }
    }
    fossil_panic("file: %s does not exist in revision: %s", file, revision);
  }else{
    fossil_panic("could not parse manifest for revision: %s", revision);
  }
  
  return 0;
}

/*
** COMMAND:  test-content-get
**
** Extract a blob from the database and write it into a file.
*/
void test_content_get_cmd(void){
Changes to src/db.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
/*
** Copyright (c) 2006 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/
**
*******************************************************************************
** 
** Code for interfacing to the various databases.
**
** There are three separate database files that fossil interacts
** with:
**
**    (1)  The "user" database in ~/.fossil
**
**    (2)  The "repository" database
**
**    (3)  A local checkout database named "FOSSIL" and located at the
**         root of the local copy of the source tree.
**
*/
#include "config.h"



#include <sqlite3.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <unistd.h>
#include "db.h"

#if INTERFACE
/*
** An single SQL statement is represented as an instance of the following
** structure.











|










|














>
>
>



<







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
/*
** Copyright (c) 2006 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/
**
*******************************************************************************
**
** Code for interfacing to the various databases.
**
** There are three separate database files that fossil interacts
** with:
**
**    (1)  The "user" database in ~/.fossil
**
**    (2)  The "repository" database
**
**    (3)  A local checkout database named "FOSSIL" and located at the
**         root of the local copy of the source tree.
**
*/
#include "config.h"
#ifndef __MINGW32__
#  include <pwd.h>
#endif
#include <sqlite3.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <unistd.h>
#include "db.h"

#if INTERFACE
/*
** An single SQL statement is represented as an instance of the following
** structure.
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
    style_footer();
    cgi_reply();
  }else{
    fprintf(stderr, "%s: %s\n", g.argv[0], z);
  }
  db_force_rollback();
  exit(1);
  exit(1);
}

static int nBegin = 0;      /* Nesting depth of BEGIN */
static int doRollback = 0;  /* True to force a rollback */























/*
** Begin and end a nested transaction
*/
void db_begin_transaction(void){
  if( nBegin==0 ) db_multi_exec("BEGIN");



  nBegin++;
}
void db_end_transaction(int rollbackFlag){
  if( rollbackFlag ) doRollback = 1;
  nBegin--;
  if( nBegin==0 ){




    db_multi_exec(doRollback ? "ROLLBACK" : "COMMIT");
    doRollback = 0;
  }
}
void db_force_rollback(void){
  if( nBegin ){
    sqlite3_exec(g.db, "ROLLBACK", 0, 0, 0);
  }
  nBegin = 0;
}
 





























/*
** Prepare or reprepare the sqlite3 statement from the raw SQL text.
*/
static void reprepare(Stmt *pStmt){
  sqlite3_stmt *pNew;
  if( sqlite3_prepare(g.db, blob_buffer(&pStmt->sql), -1, &pNew, 0)!=0 ){
    db_err("%s\n%s", blob_str(&pStmt->sql), sqlite3_errmsg(g.db));







<




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





|
>
>
>






>
>
>
>










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







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
    style_footer();
    cgi_reply();
  }else{
    fprintf(stderr, "%s: %s\n", g.argv[0], z);
  }
  db_force_rollback();
  exit(1);

}

static int nBegin = 0;      /* Nesting depth of BEGIN */
static int doRollback = 0;  /* True to force a rollback */
static int nCommitHook = 0; /* Number of commit hooks */
static struct sCommitHook {
  int (*xHook)(void);  /* Functions to call at db_end_transaction() */
  int sequence;        /* Call functions in sequence order */
} aHook[5];

/*
** This routine is called by the SQLite commit-hook mechanism
** just prior to each omit.  All this routine does is verify
** that nBegin really is zero.  That insures that transactions
** cannot commit by any means other than by calling db_end_transaction()
** below.
**
** This is just a safety and sanity check.
*/
static int db_verify_at_commit(void *notUsed){
  if( nBegin ){
    fossil_panic("illegal commit attempt");
    return 1;
  }
  return 0;
}

/*
** Begin and end a nested transaction
*/
void db_begin_transaction(void){
  if( nBegin==0 ){
    db_multi_exec("BEGIN");
    sqlite3_commit_hook(g.db, db_verify_at_commit, 0);
  }
  nBegin++;
}
void db_end_transaction(int rollbackFlag){
  if( rollbackFlag ) doRollback = 1;
  nBegin--;
  if( nBegin==0 ){
    int i;
    for(i=0; doRollback==0 && i<nCommitHook; i++){
      doRollback |= aHook[i].xHook();
    }
    db_multi_exec(doRollback ? "ROLLBACK" : "COMMIT");
    doRollback = 0;
  }
}
void db_force_rollback(void){
  if( nBegin ){
    sqlite3_exec(g.db, "ROLLBACK", 0, 0, 0);
  }
  nBegin = 0;
}

/*
** Install a commit hook.  Hooks are installed in sequence order.
** It is an error to install the same commit hook more than once.
**
** Each commit hook is called (in order of accending sequence) at
** each commit operation.  If any commit hook returns non-zero,
** the subsequence commit hooks are omitted and the transaction
** rolls back rather than commit.  It is the responsibility of the
** hooks themselves to issue any error messages.
*/
void db_commit_hook(int (*x)(void), int sequence){
  int i;
  assert( nCommitHook < sizeof(aHook)/sizeof(aHook[1]) );
  for(i=0; i<nCommitHook; i++){
    assert( x!=aHook[i].xHook );
    if( aHook[i].sequence>sequence ){
      int s = sequence;
      int (*xS)(void) = x;
      sequence = aHook[i].sequence;
      x = aHook[i].xHook;
      aHook[i].sequence = s;
      aHook[i].xHook = xS;
    }
  }
  aHook[nCommitHook].sequence = sequence;
  aHook[nCommitHook].xHook = x;
  nCommitHook++;
}

/*
** Prepare or reprepare the sqlite3 statement from the raw SQL text.
*/
static void reprepare(Stmt *pStmt){
  sqlite3_stmt *pNew;
  if( sqlite3_prepare(g.db, blob_buffer(&pStmt->sql), -1, &pNew, 0)!=0 ){
    db_err("%s\n%s", blob_str(&pStmt->sql), sqlite3_errmsg(g.db));
162
163
164
165
166
167
168



169
170
171
172
173
174
175
*/
int db_bind_int(Stmt *pStmt, const char *zParamName, int iValue){
  return sqlite3_bind_int(pStmt->pStmt, paramIdx(pStmt, zParamName), iValue);
}
int db_bind_int64(Stmt *pStmt, const char *zParamName, i64 iValue){
  return sqlite3_bind_int64(pStmt->pStmt, paramIdx(pStmt, zParamName), iValue);
}



int db_bind_text(Stmt *pStmt, const char *zParamName, const char *zValue){
  return sqlite3_bind_text(pStmt->pStmt, paramIdx(pStmt, zParamName), zValue,
                           -1, SQLITE_STATIC);
}
int db_bind_null(Stmt *pStmt, const char *zParamName){
  return sqlite3_bind_null(pStmt->pStmt, paramIdx(pStmt, zParamName));
}







>
>
>







221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
*/
int db_bind_int(Stmt *pStmt, const char *zParamName, int iValue){
  return sqlite3_bind_int(pStmt->pStmt, paramIdx(pStmt, zParamName), iValue);
}
int db_bind_int64(Stmt *pStmt, const char *zParamName, i64 iValue){
  return sqlite3_bind_int64(pStmt->pStmt, paramIdx(pStmt, zParamName), iValue);
}
int db_bind_double(Stmt *pStmt, const char *zParamName, double rValue){
  return sqlite3_bind_double(pStmt->pStmt, paramIdx(pStmt, zParamName), rValue);
}
int db_bind_text(Stmt *pStmt, const char *zParamName, const char *zValue){
  return sqlite3_bind_text(pStmt->pStmt, paramIdx(pStmt, zParamName), zValue,
                           -1, SQLITE_STATIC);
}
int db_bind_null(Stmt *pStmt, const char *zParamName){
  return sqlite3_bind_null(pStmt->pStmt, paramIdx(pStmt, zParamName));
}
254
255
256
257
258
259
260






261
262
263
264
265
266
267
  return sqlite3_column_int64(pStmt->pStmt, N);
}
double db_column_double(Stmt *pStmt, int N){
  return sqlite3_column_double(pStmt->pStmt, N);
}
const char *db_column_text(Stmt *pStmt, int N){
  return (char*)sqlite3_column_text(pStmt->pStmt, N);






}
char *db_column_malloc(Stmt *pStmt, int N){
  return mprintf("%s", db_column_text(pStmt, N));
}
void db_column_blob(Stmt *pStmt, int N, Blob *pBlob){
  blob_append(pBlob, sqlite3_column_blob(pStmt->pStmt, N),
              sqlite3_column_bytes(pStmt->pStmt, N));







>
>
>
>
>
>







316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
  return sqlite3_column_int64(pStmt->pStmt, N);
}
double db_column_double(Stmt *pStmt, int N){
  return sqlite3_column_double(pStmt->pStmt, N);
}
const char *db_column_text(Stmt *pStmt, int N){
  return (char*)sqlite3_column_text(pStmt->pStmt, N);
}
const char *db_column_name(Stmt *pStmt, int N){
  return (char*)sqlite3_column_name(pStmt->pStmt, N);
}
int db_column_count(Stmt *pStmt){
  return sqlite3_column_count(pStmt->pStmt);
}
char *db_column_malloc(Stmt *pStmt, int N){
  return mprintf("%s", db_column_text(pStmt, N));
}
void db_column_blob(Stmt *pStmt, int N, Blob *pBlob){
  blob_append(pBlob, sqlite3_column_blob(pStmt->pStmt, N),
              sqlite3_column_bytes(pStmt->pStmt, N));
483
484
485
486
487
488
489
490











491
492
493




494

495
496
497
498
499
500
501

/*
** Open the user database in "~/.fossil".  Create the database anew if
** it does not already exist.
*/
void db_open_config(void){
  char *zDbName;
  const char *zHome = getenv("HOME");











  if( zHome==0 ){
    db_err("cannot local home directory");
  }




  zDbName = mprintf("%s/.fossil", zHome);

  if( g.configOpen ) return;
  if( file_size(zDbName)<1024*3 ){
    db_init_database(zDbName, zConfigSchema, (char*)0);
  }
  db_open_or_attach(zDbName, "configdb");
  g.configOpen = 1;
}







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



>
>
>
>

>







551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585

/*
** Open the user database in "~/.fossil".  Create the database anew if
** it does not already exist.
*/
void db_open_config(void){
  char *zDbName;
  const char *zHome;
#ifdef __MINGW32__
  zHome = getenv("LOCALAPPDATA");
  if( zHome==0 ){
    zHome = getenv("APPDATA");
    if( zHome==0 ){
      zHome = getenv("HOMEPATH");
    }
  }
#else
  zHome = getenv("HOME");
#endif
  if( zHome==0 ){
    db_err("cannot local home directory");
  }
#ifdef __MINGW32__
  /* . filenames give some window systems problems and many apps problems */
  zDbName = mprintf("%s/_fossil", zHome);
#else
  zDbName = mprintf("%s/.fossil", zHome);
#endif
  if( g.configOpen ) return;
  if( file_size(zDbName)<1024*3 ){
    db_init_database(zDbName, zConfigSchema, (char*)0);
  }
  db_open_or_attach(zDbName, "configdb");
  g.configOpen = 1;
}
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
}

/*
** Locate the root directory of the local repository tree.  The root
** directory is found by searching for a file named "FOSSIL" that contains
** a valid repository database.
**
** If no valid FOSSIL file is found, we move up one level and try again.  
** Once the file is found, the g.zLocalRoot variable is set to the root of
** the repository tree and this routine returns 1.  If no database is
** found, then this routine return 0.
**
** This routine always opens the user database regardless of whether or
** not the repository database is found.  If the FOSSIL file is found,
** it is attached to the open database connection too.
*/
int db_open_local(void){
  int n;
  char zPwd[2000];

  if( g.localOpen) return 1;
  if( getcwd(zPwd, sizeof(zPwd)-20)==0 ){
    db_err("pwd too big: max %d", sizeof(zPwd)-20);
  }
  n = strlen(zPwd);



  while( n>0 ){
    if( access(zPwd, W_OK) ) break;
    strcpy(&zPwd[n], "/_FOSSIL_");
    if( isValidLocalDb(zPwd) ){
      /* Found a valid _FOSSIL_ file */
      zPwd[n] = 0;
      g.zLocalRoot = mprintf("%s/", zPwd);







|











>





>
>
>







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
}

/*
** Locate the root directory of the local repository tree.  The root
** directory is found by searching for a file named "FOSSIL" that contains
** a valid repository database.
**
** If no valid FOSSIL file is found, we move up one level and try again.
** Once the file is found, the g.zLocalRoot variable is set to the root of
** the repository tree and this routine returns 1.  If no database is
** found, then this routine return 0.
**
** This routine always opens the user database regardless of whether or
** not the repository database is found.  If the FOSSIL file is found,
** it is attached to the open database connection too.
*/
int db_open_local(void){
  int n;
  char zPwd[2000];
  char *zPwdConv;
  if( g.localOpen) return 1;
  if( getcwd(zPwd, sizeof(zPwd)-20)==0 ){
    db_err("pwd too big: max %d", sizeof(zPwd)-20);
  }
  n = strlen(zPwd);
  zPwdConv = mprintf("%/", zPwd);
  strncpy(zPwd, zPwdConv, 2000-20);
  free(zPwdConv);
  while( n>0 ){
    if( access(zPwd, W_OK) ) break;
    strcpy(&zPwd[n], "/_FOSSIL_");
    if( isValidLocalDb(zPwd) ){
      /* Found a valid _FOSSIL_ file */
      zPwd[n] = 0;
      g.zLocalRoot = mprintf("%s/", zPwd);
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

/*
** Fill an empty repository database with the basic information for a
** repository. This function is shared between 'create_repository_cmd'
** ('new') and 'reconstruct_cmd' ('reconstruct'), both of which create
** new repositories.
**
** The caller determines wheter the function inserts an empty root
** manifest (zRoot == TRUE), or not (zRoot == FALSE).

*/

void db_initial_setup (int zRoot){
  char *zDate;
  char *zUser;
  Blob hash;
  Blob manifest;

  db_set("content-schema", CONTENT_SCHEMA);
  db_set("aux-schema", AUX_SCHEMA);
  db_set_int("authenticate-localhost", 0);
  db_multi_exec(
    "INSERT INTO config(name,value) VALUES('server-code', hex(randomblob(20)));"

    "INSERT INTO config(name,value) VALUES('project-code',hex(randomblob(20)));"

  );




  zUser = db_global_get("default-user", 0);
  if( zUser==0 ){



    zUser = getenv("USER");

  }
  if( zUser==0 ){
    zUser = "root";
  }
  db_multi_exec(
     "INSERT INTO user(login, pw, cap, info)"
     "VALUES(%Q,'','s','')", zUser
  );
  db_multi_exec(
     "INSERT INTO user(login,pw,cap,info)"
     "   VALUES('anonymous','anonymous','hjkorw','Anon');"
     "INSERT INTO user(login,pw,cap,info)"
     "   VALUES('nobody','','jor','Nobody');"
  );
  user_select();

  if (zRoot){
    blob_zero(&manifest);
    blob_appendf(&manifest, "C initial\\sempty\\sbaseline\n");
    zDate = db_text(0, "SELECT datetime('now')");
    zDate[10]='T';
    blob_appendf(&manifest, "D %s\n", zDate);
    blob_appendf(&manifest, "P\n");
    md5sum_init();







|
|
>

<
|

|



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

>
>
>

>
















|







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
794

/*
** Fill an empty repository database with the basic information for a
** repository. This function is shared between 'create_repository_cmd'
** ('new') and 'reconstruct_cmd' ('reconstruct'), both of which create
** new repositories.
**
** The makeInitialVersion flag determines whether or not an initial
** manifest is created.  The makeServerCodes flag determines whether or
** not server and project codes are invented for this repository.
*/

void db_initial_setup (int makeInitialVersion, int makeServerCodes){
  char *zDate;
  const char *zUser;
  Blob hash;
  Blob manifest;

  db_set("content-schema", CONTENT_SCHEMA, 0);
  db_set("aux-schema", AUX_SCHEMA, 0);
  if( makeServerCodes ){
    db_multi_exec(
      "INSERT INTO config(name,value)"
      " VALUES('server-code', lower(hex(randomblob(20))));"
      "INSERT INTO config(name,value)"
      " VALUES('project-code', lower(hex(randomblob(20))));"
    );
  }
  if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
  if( !db_is_global("safemerge") ) db_set_int("safemerge", 0, 0);
  if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
  zUser = db_get("default-user", 0);
  if( zUser==0 ){
#ifdef __MINGW32__
    zUser = getenv("USERNAME");
#else
    zUser = getenv("USER");
#endif
  }
  if( zUser==0 ){
    zUser = "root";
  }
  db_multi_exec(
     "INSERT INTO user(login, pw, cap, info)"
     "VALUES(%Q,'','s','')", zUser
  );
  db_multi_exec(
     "INSERT INTO user(login,pw,cap,info)"
     "   VALUES('anonymous','anonymous','hjkorw','Anon');"
     "INSERT INTO user(login,pw,cap,info)"
     "   VALUES('nobody','','jor','Nobody');"
  );
  user_select();

  if (makeInitialVersion){
    blob_zero(&manifest);
    blob_appendf(&manifest, "C initial\\sempty\\sbaseline\n");
    zDate = db_text(0, "SELECT datetime('now')");
    zDate[10]='T';
    blob_appendf(&manifest, "D %s\n", zDate);
    blob_appendf(&manifest, "P\n");
    md5sum_init();
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
  if( g.argc!=3 ){
    usage("REPOSITORY-NAME");
  }
  db_create_repository(g.argv[2]);
  db_open_repository(g.argv[2]);
  db_open_config();
  db_begin_transaction();
  db_initial_setup (1);
  db_end_transaction(0);
  printf("project-id: %s\n", db_get("project-code", 0));
  printf("server-id:  %s\n", db_get("server-code", 0));
  printf("admin-user: %s (no password set yet!)\n", g.zLogin);
  printf("baseline:   %s\n", db_text(0, "SELECT uuid FROM blob"));
}








|







813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
  if( g.argc!=3 ){
    usage("REPOSITORY-NAME");
  }
  db_create_repository(g.argv[2]);
  db_open_repository(g.argv[2]);
  db_open_config();
  db_begin_transaction();
  db_initial_setup(1, 1);
  db_end_transaction(0);
  printf("project-id: %s\n", db_get("project-code", 0));
  printf("server-id:  %s\n", db_get("server-code", 0));
  printf("admin-user: %s (no password set yet!)\n", g.zLogin);
  printf("baseline:   %s\n", db_text(0, "SELECT uuid FROM blob"));
}

799
800
801
802
803
804
805
806
807

808
809








810

811












812
813




814



815
816
817


818
819
820

821
822


823

824
825


826















827
828
829
830
831
832
833
834
835
  }
}

/*
** Get and set values from the CONFIG, GLOBAL_CONFIG and VVAR table in the
** repository and local databases.
*/
char *db_get(const char *zName, const char *zDefault){
  return db_text((char*)zDefault, 

                 "SELECT value FROM config WHERE name=%Q", zName);
}








void db_set(const char *zName, const char *zValue){

  db_multi_exec("REPLACE INTO config(name,value) VALUES(%Q,%Q)", zName, zValue);












}
int db_get_int(const char *zName, int dflt){




  return db_int(dflt, "SELECT value FROM config WHERE name=%Q", zName);



}
void db_set_int(const char *zName, int value){
  db_multi_exec("REPLACE INTO config(name,value) VALUES(%Q,%d)", zName, value);


}
char *db_global_get(const char *zName, const char *zDefault){
  return db_text((char*)zDefault, 

                 "SELECT value FROM global_config WHERE name=%Q", zName);
}


void db_global_set(const char *zName, const char *zValue){

  db_multi_exec("REPLACE INTO global_config(name,value)"
                "VALUES(%Q,%Q)", zName, zValue);


}















char *db_lget(const char *zName, const char *zDefault){
  return db_text((char*)zDefault, 
                 "SELECT value FROM vvar WHERE name=%Q", zName);
}
void db_lset(const char *zName, const char *zValue){
  db_multi_exec("REPLACE INTO vvar(name,value) VALUES(%Q,%Q)", zName, zValue);
}
int db_lget_int(const char *zName, int dflt){
  return db_int(dflt, "SELECT value FROM vvar WHERE name=%Q", zName);







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


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







897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943

944
945
946


947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
  }
}

/*
** Get and set values from the CONFIG, GLOBAL_CONFIG and VVAR table in the
** repository and local databases.
*/
char *db_get(const char *zName, char *zDefault){
  char *z = 0;
  if( g.repositoryOpen ){
    z = db_text(0, "SELECT value FROM config WHERE name=%Q", zName);
  }
  if( z==0 && g.configOpen ){
    z = db_text(0, "SELECT value FROM global_config WHERE name=%Q", zName);
  }
  if( z==0 ){
    z = zDefault;
  }
  return z;
}
void db_set(const char *zName, const char *zValue, int globalFlag){
  db_begin_transaction();
  db_multi_exec("REPLACE INTO %sconfig(name,value) VALUES(%Q,%Q)",
                 globalFlag ? "global_" : "", zName, zValue);
  if( globalFlag && g.repositoryOpen ){
    db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
  }
  db_end_transaction(0);
}
int db_is_global(const char *zName){
  if( g.configOpen ){
    return db_exists("SELECT 1 FROM global_config WHERE name=%Q", zName);
  }else{
    return 0;
  }
}
int db_get_int(const char *zName, int dflt){
  int v;
  int rc;
  if( g.repositoryOpen ){
    Stmt q;
    db_prepare(&q, "SELECT value FROM config WHERE name=%Q", zName);
    rc = db_step(&q);
    if( rc==SQLITE_ROW ){
      v = db_column_int(&q, 0);
    }
    db_finalize(&q);

  }else{
    rc = SQLITE_DONE;
  }


  if( rc==SQLITE_DONE && g.configOpen ){
    v = db_int(dflt, "SELECT value FROM global_config WHERE name=%Q", zName);
  }
  return v;
}
void db_set_int(const char *zName, int value, int globalFlag){
  db_begin_transaction();
  db_multi_exec("REPLACE INTO %sconfig(name,value) VALUES(%Q,%d)",
                globalFlag ? "global_" : "", zName, value);
  if( globalFlag && g.repositoryOpen ){
    db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
  }
  db_end_transaction(0);
}
int db_get_boolean(const char *zName, int dflt){
  static const char *azOn[] = { "on", "yes", "true", "1" };
  static const char *azOff[] = { "off", "no", "false", "0" };
  int i;
  char *zVal = db_get(zName, dflt ? "on" : "off");
  for(i=0; i<sizeof(azOn)/sizeof(azOn[0]); i++){
    if( strcmp(zVal,azOn[i])==0 ) return 1;
  }
  for(i=0; i<sizeof(azOff)/sizeof(azOff[0]); i++){
    if( strcmp(zVal,azOff[i])==0 ) return 0;
  }
  return dflt;
}
char *db_lget(const char *zName, char *zDefault){
  return db_text((char*)zDefault,
                 "SELECT value FROM vvar WHERE name=%Q", zName);
}
void db_lset(const char *zName, const char *zValue){
  db_multi_exec("REPLACE INTO vvar(name,value) VALUES(%Q,%Q)", zName, zValue);
}
int db_lget_int(const char *zName, int dflt){
  return db_int(dflt, "SELECT value FROM vvar WHERE name=%Q", zName);
845
846
847
848
849
850
851


852
853
854
855
856
857
858
859
860
861
862



863





864


















865




866
867
868
869
870
871
872
873
874
875
876
877

878

879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894















895
896



897
898
899
900
901
902
903







904

905

906

907


908


909

910
911
912
913
914
915
916
917


918
919
**
** Open a connection to the local repository in FILENAME.  A checkout
** for the repository is created with its root at the working directory.
** See also the "close" command.
*/
void cmd_open(void){
  Blob path;


  if( g.argc!=3 ){
    usage("REPOSITORY-FILENAME");
  }
  if( db_open_local() ){
    fossil_panic("already within an open tree rooted at %s", g.zLocalRoot);
  }
  file_canonical_name(g.argv[2], &path);
  db_open_repository(blob_str(&path));
  db_init_database("./_FOSSIL_", zLocalSchema, (char*)0);
  db_open_local();
  db_lset("repository", blob_str(&path));



  db_lset_int("checkout", 1);





}























/*
** COMMAND: config
**
** Usage: %fossil config NAME=VALUE ...
**
** List or change the global configuration settings.  With no arguments,
** all settings are listed.  Arguments of simply NAME cause that setting
** to be displayed.  Arguments of the form NAME=VALUE change the value of
** a setting.  Arguments of the form NAME= delete a setting.
**
** Recognized settings include:
**

**   editor        Text editor command used for check-in comments.

**
**   clear-sign    Command used to clear-sign manifests at check-in.
**                 The default is "gpg --clearsign -o ".
*/
void cmd_config(void){
  db_open_config();
  if( g.argc>2 ){
    int i;
    db_begin_transaction();
    for(i=2; i<g.argc; i++){
      char *zName, *zValue;
      int j;

      zName = mprintf("%s", g.argv[i]);
      for(j=0; zName[j] && zName[j]!='='; j++){}
      if( zName[j] ){















        zName[j] = 0;
        zValue = &zName[j+1];



        if( zValue[0] ){
          db_global_set(zName, zValue);
        }else{
          db_multi_exec("DELETE FROM global_config WHERE name=%Q", zName);
        }
      }
      zValue = db_global_get(zName, 0);







      if( zValue ){

        printf("%s=%s\n", zName, zValue);

      }else{

        printf("%s is undefined\n", zName);


      }


    }

    db_end_transaction(0);
  }else{
    Stmt q;
    db_prepare(&q, "SELECT name, value FROM global_config ORDER BY name");
    while( db_step(&q)==SQLITE_ROW ){
      printf("%s=%s\n", db_column_text(&q, 0), db_column_text(&q, 1));
    }
    db_finalize(&q);


  }
}







>
>











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

|
<
|

|
<
|
|

<
<
>
|
>

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

>
|
|
<
<
<
|

<
>
>


992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046

1047
1048
1049

1050
1051
1052


1053
1054
1055
1056
1057
1058
1059





1060
1061
1062
1063
1064
1065

1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081

1082
1083
1084
1085

1086




1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109



1110
1111

1112
1113
1114
1115
**
** Open a connection to the local repository in FILENAME.  A checkout
** for the repository is created with its root at the working directory.
** See also the "close" command.
*/
void cmd_open(void){
  Blob path;
  int vid;
  static char *azNewArgv[] = { 0, "update", "--latest", 0 };
  if( g.argc!=3 ){
    usage("REPOSITORY-FILENAME");
  }
  if( db_open_local() ){
    fossil_panic("already within an open tree rooted at %s", g.zLocalRoot);
  }
  file_canonical_name(g.argv[2], &path);
  db_open_repository(blob_str(&path));
  db_init_database("./_FOSSIL_", zLocalSchema, (char*)0);
  db_open_local();
  db_lset("repository", blob_str(&path));
  vid = db_int(0, "SELECT pid FROM plink y"
                  " WHERE NOT EXISTS(SELECT 1 FROM plink x WHERE x.cid=y.pid)");
  if( vid==0 ){
    db_lset_int("checkout", 1);
  }else{
    db_lset_int("checkout", vid);
    g.argv = azNewArgv;
    g.argc = 3;
    update_cmd();
  }
}

/*
** Print the value of a setting named zName
*/
static void print_setting(const char *zName){
  Stmt q;
  db_prepare(&q,
     "SELECT '(local)', value FROM config WHERE name=%Q"
     " UNION ALL "
     "SELECT '(global)', value FROM global_config WHERE name=%Q",
     zName, zName
  );
  if( db_step(&q)==SQLITE_ROW ){
    printf("%-20s %-8s %s\n", zName, db_column_text(&q, 0),
        db_column_text(&q, 1));
  }else{
    printf("%-20s\n", zName);
  }
  db_finalize(&q);
}


/*
** COMMAND: settings

** %fossil setting ?PROPERTY? ?VALUE? ?-global?
**
** With no arguments, list all properties and their values.  With just

** a property name, show the value of that property.  With a value
** argument, change the property for the current repository.
**


**    autosync         If enabled, automatically pull prior to
**                     commit or update and automatically push
**                     after commit or tag or branch creation.
**
**    clearsign        Command used to clear-sign manifests at check-in.
**                     The default is "gpg --clearsign -o ".
**





**    editor           Text editor command used for check-in comments.
**
**    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.

**
**    omitsign         When enabled, fossil will not attempt to sign any
**                     commit with gpg. All commits will be unsigned.
**
**    safemerge        If enabled, when commit will cause a fork, the
**                     commit will not abort with warning. Also update
**                     will not be allowed if local changes exist.
**
**   diff-command      External command to run when performing a diff.
**                     If undefined, the internal text diff will be used.
**
**   gdiff-command     External command to run when performing a graphical
**                     diff. If undefined, text diff will be used.
*/
void setting_cmd(void){
  static const char *azName[] = {

    "autosync",
    "clearsign",
    "editor",
    "localauth",

    "omitsign",




    "safemerge",
    "diff-command",
    "gdiff-command",
  };
  int i;
  int globalFlag = find_option("global","g",0)!=0;
  db_find_and_open_repository();
  if( g.argc==2 ){
    for(i=0; i<sizeof(azName)/sizeof(azName[0]); i++){
      print_setting(azName[i]);
    }
  }else if( g.argc==3 || g.argc==4 ){
    const char *zName = g.argv[2];
    int n = strlen(zName);
    for(i=0; i<sizeof(azName)/sizeof(azName[0]); i++){
      if( strncmp(azName[i], zName, n)==0 ) break;
    }
    if( i>=sizeof(azName)/sizeof(azName[0]) ){
      fossil_fatal("no such setting: %s", zName);
    }
    if( g.argc==4 ){
      db_set(azName[i], g.argv[3], globalFlag);
    }else{



      print_setting(azName[i]);
    }

  }else{
    usage("?PROPERTY? ?VALUE?");
  }
}
Changes to src/delta.c.
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346

  /* Begin scanning the target file and generating copy commands and
  ** literal sections of the delta.
  */
  base = 0;    /* We have already generated everything before zOut[base] */
  while( base+NHASH<lenOut ){
    int iSrc, iBlock;
    unsigned int bestCnt, bestOfst, bestLitsz;
    hash_init(&h, &zOut[base]);
    i = 0;     /* Trying to match a landmark against zOut[base+i] */
    bestCnt = 0;
    while( 1 ){
      int hv;
      int limit = 250;








|







332
333
334
335
336
337
338
339
340
341
342
343
344
345
346

  /* Begin scanning the target file and generating copy commands and
  ** literal sections of the delta.
  */
  base = 0;    /* We have already generated everything before zOut[base] */
  while( base+NHASH<lenOut ){
    int iSrc, iBlock;
    unsigned int bestCnt, bestOfst=0, bestLitsz=0;
    hash_init(&h, &zOut[base]);
    i = 0;     /* Trying to match a landmark against zOut[base+i] */
    bestCnt = 0;
    while( 1 ){
      int hv;
      int limit = 250;

Changes to src/descendents.c.
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
    base = db_lget_int("checkout", 0);
  }else{
    base = name_to_rid(g.argv[2]);
  }
  if( base==0 ) return;
  compute_leaves(base);
  db_prepare(&q,
    "SELECT blob.rid, uuid, datetime(event.mtime,'localtime'), comment, 0,"
    "       (SELECT count(*) FROM plink WHERE cid=blob.rid)"
    "  FROM leaves, blob, event"
    " WHERE blob.rid=leaves.rid"
    "   AND event.objid=leaves.rid"
    " ORDER BY event.mtime DESC"

  );
  print_timeline(&q, 20);
  db_finalize(&q);
}

/*
** COMMAND:  leaves
**
** Usage: %fossil leaves
** Find leaves of all branches.
*/
void branches_cmd(void){
  Stmt q;

  db_must_be_within_tree();
  db_prepare(&q,
    "SELECT blob.rid, blob.uuid, datetime(event.mtime,'localtime'),"
    "       event.comment, 0,"
    "       (SELECT count(*) FROM plink WHERE cid=blob.rid)"
    "  FROM blob, event"
    " WHERE blob.rid IN"
    "       (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
    "   AND event.objid=blob.rid"
    " ORDER BY event.mtime DESC"

  );
  print_timeline(&q, 2000);
  db_finalize(&q);
}

/*
** WEBPAGE:  leaves
**
** Find leaves of all branches.
*/
void leaves_page(void){
  Stmt q;

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

  style_header("Leaves");
  db_prepare(&q,
    "SELECT blob.rid, blob.uuid, datetime(event.mtime,'localtime'),"
    "       event.comment, event.user, 1, 1, 0"
    "  FROM blob, event"
    " WHERE blob.rid IN"
    "       (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
    "   AND event.objid=blob.rid"
    " ORDER BY event.mtime DESC"

  );
  www_print_timeline(&q, 0, 0, 0, 0);
  db_finalize(&q);
  @ <script>
  @ function xin(id){
  @ }
  @ function xout(id){
  @ }
  @ </script>
  style_footer();
}







|
<
<
<
|
|
>
















|
<
<
<
|

<
|
>


















|
<
<
|

<
|
>











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
    base = db_lget_int("checkout", 0);
  }else{
    base = name_to_rid(g.argv[2]);
  }
  if( base==0 ) return;
  compute_leaves(base);
  db_prepare(&q,
    "%s"



    "   AND event.objid IN (SELECT rid FROM leaves)"
    " ORDER BY event.mtime DESC",
    timeline_query_for_tty()
  );
  print_timeline(&q, 20);
  db_finalize(&q);
}

/*
** COMMAND:  leaves
**
** Usage: %fossil leaves
** Find leaves of all branches.
*/
void branches_cmd(void){
  Stmt q;

  db_must_be_within_tree();
  db_prepare(&q,
    "%s"



    "   AND blob.rid IN"
    "       (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"

    " ORDER BY event.mtime DESC",
    timeline_query_for_tty()
  );
  print_timeline(&q, 2000);
  db_finalize(&q);
}

/*
** WEBPAGE:  leaves
**
** Find leaves of all branches.
*/
void leaves_page(void){
  Stmt q;

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

  style_header("Leaves");
  db_prepare(&q,
    "%s"


    "   AND blob.rid IN"
    "       (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"

    " ORDER BY event.mtime DESC",
    timeline_query_for_www()
  );
  www_print_timeline(&q, 0, 0, 0, 0);
  db_finalize(&q);
  @ <script>
  @ function xin(id){
  @ }
  @ function xout(id){
  @ }
  @ </script>
  style_footer();
}
Changes to src/diff.c.
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
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to implement "diff" operators.

*/
#include "config.h"
#include "diff.h"
#include <assert.h>








/*
** Information about each line of a file being diffed.




*/
typedef struct DLine DLine;
struct DLine {
  const char *z;    /* The text of the line */
  unsigned int h;   /* Hash of the line */
};



/*
** Break a blob into lines by converting each \n into a \000 and

** creating pointers to the beginning of each line.






*/
static DLine *break_into_lines(char *z, int *pnLine){
  int nLine, i, j;
  unsigned int h;
  DLine *a;




  for(i=0, nLine=1; z[i]; i++){





    if( z[i]=='\n' ) nLine++;








  }
  a = malloc( nLine*sizeof(a[0]) );
  if( a==0 ) fossil_panic("out of memory");
  a[0].z = z;

  for(i=0, j=0, h=0; z[i]; i++){
    if( z[i]=='\n' ){
      a[j].h = h;
      j++;
      a[j].z = &z[i+1];
      z[i] = 0;
      h = 0;
    }else{
      h = h ^ (h<<2) ^ z[i];
    }


  }
  a[j].h = h;

  *pnLine = j+1;
  return a;
}

/*
** Return true if two DLine elements are identical.
*/
static int same_dline(DLine *pA, DLine *pB){
  return pA->h==pB->h && strcmp(pA->z,pB->z)==0;
}

/*







** Generate a unified diff of two blobs.  The text of the original
** two blobs is destroyed by the diffing process.



























































































*/




void unified_diff(Blob *pA, Blob *pB, int nContext, Blob *pOut){

  DLine *pDA, *pDB, *A, *B;
  int nA, nB, nAp1;
  int x, y;

  int cnt;
  int i, iStart;


  int *m;




  /* Break the two files being diffed into individual lines.
  ** Compute hashes on each line for fast comparison.
  */
  pDA = break_into_lines(blob_str(pA), &nA);
  pDB = break_into_lines(blob_str(pB), &nB);

  /* Remove common prefix and suffix to help reduce the value
  ** of N in the O(N^2) minimum edit distance algorithm.
  */

  for(i=0; i<nA && i<nB && same_dline(&pDA[i],&pDB[i]); i++){}
  i -= nContext;

  if( i<0 ) i = 0;

  iStart = i;
  A = &pDA[iStart];
  B = &pDB[iStart];
  nA -= iStart;


  nB -= iStart;









  for(i=1; i<nA && i<nB && same_dline(&A[nA-i],&B[nB-i]); i++){}
  i -= nContext;
  if( i<1 ) i = 1;
  i--;
  nA -= i;
  nB -= i;





  
  /* Create the matrix used for the minimum edit distance
  ** calculation.
  */









  nAp1 = nA + 1;





  m = malloc( sizeof(m[0])*(nB+1)*nAp1 );








# define M(X,Y) m[(Y)*nAp1+(X)]


































  /* Find the minimum edit distance using Wagner's algorithm.
  */

  for(x=0; x<=nA; x++){



    M(x,0) = x;


  }
  for(y=0; y<=nB; y++){
    M(0,y) = y;



  }
  for(x=1; x<=nA; x++){
    for(y=1; y<=nB; y++){
      int e = M(x-1,y) + 1;
      if( e>M(x,y-1)+1 ){
        e = M(x,y-1)+1;

      }
      if( e<=M(x-1,y-1) ){
        M(x,y) = e;
      }else if( same_dline(&A[x-1], &B[y-1]) ){
        M(x,y) = M(x-1,y-1);

      }else{
        M(x,y) = e;
      }





    }
  }






  /* Walk backwards through the Wagner algorithm matrix to determine





  ** the specific edits that give the minimum edit distance.  Mark our

  ** path through the matrix with -1.
  */
  x = nA;
  y = nB;
  while( x>0 || y>0 ){
    int v = M(x,y);
    M(x,y) = -1;
    if( x==0 ){
      y--;
    }else if( y==0 ){
      x--;
    }else if( M(x,y-1)+1==v ){
      y--;
    }else if( M(x-1,y)+1==v ){
      x--;
    }else{
      x--;
      y--;



    }





  }










#if 0
for(y=0; y<=nB; y++){










  for(x=0; x<=nA; x++){
    printf(" %2d", M(x,y));


  }

  printf("\n");






}
#endif




  x = y = 0;
  cnt = nContext;
  while( x<nA || y<nB ){
    int t1, t2;
    if( (t1 = M(x+1,y))<0 || (t2 = M(x,y+1))<0 ){
      if( cnt>=nContext ){
        blob_appendf(pOut, "@@ -%d +%d @@\n", 
            x-nContext+iStart+2, y-nContext+iStart+2);



        for(i=x-nContext+1; i<x; i++){



          if( i<0 ) continue;


          blob_appendf(pOut, " %s\n", A[i].z);
        }


      }









    }
    if( t1<0 ){
      blob_appendf(pOut, "-%s\n", A[x].z);
      x++;
      cnt = 0;

    }else if( t2<0 ){

      blob_appendf(pOut, "+%s\n", B[y].z);
      y++;
      cnt = 0;
    }else{
      if( M(x+1,y+1)==(-1) && cnt<nContext ){
        blob_appendf(pOut, " %s\n", A[x].z);
      }

      cnt++;
      x++;
      y++;
    }
  }

















  /* Cleanup allocationed memory */
  free(m);
  free(pDA);
  free(pDB);

}

/*
** COMMAND: test-diff
*/
void test_diff_cmd(void){
  Blob a, b, out;
  if( g.argc!=4 ) usage("FILE1 FILE2");
  blob_read_from_file(&a, g.argv[2]);
  blob_read_from_file(&b, g.argv[3]);
  blob_zero(&out);
  unified_diff(&a, &b, 4, &out);
  blob_reset(&a);
  blob_reset(&b);
  printf("%s", blob_str(&out));
  blob_reset(&out);
}
/*
** COMMAND: test-uuid-diff
*/
void test_uuiddiff_cmd(void){
  Blob a, b, out;
  int ridA, ridB;
  if( g.argc!=4 ) usage("UUID2 UUID1");
  db_must_be_within_tree();
  ridA = name_to_rid(g.argv[2]);
  content_get(ridA, &a);
  ridB = name_to_rid(g.argv[3]);
  content_get(ridB, &b);
  blob_zero(&out);
  unified_diff(&a, &b, 4, &out);
  blob_reset(&a);
  blob_reset(&b);
  printf("%s", blob_str(&out));
  blob_reset(&out);
}







|
>





>
>
>
>
>
>
>


>
>
>
>







>
>

<
>
|
>
>
>
>
>
>


|


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



|
>
|
<
|
|
|
<
|
<
|

>
>

|
>
|







|



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

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




|
|

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

|

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

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

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

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



|

|





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

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
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
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to compute a "diff" between two
** text files.
*/
#include "config.h"
#include "diff.h"
#include <assert.h>


#if 0
#define DEBUG(X) X
#else
#define DEBUG(X)
#endif

/*
** Information about each line of a file being diffed.
**
** The lower 20 bits of the hash are the length of the
** line.  If any line is longer than 1048575 characters,
** the file is considered binary.
*/
typedef struct DLine DLine;
struct DLine {
  const char *z;    /* The text of the line */
  unsigned int h;   /* Hash of the line */
};

#define LENGTH_MASK  0x000fffff

/*

** Return an array of DLine objects containing a pointer to the
** start of each line and a hash of that line.  The lower 
** bits of the hash store the length of each line.
**
** Trailing whitespace is removed from each line.
**
** Return 0 if the file is binary or contains a line that is
** longer than 1048575 bytes.
*/
static DLine *break_into_lines(char *z, int *pnLine){
  int nLine, i, j, k, x;
  unsigned int h;
  DLine *a;

  /* Count the number of lines.  Allocate space to hold
  ** the returned array.
  */
  for(i=j=0, nLine=1; z[i]; i++, j++){
    int c = z[i];
    if( c==0 ){
      return 0;
    }
    if( c=='\n' && z[i+1]!=0 ){
      nLine++;
      if( j>1048575 ){
        return 0;
      }
      j = 0;
    }
  }
  if( j>1048575 ){
    return 0;
  }
  a = malloc( nLine*sizeof(a[0]) );
  if( a==0 ) fossil_panic("out of memory");

  /* Fill in the array */
  for(i=0; i<nLine; i++){

    a[i].z = z;
    for(j=0; z[j] && z[j]!='\n'; j++){}
    for(k=j; k>0 && isspace(z[k-1]); k--){}

    for(h=0, x=0; x<k; x++){

      h = h ^ (h<<2) ^ z[x];
    }
    a[i].h = (h<<20) | k;;
    z += j+1;
  }

  /* Return results */
  *pnLine = nLine;
  return a;
}

/*
** Return true if two DLine elements are identical.
*/
static int same_dline(DLine *pA, DLine *pB){
  return pA->h==pB->h && memcmp(pA->z,pB->z,pA->h & LENGTH_MASK)==0;
}

/*
** Append a single line of "diff" output to pOut.
*/
static void appendDiffLine(Blob *pOut, char *zPrefix, DLine *pLine){
  blob_append(pOut, zPrefix, 1);
  blob_append(pOut, pLine->z, pLine->h & LENGTH_MASK);
  blob_append(pOut, "\n", 1);
}


/*
** Generate a report of the differences between files pA and pB.
** If pOut is not NULL then a unified diff is appended there.  It
** is assumed that pOut has already been initialized.  If pOut is
** NULL, then a pointer to an array of integers is returned.  
** The integers come in triples.  For each triple,
** the elements are the number of lines copied, the number of
** lines deleted, and the number of lines inserted.  The vector
** is terminated by a triple of all zeros.
**
** This diff utility does not work on binary files.  If a binary
** file is encountered, 0 is returned and pOut is written with
** text "cannot compute difference between binary files".
**
****************************************************************************
**
** The core algorithm is a variation on the classic Wagner
** minimum edit distance with enhancements to reduce the runtime
** to be almost linear in the common case where the two files
** have a lot in common.  For additional information see
** Eugene W. Myers, "An O(ND) Difference Algorithm And Its
** Variations"
**
** Consider comparing strings A and B.  A=abcabba and B=cbabac
** We construct a "Wagner" matrix W with A along the X axis and 
** B along the Y axis:
**
**     c 6               *
**     a 5               *
**     b 4           * *
**     a 3         *
**     b 2       *
**   B c 1       *
**       0 * * * 
**         0 1 2 3 4 5 6 7
**           a b c a b b a
**           A
**
** (Note: we draw this Wagner matrix with the origin at the lower 
** left whereas Myers uses the origin at the upper left.  Otherwise,
** they are the same.)
**
** Let Y be the maximum y value or the number of characters in B.
** 6 in this example.  X is the maximum x value or the number of
** elements in A.  Here 7.
**
** Our goal is to find a path from (0,0) to (X,Y).  The path can
** use horizontal, vertical, or diagonal steps.  A diagonal from
** (x-1,y-1) to (x,y) is only allowed if A[x]==B[y].  A vertical
** steps corresponds to an insertion.  A horizontal step corresponds
** to a deletion.  We want to find the path with the fewest
** horizontal and vertical steps.
**
** Diagonal k consists of all points such that x-y==k.  Diagonal
** zero begins at the origin.  Diagonal 1 begins at (1,0).  
** Diagonal -1 begins at (0,1).  All diagonals move up and to the
** right at 45 degrees.  Diagonal number increase from upper left
** to lower right.
** 
** Myers matrix M is a lower right triangular matrix with indices d
** along the bottom and i vertically:
**
** 
**   i=4 |            +4  \
**     3 |         +3 +2   |
**     2 |      +2 +1  0   |- k values.   k = 2*i-d
**     1 |   +1  0 -1 -2   |
**     0 | 0 -1 -2 -3 -4  /
**       ---------------
**         0  1  2  3  4 = d
**
** Each element of the Myers matrix corresponds to a diagonal.
** The diagonal is k=2*i-d.  The diagonal values are shown
** in the template above.
**
** Each entry in M represents the end-point on a path from (0,0).
** The end-point is on diagonal k.  The value stored in M is
** q=x+y where (x,y) is the terminus of the path.  A path
** to M[d][i] will come through either M[d-1][i-1] or
** though M[d-1][i], whichever holds the largest value of q.
** If both elements hold the same value, the path comes
** through M[d-1][i-1].
**
** The value of d is the number of insertions and deletions
** made so far on the path.  M grows progressively.  So the
** size of the M matrix is proportional to d*d.  For the
** common case where A and B are similar, d will be small
** compared to X and Y so little memory is required.  The
** original Wagner algorithm requires X*Y memory, which for
** larger files (100K lines) is more memory than we have at
** hand.
*/
int *text_diff(
  Blob *pA_Blob,   /* FROM file */
  Blob *pB_Blob,   /* TO file */
  Blob *pOut,      /* Write unified diff here if not NULL */
  int nContext     /* Amount of context to unified diff */
){
  DLine *A, *B;    /* Files being compared */
  int X, Y;        /* Number of elements in A and B */
  int x, y;        /* Indices:  A[x] and B[y] */
  int szM = 0;     /* Number of rows and columns in M */
  int **M = 0;     /* Myers matrix */
  int i, d;        /* Indices on M.  M[d][i] */
  int k, q;        /* Diagonal number and distinct from (0,0) */
  int K, D;        /* The diagonal and d for the final solution */          
  int *R = 0;      /* Result vector */
  int r;           /* Loop variables */
  int go = 1;      /* Outer loop control */
  int MAX;         /* Largest of X and Y */

  /* Break the two files being diffed into individual lines.
  ** Compute hashes on each line for fast comparison.
  */
  A = break_into_lines(blob_str(pA_Blob), &X);
  B = break_into_lines(blob_str(pB_Blob), &Y);

  if( A==0 || B==0 ){
    free(A);

    free(B);
    if( pOut ){
      blob_appendf(pOut, "cannot compute difference between binary files\n");
    }
    return 0;
  }

  szM = 0;
  MAX = X>Y ? X : Y;
  if( MAX>2000 ) MAX = 2000;
  for(d=0; go && d<=MAX; d++){
    if( szM<d+1 ){
      szM += szM + 10;
      M = realloc(M, sizeof(M[0])*szM);
      if( M==0 ){
        fossil_panic("out of memory");
      }
    }
    M[d] = malloc( sizeof(M[d][0])*(d+1) );
    if( M[d]==0 ){
      fossil_panic("out of memory");
    }
    for(i=0; i<=d; i++){
      k = 2*i - d;
      if( d==0 ){

        q = 0;
      }else if( i==0 ){
        q = M[d-1][0];
      }else if( i<d-1 && M[d-1][i-1] < M[d-1][i] ){
        q = M[d-1][i];
      }else{
        q = M[d-1][i-1];
      }


      x = (k + q + 1)/2;
      y = x - k;
      if( x<0 || x>X || y<0 || y>Y ){
        x = y = 0;
      }else{
        while( x<X && y<Y && same_dline(&A[x],&B[y]) ){ x++; y++; }
      }
      M[d][i] = x + y;
      DEBUG( printf("M[%d][%i] = %d  k=%d (%d,%d)\n", d, i, x+y, k, x, y); )
      if( x==X && y==Y ){
        go = 0;
        break;
      }
    }
  }
  if( d>MAX ){
    R = malloc( sizeof(R[0])*7 );
    R[0] = 0;
    R[1] = X;
    R[2] = Y;
    R[3] = 0;
    R[4] = 0;
    R[5] = 0;
    R[6] = 0;
  }else{
    /* Reuse M[] as follows:
    **
    **     M[d][1] = 1 if a line is inserted or 0 if a line is deleted.
    **     M[d][0] = number of lines copied after the ins or del above.
    **
    */
    D = d - 1;
    K = X - Y;
    for(d=D, i=(K+D)/2; d>0; d--){
      DEBUG( printf("d=%d i=%d\n", d, i); )
      if( i==d || (i>0 && M[d-1][i-1] > M[d-1][i]) ){
        M[d][0] = M[d][i] - M[d-1][i-1] - 1;
        M[d][1] = 0;
        i--;
      }else{
        M[d][0] = M[d][i] - M[d-1][i] - 1;
        M[d][1] = 1;
      }
    }
    
    DEBUG(
      printf("---------------\nM[0][0] = %5d\n", M[0][0]);
      for(d=1; d<=D; d++){
        printf("M[%d][0] = %5d    M[%d][1] = %d\n",d,M[d][0],d,M[d][1]);
      }
    )
    
    /* Allocate the output vector
    */
    R = malloc( sizeof(R[0])*(D+2)*3 );
    if( R==0 ){
      fossil_fatal("out of memory");
    }
    
    /* Populate the output vector

    */
    d = r = 0;
    while( d<=D ){
      int n;
      R[r++] = M[d++][0]/2;   /* COPY */
      if( d>D ){
        R[r++] = 0;
        R[r++] = 0;
        break;
      }
      if( M[d][1]==0 ){
        n = 1;
        while( M[d][0]==0 && d<D && M[d+1][1]==0 ){
          d++;
          n++;
        }


        R[r++] = n;           /* DELETE */
        if( d==D || M[d][0]>0 ){
          R[r++] = 0;         /* INSERT */
          continue;
        }




        d++;
      }else{
        R[r++] = 0;           /* DELETE */
      }
      assert( M[d][1]==1 );
      n = 1;
      while( M[d][0]==0 && d<D && M[d+1][1]==1 ){
        d++;
        n++;
      }

      R[r++] = n;            /* INSERT */
    }
    R[r++] = 0;
    R[r++] = 0;
    R[r++] = 0;
  }
    
  /* Free the Myers matrix */
  for(d=0; d<=D; d++){
    free(M[d]);
  }
  free(M);

  /* If pOut is defined, construct a unified diff into pOut and
  ** delete R
  */





  if( pOut ){

    int a = 0;    /* Index of next line in A[] */




    int b = 0;    /* Index of next line in B[] */
    int nr;       /* Number of COPY/DELETE/INSERT triples to process */
    int mxr;      /* Maximum value for r */
    int na, nb;   /* Number of lines shown from A and B */
    int i, j;     /* Loop counters */
    int m;        /* Number of lines to output */
    int skip;     /* Number of lines to skip */

    for(mxr=0; R[mxr+1] || R[mxr+2] || R[mxr+3]; mxr += 3){}
    for(r=0; r<mxr; r += 3*nr){
      /* Figure out how many triples to show in a single block */
      for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
      DEBUG( printf("r=%d nr=%d\n", r, nr); )

      /* For the current block comprising nr triples, figure out
      ** how many lines of A and B are to be displayed
      */
      if( R[r]>nContext ){
        na = nb = nContext;
        skip = R[r] - nContext;
      }else{
        na = nb = R[r];
        skip = 0;
      }

      for(i=0; i<nr; i++){
        na += R[r+i*3+1];
        nb += R[r+i*3+2];
      }
      if( R[r+nr*3]>nContext ){
        na += nContext;
        nb += nContext;
      }else{
        na += R[r+nr*3];
        nb += R[r+nr*3];
      }
      for(i=1; i<nr; i++){

        na += R[r+i*3];
        nb += R[r+i*3];
      }
      blob_appendf(pOut,"@@ -%d,%d +%d,%d @@\n", a+skip+1, na, b+skip+1, nb);

      /* Show the initial common area */
      a += skip;
      b += skip;
      m = R[r] - skip;
      for(j=0; j<m; j++){
        appendDiffLine(pOut, " ", &A[a+j]);
      }

      a += m;
      b += m;

      /* Show the differences */
      for(i=0; i<nr; i++){
        m = R[r+i*3+1];



        for(j=0; j<m; j++){
          appendDiffLine(pOut, "-", &A[a+j]);

        }
        a += m;
        m = R[r+i*3+2];
        for(j=0; j<m; j++){
          appendDiffLine(pOut, "+", &B[b+j]);
        }
        b += m;
        if( i<nr-1 ){
          m = R[r+i*3+3];
          for(j=0; j<m; j++){
            appendDiffLine(pOut, " ", &B[b+j]);
          }
          b += m;
          a += m;
        }
      }

      /* Show the final common area */
      assert( nr==i );
      m = R[r+nr*3];
      if( m>nContext ) m = nContext;
      for(j=0; j<m; j++){
        appendDiffLine(pOut, " ", &B[b+j]);
      }
    }
    free(R);


    R = 0;
  }

  /* We no longer need the A[] and B[] vectors */
  free(A);


  free(B);



  /* Return the result */
  return R;


}

/*
** COMMAND: test-rawdiff
*/
void test_rawdiff_cmd(void){
  Blob a, b;
  int r;
  int i;
  int *R;
  if( g.argc<4 ) usage("FILE1 FILE2 ...");
  blob_read_from_file(&a, g.argv[2]);
  for(i=3; i<g.argc; i++){
    if( i>3 ) printf("-------------------------------\n");
    blob_read_from_file(&b, g.argv[i]);
    R = text_diff(&a, &b, 0, 0);
    for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
      printf(" copy %4d  delete %4d  insert %4d\n", R[r], R[r+1], R[r+2]);
    }

    /* free(R); */
    blob_reset(&b);

  }
}

/*
** COMMAND: test-udiff
*/
void test_udiff_cmd(void){
  Blob a, b, out;
  if( g.argc!=4 ) usage("FILE1 FILE2");
  blob_read_from_file(&a, g.argv[2]);
  blob_read_from_file(&b, g.argv[3]);
  blob_zero(&out);
  text_diff(&a, &b, &out, 3);
  blob_write_to_file(&out, "-");






















}
Changes to src/diffcmd.c.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
** Copyright (c) 2007 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











|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
** Copyright (c) 2007 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
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
  blob_appendf(pBlob, "\"%s\"", zIn);
  z = blob_buffer(pBlob);
  for(i=n+1; i<=n+k; i++){
    if( z[i]=='"' ) z[i] = '_';
  }
}



/*
** COMMAND: diff
** COMMAND: tkdiff
**
** Usage: %fossil diff|tkdiff FILE...

** Show the difference between the current version of a file (as it
** exists on disk) and that same file as it was checked out.  Use




** either "diff -u" or "tkdiff".














*/
void diff_cmd(void){
  const char *zFile;
  Blob cmd;
  Blob fname;


  int i;
  char *zV1 = 0;
  char *zV2 = 0;



  if( g.argc<3 ){
    usage("?OPTIONS? FILE");
  }
  db_must_be_within_tree();
  blob_zero(&cmd);
  blob_appendf(&cmd, "%s ", g.argv[1]);
  for(i=2; i<g.argc-1; i++){
    const char *z = g.argv[i];
    if( (strcmp(z,"-v")==0 || strcmp(z,"--version")==0) && i<g.argc-2 ){
      if( zV1==0 ){
        zV1 = g.argv[i+1];
      }else if( zV2==0 ){
        zV2 = g.argv[i+1];
      }else{
        fossil_panic("too many versions");

      }

    }else{
      blob_appendf(&cmd, "%s ", z);
    }


  }
  zFile = g.argv[g.argc-1];
  if( !file_tree_name(zFile, &fname) ){
    fossil_panic("unknown file: %s", zFile);
  }
  if( zV1==0 ){
    int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B", &fname);
    Blob record;
    Blob vname;
    int cnt = 0;




    if( rid==0 ){
      fossil_panic("no history for file: %b", &fname);
    }







    blob_zero(&vname);

    do{


      blob_reset(&vname);
      blob_appendf(&vname, "%s~%d", zFile, cnt++);
    }while( access(blob_str(&vname),0)==0 );
    content_get(rid, &record);

    blob_write_to_file(&record, blob_str(&vname));
    blob_reset(&record);
    shell_escape(&cmd, blob_str(&vname));
    blob_appendf(&cmd, " ");
    shell_escape(&cmd, zFile);
    system(blob_str(&cmd));
    unlink(blob_str(&vname));
    blob_reset(&vname);
    blob_reset(&cmd);
  }else{
    fossil_panic("not yet implemented");
  }
  blob_reset(&fname);
}







<
<


|

|
>

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


|


>
>
|
|
|
>
>





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

>
>





|
|
|
|
|
>

>
>



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









<
<



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
  blob_appendf(pBlob, "\"%s\"", zIn);
  z = blob_buffer(pBlob);
  for(i=n+1; i<=n+k; i++){
    if( z[i]=='"' ) z[i] = '_';
  }
}



/*
** COMMAND: diff
** COMMAND: gdiff
**
** Usage: %fossil diff|gdiff ?-i? ?-r REVISION? FILE...
**
** Show the difference between the current version of a file (as it
** exists on disk) and that same file as it was checked out.
**
** diff will show a textual diff while gdiff will attempt to run a
** graphical diff command that you have setup. If the choosen command
** is not yet configured, the internal textual diff command will be
** used.
**
** If -i is supplied for either diff or gdiff, the internal textual
** diff command will be executed.
**
** Here are a few external diff command settings, for example:
**
**   %fossil setting diff-command diff
**
**   %fossil setting gdiff-command tkdiff
**   %fossil setting gdiff-command eskill22
**   %fossil setting gdiff-command tortoisemerge
**   %fossil setting gdiff-command meld
**   %fossil setting gdiff-command xxdiff
**   %fossil setting gdiff-command kdiff3
*/
void diff_cmd(void){
  const char *zFile, *zRevision;
  Blob cmd;
  Blob fname;
  Blob vname;
  Blob record;
  int cnt=0,internalDiff;

  internalDiff = find_option("internal","i",0)!=0;
  zRevision = find_option("revision", "r", 1);
  verify_all_options();

  if( g.argc<3 ){
    usage("?OPTIONS? FILE");
  }
  db_must_be_within_tree();

  if( internalDiff==0 ){

    const char *zExternalCommand;


    if( strcmp(g.argv[1], "diff")==0 ){

      zExternalCommand = db_get("diff-command", 0);
    }else{

      zExternalCommand = db_get("gdiff-command", 0);
    }
    if( zExternalCommand==0 ){
      internalDiff=1;

    }
    blob_zero(&cmd);
    blob_appendf(&cmd, "%s ", zExternalCommand);
  }
  zFile = g.argv[g.argc-1];
  if( !file_tree_name(zFile, &fname) ){
    fossil_panic("unknown file: %s", zFile);
  }

  blob_zero(&vname);
  do{
    blob_reset(&vname);
    blob_appendf(&vname, "%s~%d", zFile, cnt++);
  }while( access(blob_str(&vname),0)==0 );

  if( zRevision==0 ){
    int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B", &fname);
    if( rid==0 ){
      fossil_panic("no history for file: %b", &fname);
    }
    content_get(rid, &record);
  }else{
    historical_version_of_file(zRevision, zFile, &record);
  }
  if( internalDiff==1 ){
    Blob out;
    Blob current;
    blob_zero(&current);
    blob_read_from_file(&current, zFile);
    blob_zero(&out);
    text_diff(&record, &current, &out, 5);
    printf("%s\n", blob_str(&out));
    blob_reset(&current);

    blob_reset(&out);

  }else{
    blob_write_to_file(&record, blob_str(&vname));
    blob_reset(&record);
    shell_escape(&cmd, blob_str(&vname));
    blob_appendf(&cmd, " ");
    shell_escape(&cmd, zFile);
    system(blob_str(&cmd));
    unlink(blob_str(&vname));
    blob_reset(&vname);
    blob_reset(&cmd);


  }
  blob_reset(&fname);
}
Changes to src/encode.c.
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
/*
** Encode a N-digit base-256 in base-16.  Return zero on success
** and non-zero if there is an error.
*/
int encode16(const unsigned char *pIn, unsigned char *zOut, int N){
  int i;
  for(i=0; i<N; i++){
    *(zOut++) = zEncode[pIn[0]>>4];
    *(zOut++) = zEncode[pIn[0]&0xf];
  }
  *zOut = 0;
  return 0;
}

/*
** An array for translating single base-16 characters into a value.







|
|







400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
/*
** Encode a N-digit base-256 in base-16.  Return zero on success
** and non-zero if there is an error.
*/
int encode16(const unsigned char *pIn, unsigned char *zOut, int N){
  int i;
  for(i=0; i<N; i++){
    *(zOut++) = zEncode[pIn[i]>>4];
    *(zOut++) = zEncode[pIn[i]&0xf];
  }
  *zOut = 0;
  return 0;
}

/*
** An array for translating single base-16 characters into a value.
Changes to src/file.c.
103
104
105
106
107
108
109



110

111
112
113
114
115
116
117
int file_mkdir(const char *zName, int forceFlag){
  int rc = file_isdir(zName);
  if( rc==2 ){
    if( !forceFlag ) return 1;
    unlink(zName);
  }
  if( rc!=1 ){



    return mkdir(zName, 0755);

  }
  return 0;
}

/*
** Return true if the filename given is a valid filename for
** a file in a repository.  Valid filenames follow all of the







>
>
>

>







103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
int file_mkdir(const char *zName, int forceFlag){
  int rc = file_isdir(zName);
  if( rc==2 ){
    if( !forceFlag ) return 1;
    unlink(zName);
  }
  if( rc!=1 ){
#ifdef __MINGW32__
    return mkdir(zName);
#else
    return mkdir(zName, 0755);
#endif
  }
  return 0;
}

/*
** Return true if the filename given is a valid filename for
** a file in a repository.  Valid filenames follow all of the
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
** Compute a canonical pathname for a file or directory.
** Make the name absolute if it is relative.
** Remove redundant / characters
** Remove all /./ path elements.
** Convert /A/../ to just /
*/
void file_canonical_name(const char *zOrigName, Blob *pOut){
  if( zOrigName[0]=='/' ){

    blob_set(pOut, zOrigName);
    blob_materialize(pOut);
  }else{
    char zPwd[2000];
    if( getcwd(zPwd, sizeof(zPwd)-20)==0 ){
      fprintf(stderr, "pwd too big: max %d\n", sizeof(zPwd)-20);
      exit(1);
    }
    blob_zero(pOut);
    blob_appendf(pOut, "%s/%s", zPwd, zOrigName);
  }
  blob_resize(pOut, file_simplify_name(blob_buffer(pOut), blob_size(pOut)));
}

/*
** COMMAND:  test-canonical-name
**







|
>





|



|







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
** Compute a canonical pathname for a file or directory.
** Make the name absolute if it is relative.
** Remove redundant / characters
** Remove all /./ path elements.
** Convert /A/../ to just /
*/
void file_canonical_name(const char *zOrigName, Blob *pOut){
  if( zOrigName[0]=='/' 
      || (strlen(zOrigName)>3 && zOrigName[1]==':' && zOrigName[2]=='\\') ){
    blob_set(pOut, zOrigName);
    blob_materialize(pOut);
  }else{
    char zPwd[2000];
    if( getcwd(zPwd, sizeof(zPwd)-20)==0 ){
      fprintf(stderr, "pwd too big: max %d\n", (int)sizeof(zPwd)-20);
      exit(1);
    }
    blob_zero(pOut);
    blob_appendf(pOut, "%//%/", zPwd, zOrigName);
  }
  blob_resize(pOut, file_simplify_name(blob_buffer(pOut), blob_size(pOut)));
}

/*
** COMMAND:  test-canonical-name
**
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
  blob_resize(pOut, file_simplify_name(blob_buffer(pOut), blob_size(pOut))); 
  zPath = blob_buffer(pOut);
  if( zPath[0]=='/' ){
    int i, j;
    Blob tmp;
    char zPwd[2000];
    if( getcwd(zPwd, sizeof(zPwd)-20)==0 ){
      fprintf(stderr, "pwd too big: max %d\n", sizeof(zPwd)-20);
      exit(1);
    }
    for(i=1; zPath[i] && zPwd[i]==zPath[i]; i++){}
    if( zPath[i]==0 ){
      blob_reset(pOut);
      if( zPwd[i]==0 ){
        blob_append(pOut, ".", 1);







|







226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
  blob_resize(pOut, file_simplify_name(blob_buffer(pOut), blob_size(pOut))); 
  zPath = blob_buffer(pOut);
  if( zPath[0]=='/' ){
    int i, j;
    Blob tmp;
    char zPwd[2000];
    if( getcwd(zPwd, sizeof(zPwd)-20)==0 ){
      fprintf(stderr, "pwd too big: max %d\n", (int)sizeof(zPwd)-20);
      exit(1);
    }
    for(i=1; zPath[i] && zPwd[i]==zPath[i]; i++){}
    if( zPath[i]==0 ){
      blob_reset(pOut);
      if( zPwd[i]==0 ){
        blob_append(pOut, ".", 1);
Changes to src/http.c.
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
**
*******************************************************************************
**
** This file contains code that implements the client-side HTTP protocol
*/
#include "config.h"
#include "http.h"

#include <assert.h>


#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>



#include <signal.h>

/*
** Persistent information about the HTTP connection.
*/





static FILE *pSocket = 0;   /* The socket on which we talk to the server */
























/*
** Open a socket connection to the server.  Return 0 on success and
** non-zero if an error occurs.
*/
static int http_open_socket(void){
  static struct sockaddr_in addr;  /* The server address */
  static int addrIsInit = 0;       /* True once addr is initialized */
  int s;



  if( !addrIsInit ){

    addr.sin_family = AF_INET;
    addr.sin_port = htons(g.urlPort);
    *(int*)&addr.sin_addr = inet_addr(g.urlName);
    if( -1 == *(int*)&addr.sin_addr ){
#ifndef FOSSIL_STATIC_LINK
      struct hostent *pHost;
      pHost = gethostbyname(g.urlName);







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





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










>
>

>







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
**
*******************************************************************************
**
** This file contains code that implements the client-side HTTP protocol
*/
#include "config.h"
#include "http.h"
#ifdef __MINGW32__
#  include <windows.h>
#  include <winsock2.h>
#else
#  include <arpa/inet.h>

#  include <sys/socket.h>
#  include <netdb.h>
#  include <netinet/in.h>
#endif
#include <assert.h>
#include <sys/types.h>
#include <signal.h>

/*
** Persistent information about the HTTP connection.
*/

#ifdef __MINGW32__
static WSADATA ws_info;
static int pSocket = 0;     /* The socket on which we talk to the server on */
#else
static FILE *pSocket = 0;   /* The socket filehandle on which we talk to the server */
#endif

/*
** Winsock must be initialize before use.  This helper method allows us to
** always call ws_init in our code regardless of platform but only actually
** initialize winsock on the windows platform.
*/
static void ws_init(){
#ifdef __MINGW32__
  if (WSAStartup(MAKEWORD(2,0), &ws_info) != 0){
    fossil_panic("can't initialize winsock");
  }
#endif
}

/*
** Like ws_init, winsock must also be cleaned up after.
*/
static void ws_cleanup(){
#ifdef __MINGW32__
  WSACleanup();
#endif
}

/*
** Open a socket connection to the server.  Return 0 on success and
** non-zero if an error occurs.
*/
static int http_open_socket(void){
  static struct sockaddr_in addr;  /* The server address */
  static int addrIsInit = 0;       /* True once addr is initialized */
  int s;

  ws_init();
  
  if( !addrIsInit ){

    addr.sin_family = AF_INET;
    addr.sin_port = htons(g.urlPort);
    *(int*)&addr.sin_addr = inet_addr(g.urlName);
    if( -1 == *(int*)&addr.sin_addr ){
#ifndef FOSSIL_STATIC_LINK
      struct hostent *pHost;
      pHost = gethostbyname(g.urlName);
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
  s = socket(AF_INET,SOCK_STREAM,0);
  if( s<0 ){
    fossil_panic("cannot create a socket");
  }
  if( connect(s,(struct sockaddr*)&addr,sizeof(addr))<0 ){
    fossil_panic("cannot connect to host %s:%d", g.urlName, g.urlPort);
  }



  pSocket = fdopen(s,"r+");
  signal(SIGPIPE, SIG_IGN);

  return 0;
}


















































/*
** Make a single attempt to talk to the server.  Return TRUE on success
** and FALSE on a failure.
**
** pHeader contains the HTTP header.  pPayload contains the content.
** The content of the reply is written into pReply.  pReply is assumed
** to be uninitialized prior to this call.
**
** If an error occurs, this routine return false, resets pReply and
** closes the persistent connection, if any.
*/
static int http_send_recv(Blob *pHeader, Blob *pPayload, Blob *pReply){

  int rc;
  int closeConnection;
  int iLength;
  int iHttpVersion;
  int i;
  int nRead;
  char zLine[2000];

  if( pSocket==0 && http_open_socket() ){
    return 0;
  }







































  rc = fwrite(blob_buffer(pHeader), 1, blob_size(pHeader), pSocket);
  if( rc!=blob_size(pHeader) ) goto write_err;
  rc = fwrite(blob_buffer(pPayload), 1, blob_size(pPayload), pSocket);
  if( rc!=blob_size(pPayload) ) goto write_err;
  if( fflush(pSocket) ) goto write_err;
  if( fgets(zLine, sizeof(zLine), pSocket)==0 ) goto write_err;
  if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err;
  if( rc!=200 ) goto write_err;
  if( iHttpVersion==0 ){
    closeConnection = 1;
  }else{
    closeConnection = 0;
  }
  iLength = -1;
  while( fgets(zLine, sizeof(zLine), pSocket) ){
    for(i=0; zLine[i] && zLine[i]!='\n' && zLine[i]!='\r'; i++){}
    if( i==0 ) break;
    zLine[i] = 0;
    if( strncasecmp(zLine, "content-length:",15)==0 ){
      iLength = atoi(&zLine[16]);
    }else if( strncasecmp(zLine, "connection:", 11)==0 ){
      for(i=12; isspace(zLine[i]); i++){}
      if( zLine[i]=='c' || zLine[i]=='C' ){
        closeConnection = 1;   /* Connection: close */
      }else if( zLine[i]=='k' || zLine[i]=='K' ){
        closeConnection = 0;   /* Connection: keep-alive */
      }
    }
  }
  if( iLength<0 ) goto write_err;
  nRead = blob_read_from_channel(pReply, pSocket, iLength);

  if( nRead!=iLength ){
    blob_reset(pReply);
    goto write_err;
  }
  if( closeConnection ){
    http_close();
  }







>
>
>


>


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













>

<









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









|

|

<




|












>







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
  s = socket(AF_INET,SOCK_STREAM,0);
  if( s<0 ){
    fossil_panic("cannot create a socket");
  }
  if( connect(s,(struct sockaddr*)&addr,sizeof(addr))<0 ){
    fossil_panic("cannot connect to host %s:%d", g.urlName, g.urlPort);
  }
#ifdef __MINGW32__
  pSocket = s;
#else
  pSocket = fdopen(s,"r+");
  signal(SIGPIPE, SIG_IGN);
#endif
  return 0;
}

#ifdef __MINGW32__
/*
** Read the socket until a newline '\n' is found.  Return the number
** of characters read. pSockId contains the socket handel.  pOut
** contains a pointer to the buffer to write to.  pOutSize contains
** the maximum size of the line that pOut can handle.
*/
static int socket_recv_line(int pSockId, char* pOut, int pOutSize){
  int received=0;
  char letter;
  memset(pOut,0,pOutSize);
  for(; received<pOutSize-1;received++){
    if( recv(pSockId,(char*)&letter,1,0)>0 ){
      pOut[received]=letter;
      if( letter=='\n' ){
        break;
      }
    }else{
      break;
    }
  }
  return received;
}

/*
** Initialize a blob to the data on an input socket.  return
** the number of bytes read into the blob.  Any prior content
** of the blob is discarded, not freed.
**
** The function was placed here in http.c due to it's socket
** nature and we did not want to introduce socket headers into
** the socket netural blob.c file.
*/
int socket_read_blob(Blob *pBlob, int pSockId, int nToRead){
  int i=0,read=0;
  char rbuf[50];
  blob_zero(pBlob);
  while ( i<nToRead ){
    read = recv(pSockId, rbuf, 50, 0);
    i += read;
    if( read<0 ){
      return 0;
    }
    blob_append(pBlob, rbuf, read);
  }
  return blob_size(pBlob);
}
#endif

/*
** Make a single attempt to talk to the server.  Return TRUE on success
** and FALSE on a failure.
**
** pHeader contains the HTTP header.  pPayload contains the content.
** The content of the reply is written into pReply.  pReply is assumed
** to be uninitialized prior to this call.
**
** If an error occurs, this routine return false, resets pReply and
** closes the persistent connection, if any.
*/
static int http_send_recv(Blob *pHeader, Blob *pPayload, Blob *pReply){
  int closeConnection=1;   /* default to closing the connection */
  int rc;

  int iLength;
  int iHttpVersion;
  int i;
  int nRead;
  char zLine[2000];

  if( pSocket==0 && http_open_socket() ){
    return 0;
  }
  iLength = -1;
#ifdef __MINGW32__
  /*
  ** Use recv/send on the windows platform as winsock does not allow
  ** sockets to be used as FILE handles, thus fdopen, fwrite, fgets
  ** does not function on windows for sockets.
  */
  rc = send(pSocket, blob_buffer(pHeader), blob_size(pHeader), 0);
  if( rc!=blob_size(pHeader) ) goto write_err;
  rc = send(pSocket, blob_buffer(pPayload), blob_size(pPayload), 0);
  if( rc!=blob_size(pPayload) ) goto write_err;
  
  /* Read the response */
  while( socket_recv_line(pSocket, zLine, 2000) ){
    for( i=0; zLine[i] && zLine[i]!='\n' && zLine[i]!='\r'; i++ ){}
    if( i==0 ) break;
    zLine[i] = 0;
    if( strncasecmp(zLine, "http/1.", 7)==0 ){
      if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err;
      if( rc!=200 ) goto write_err;
      if( iHttpVersion==0 ){
        closeConnection = 1;
      }else{
        closeConnection = 0;
      }
    } else if( strncasecmp(zLine, "content-length:", 15)==0 ){
      iLength = atoi(&zLine[16]);
    }else if( strncasecmp(zLine, "connection:", 11)==0 ){
      for(i=12; isspace(zLine[i]); i++){}
      if( zLine[i]=='c' || zLine[i]=='C' ){
        closeConnection = 1;
      }else if( zLine[i]=='k' || zLine[i]=='K' ){
        closeConnection = 0;
      }
    }
  }
  if( iLength<0 ) goto write_err;
  nRead = socket_read_blob(pReply, pSocket, iLength);
#else
  rc = fwrite(blob_buffer(pHeader), 1, blob_size(pHeader), pSocket);
  if( rc!=blob_size(pHeader) ) goto write_err;
  rc = fwrite(blob_buffer(pPayload), 1, blob_size(pPayload), pSocket);
  if( rc!=blob_size(pPayload) ) goto write_err;
  if( fflush(pSocket) ) goto write_err;
  if( fgets(zLine, sizeof(zLine), pSocket)==0 ) goto write_err;
  if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err;
  if( rc!=200 ) goto write_err;
  if( iHttpVersion==0 ){
    closeConnection = 1;   /* Connection: close */
  }else{
    closeConnection = 0;   /* Connection: keep-alive */
  }

  while( fgets(zLine, sizeof(zLine), pSocket) ){
    for(i=0; zLine[i] && zLine[i]!='\n' && zLine[i]!='\r'; i++){}
    if( i==0 ) break;
    zLine[i] = 0;
    if( strncasecmp(zLine,"content-length:",15)==0 ){
      iLength = atoi(&zLine[16]);
    }else if( strncasecmp(zLine, "connection:", 11)==0 ){
      for(i=12; isspace(zLine[i]); i++){}
      if( zLine[i]=='c' || zLine[i]=='C' ){
        closeConnection = 1;   /* Connection: close */
      }else if( zLine[i]=='k' || zLine[i]=='K' ){
        closeConnection = 0;   /* Connection: keep-alive */
      }
    }
  }
  if( iLength<0 ) goto write_err;
  nRead = blob_read_from_channel(pReply, pSocket, iLength);
#endif
  if( nRead!=iLength ){
    blob_reset(pReply);
    goto write_err;
  }
  if( closeConnection ){
    http_close();
  }
258
259
260
261
262
263
264



265

266
267








268


/*
** Make sure the socket to the HTTP server is closed 
*/
void http_close(void){
  if( pSocket ){



    fclose(pSocket);

    pSocket = 0;
  }








}







>
>
>

>


>
>
>
>
>
>
>
>

386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408


/*
** Make sure the socket to the HTTP server is closed 
*/
void http_close(void){
  if( pSocket ){
#ifdef __MINGW32__
    closesocket(pSocket);
#else
    fclose(pSocket);
#endif
    pSocket = 0;
  }
  /*
  ** This is counter productive. Each time we open a connection we initialize
  ** winsock and then when closing we cleanup. It would be better to
  ** initialize winsock once at application start when we know we are going to
  ** use the socket interface and then cleanup once at application exit when
  ** we are all done with all socket operations.
  */
  ws_cleanup();
}
Changes to src/info.c.
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
** to a depth of N.  Return true if descendents are shown and false if not.
*/
static int showDescendents(int pid, int depth, const char *zTitle){
  Stmt q;
  int cnt = 0;
  db_prepare(&q,
    "SELECT plink.cid, blob.uuid, datetime(plink.mtime, 'localtime'),"

    "       event.user, event.comment"
    "  FROM plink, blob, event"
    " WHERE plink.pid=%d"
    "   AND blob.rid=plink.cid"
    "   AND event.objid=plink.cid"
    " ORDER BY plink.mtime ASC",
    pid
  );
  while( db_step(&q)==SQLITE_ROW ){
    int n;
    int cid = db_column_int(&q, 0);
    const char *zUuid = db_column_text(&q, 1);
    const char *zDate = db_column_text(&q, 2);
    const char *zUser = db_column_text(&q, 3);
    const char *zCom = db_column_text(&q, 4);
    cnt++;
    if( cnt==1 ){
      if( zTitle ){
        @ <h2>%s(zTitle)</h2>
      }
      @ <ul>
    }
    @ <li>
    hyperlink_to_uuid(zUuid);
    @ %s(zCom) (by %s(zUser) on %s(zDate))
    if( depth ){
      n = showDescendents(cid, depth-1, 0);
    }else{
      n = db_int(0, "SELECT 1 FROM plink WHERE pid=%d", cid);
    }
    if( n==0 ){
      db_multi_exec("DELETE FROM leaves WHERE rid=%d", cid);
      @ <b>leaf</b>
    }
  }

  if( cnt ){
    @ </ul>
  }
  return cnt;
}

/*
** Show information about ancestors of a version.  Do this recursively
** to a depth of N.  Return true if ancestors are shown and false if not.
*/
static void showAncestors(int pid, int depth, const char *zTitle){
  Stmt q;
  int cnt = 0;
  db_prepare(&q,
    "SELECT plink.pid, blob.uuid, datetime(event.mtime, 'localtime'),"

    "       event.user, event.comment"
    "  FROM plink, blob, event"
    " WHERE plink.cid=%d"
    "   AND blob.rid=plink.pid"
    "   AND event.objid=plink.pid"
    " ORDER BY event.mtime DESC",
    pid
  );
  while( db_step(&q)==SQLITE_ROW ){
    int cid = db_column_int(&q, 0);
    const char *zUuid = db_column_text(&q, 1);
    const char *zDate = db_column_text(&q, 2);
    const char *zUser = db_column_text(&q, 3);
    const char *zCom = db_column_text(&q, 4);
    cnt++;
    if( cnt==1 ){
      if( zTitle ){
        @ <h2>%s(zTitle)</h2>
      }
      @ <ul>
    }
    @ <li>
    hyperlink_to_uuid(zUuid);
    @ %s(zCom) (by %s(zUser) on %s(zDate))
    if( depth ){
      showAncestors(cid, depth-1, 0);
    }
  }

  if( cnt ){
    @ </ul>
  }
}


/*
** Show information about versions mentioned in the "leaves" table.
*/
static void showLeaves(void){
  Stmt q;
  int cnt = 0;
  db_prepare(&q,
    "SELECT blob.uuid, datetime(event.mtime, 'localtime'),"
    "       event.user, event.comment"

    "  FROM leaves, plink, blob, event"
    " WHERE plink.cid=leaves.rid"
    "   AND blob.rid=leaves.rid"
    "   AND event.objid=leaves.rid"
    " ORDER BY event.mtime DESC"
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    const char *zDate = db_column_text(&q, 1);
    const char *zUser = db_column_text(&q, 2);
    const char *zCom = db_column_text(&q, 3);
    cnt++;
    if( cnt==1 ){
      @ <h2>Leaves</h2>
      @ <ul>
    }
    @ <li>
    hyperlink_to_uuid(zUuid);
    @ %s(zCom) (by %s(zUser) on %s(zDate))
  }


















































  if( cnt ){
    @ </ul>
  }
}


/*
** WEBPAGE: vinfo

**
** Return information about a version.  The version number is contained
** in g.zExtra.
*/
void vinfo_page(void){
  Stmt q;
  int rid;
  int isLeaf;

  login_check_credentials();
  if( !g.okHistory ){ login_needed(); return; }
  style_header("Version Information");
  rid = name_to_rid(g.zExtra);
  if( rid==0 ){

    @ No such object: %h(g.argv[2])
    style_footer();
    return;
  }
  isLeaf = !db_exists("SELECT 1 FROM plink WHERE pid=%d", rid);
  db_prepare(&q, 
     "SELECT uuid, datetime(mtime, 'localtime'), user, comment"
     "  FROM blob, event"
     " WHERE blob.rid=%d"
     "   AND event.objid=%d",
     rid, rid
  );
  if( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);



    @ <h2>Version %s(zUuid)</h2>

    @ <ul>

    @ <li><b>Date:</b> %s(db_column_text(&q, 1))</li>



    @ <li><b>User:</b> %s(db_column_text(&q, 2))</li>
    @ <li><b>Comment:</b> %s(db_column_text(&q, 3))</li>



    @ <li><a href="%s(g.zBaseURL)/vdiff/%d(rid)">diff</a></li>
    @ <li><a href="%s(g.zBaseURL)/zip/%s(zUuid).zip">ZIP archive</a></li>
    @ <li><a href="%s(g.zBaseURL)/fview/%d(rid)">manifest</a></li>
    if( g.okSetup ){
      @ <li><b>Record ID:</b> %d(rid)</li>
    }

    @ </ul>



  }
  db_finalize(&q);

  @ <p><h2>Changes:</h2>
  @ <ul>
  db_prepare(&q, 
     "SELECT name, pid, fid"
     "  FROM mlink, filename"
     " WHERE mid=%d"
     "   AND filename.fnid=mlink.fnid",
     rid
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    int pid = db_column_int(&q, 1);
    int fid = db_column_int(&q, 2);
    @ <li>
    if( pid && fid ){
      @ <b>Modified:</b>
    }else if( fid ){
      @ <b>Added:</b>
    }else{
      @ <b>Deleted:</b>
    }
    @ <a href="%s(g.zBaseURL)/finfo/%T(zName)">%h(zName)</a></li>
  }
  @ </ul>
  compute_leaves(rid);
  showDescendents(rid, 2, "Descendents");
  showLeaves();
  showAncestors(rid, 2, "Ancestors");
  style_footer();
}

/*




























































** WEBPAGE: finfo

**
** Show the complete change history for a single file.  The name
** of the file is in g.zExtra
*/
void finfo_page(void){
  Stmt q;

  char zPrevDate[20];
  login_check_credentials();
  if( !g.okHistory ){ login_needed(); return; }
  style_header("File History");

  zPrevDate[0] = 0;

  db_prepare(&q,
    "SELECT a.uuid, substr(b.uuid,1,10), datetime(event.mtime,'localtime'),"


    "       event.comment, event.user, mlink.pid, mlink.fid"
    "  FROM mlink, blob a, blob b, event"
    " WHERE mlink.fnid=(SELECT fnid FROM filename WHERE name=%Q)"
    "   AND a.rid=mlink.mid"
    "   AND b.rid=mlink.fid"
    "   AND event.objid=mlink.mid"
    " ORDER BY event.mtime DESC",
    g.zExtra
  );
  @ <h2>History of %h(g.zExtra)</h2>
  @ <table cellspacing=0 border=0 cellpadding=0>
  while( db_step(&q)==SQLITE_ROW ){
    const char *zVers = db_column_text(&q, 0);
    const char *zUuid = db_column_text(&q, 1);
    const char *zDate = db_column_text(&q, 2);
    const char *zCom = db_column_text(&q, 3);
    const char *zUser = db_column_text(&q, 4);







>
|

















|





|










>















>
|
















|





|




>














|
>
|
<
|










|




|

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








>

|
<








<
|

>














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


>
|




















|










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

>

|
<



>






>


>
>
|






|

|







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
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
** to a depth of N.  Return true if descendents are shown and false if not.
*/
static int showDescendents(int pid, int depth, const char *zTitle){
  Stmt q;
  int cnt = 0;
  db_prepare(&q,
    "SELECT plink.cid, blob.uuid, datetime(plink.mtime, 'localtime'),"
    "       coalesce(event.euser,event.user),"
    "       coalesce(event.comment,event.ecomment)"
    "  FROM plink, blob, event"
    " WHERE plink.pid=%d"
    "   AND blob.rid=plink.cid"
    "   AND event.objid=plink.cid"
    " ORDER BY plink.mtime ASC",
    pid
  );
  while( db_step(&q)==SQLITE_ROW ){
    int n;
    int cid = db_column_int(&q, 0);
    const char *zUuid = db_column_text(&q, 1);
    const char *zDate = db_column_text(&q, 2);
    const char *zUser = db_column_text(&q, 3);
    const char *zCom = db_column_text(&q, 4);
    cnt++;
    if( cnt==1 ){
      if( zTitle ){
        @ <div class="section">%s(zTitle)</div>
      }
      @ <ul>
    }
    @ <li>
    hyperlink_to_uuid(zUuid);
    @ %w(zCom) (by %s(zUser) on %s(zDate))
    if( depth ){
      n = showDescendents(cid, depth-1, 0);
    }else{
      n = db_int(0, "SELECT 1 FROM plink WHERE pid=%d", cid);
    }
    if( n==0 ){
      db_multi_exec("DELETE FROM leaves WHERE rid=%d", cid);
      @ <b>leaf</b>
    }
  }
  db_finalize(&q);
  if( cnt ){
    @ </ul>
  }
  return cnt;
}

/*
** Show information about ancestors of a version.  Do this recursively
** to a depth of N.  Return true if ancestors are shown and false if not.
*/
static void showAncestors(int pid, int depth, const char *zTitle){
  Stmt q;
  int cnt = 0;
  db_prepare(&q,
    "SELECT plink.pid, blob.uuid, datetime(event.mtime, 'localtime'),"
    "       coalesce(event.euser,event.user),"
    "       coalesce(event.comment,event.ecomment)"
    "  FROM plink, blob, event"
    " WHERE plink.cid=%d"
    "   AND blob.rid=plink.pid"
    "   AND event.objid=plink.pid"
    " ORDER BY event.mtime DESC",
    pid
  );
  while( db_step(&q)==SQLITE_ROW ){
    int cid = db_column_int(&q, 0);
    const char *zUuid = db_column_text(&q, 1);
    const char *zDate = db_column_text(&q, 2);
    const char *zUser = db_column_text(&q, 3);
    const char *zCom = db_column_text(&q, 4);
    cnt++;
    if( cnt==1 ){
      if( zTitle ){
        @ <div class="section">%s(zTitle)</div>
      }
      @ <ul>
    }
    @ <li>
    hyperlink_to_uuid(zUuid);
    @ %w(zCom) (by %s(zUser) on %s(zDate))
    if( depth ){
      showAncestors(cid, depth-1, 0);
    }
  }
  db_finalize(&q);
  if( cnt ){
    @ </ul>
  }
}


/*
** Show information about versions mentioned in the "leaves" table.
*/
static void showLeaves(void){
  Stmt q;
  int cnt = 0;
  db_prepare(&q,
    "SELECT blob.uuid, datetime(event.mtime, 'localtime'),"
    "       coalesce(event.euser, event.user),"
    "       coalesce(event.ecomment,event.comment)"
    "  FROM leaves, blob, event"

    " WHERE blob.rid=leaves.rid"
    "   AND event.objid=leaves.rid"
    " ORDER BY event.mtime DESC"
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    const char *zDate = db_column_text(&q, 1);
    const char *zUser = db_column_text(&q, 2);
    const char *zCom = db_column_text(&q, 3);
    cnt++;
    if( cnt==1 ){
      @ <div class="section">Leaves</div>
      @ <ul>
    }
    @ <li>
    hyperlink_to_uuid(zUuid);
    @ %w(zCom) (by %s(zUser) on %s(zDate))
  }
  db_finalize(&q);
  if( cnt ){
    @ </ul>
  }
}

/*
** Show information about all tags on a given node.
*/
static void showTags(int rid){
  Stmt q;
  int cnt = 0;
  db_prepare(&q,
    "SELECT tag.tagid, tagname, srcid, blob.uuid, value,"
    "       datetime(tagxref.mtime,'localtime'), tagtype"
    "  FROM tagxref JOIN tag ON tagxref.tagid=tag.tagid"
    "       LEFT JOIN blob ON blob.rid=tagxref.srcid"
    " WHERE tagxref.rid=%d"
    " ORDER BY tagname", rid
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zTagname = db_column_text(&q, 1);
    int srcid = db_column_int(&q, 2);
    const char *zUuid = db_column_text(&q, 3);
    const char *zValue = db_column_text(&q, 4);
    const char *zDate = db_column_text(&q, 5);
    int tagtype = db_column_int(&q, 6);
    cnt++;
    if( cnt==1 ){
      @ <div class="section">Tags And Properties</div>
      @ <ul>
    }
    @ <li>
    @ <b>%h(zTagname)</b>
    if( zValue ){
      @ = %h(zValue)<i>
    }else if( tagtype==0 ){
      @ <i>Cancelled
    }else{
      @ <i>
    }
    if( srcid==0 ){
      @ Inherited
    }else if( zUuid ){
      @ From
      hyperlink_to_uuid(zUuid);
    }
    @ on %s(zDate)</i>
  }
  db_finalize(&q);
  if( cnt ){
    @ </ul>
  }
}


/*
** WEBPAGE: vinfo
** URL:  /vinfo?name=RID|UUID
**
** Return information about a version.

*/
void vinfo_page(void){
  Stmt q;
  int rid;
  int isLeaf;

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

  rid = name_to_rid(PD("name","0"));
  if( rid==0 ){
    style_header("Version Information Error");
    @ No such object: %h(g.argv[2])
    style_footer();
    return;
  }
  isLeaf = !db_exists("SELECT 1 FROM plink WHERE pid=%d", rid);
  db_prepare(&q, 
     "SELECT uuid, datetime(mtime, 'localtime'), user, comment"
     "  FROM blob, event"
     " WHERE blob.rid=%d"
     "   AND event.objid=%d",
     rid, rid
  );
  if( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    char *zTitle = mprintf("Version: [%.10s]", zUuid);
    style_header(zTitle);
    free(zTitle);
    /*@ <h2>Version %s(zUuid)</h2>*/
    @ <div class="section">Overview</div>
    @ <p><table class="label-value">
    @ <tr><th>Version:</th><td>%s(zUuid)</td></tr>
    @ <tr><th>Date:</th><td>%s(db_column_text(&q, 1))</td></tr>
    if( g.okSetup ){
      @ <tr><th>Record ID:</th><td>%d(rid)</td></tr>
    }
    @ <tr><th>Original&nbsp;User:</th><td>%h(db_column_text(&q, 2))</td></tr>
    @ <tr><th>Original&nbsp;Comment:</th><td>%w(db_column_text(&q,3))</td></tr>
    @ </td></tr>
    @ <tr><th>Commands:</th>
    @   <td>
    @     <a href="%s(g.zBaseURL)/vdiff/%d(rid)">diff</a>
    @     | <a href="%s(g.zBaseURL)/zip/%s(zUuid).zip">ZIP archive</a>
    @     | <a href="%s(g.zBaseURL)/fview/%d(rid)">manifest</a>



    @   </td>
    @ </tr>
    @ </table></p>
  }else{
    style_header("Version Information");
  }
  db_finalize(&q);
  showTags(rid);
  @ <div class="section">Changes</div>
  @ <ul>
  db_prepare(&q, 
     "SELECT name, pid, fid"
     "  FROM mlink, filename"
     " WHERE mid=%d"
     "   AND filename.fnid=mlink.fnid",
     rid
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    int pid = db_column_int(&q, 1);
    int fid = db_column_int(&q, 2);
    @ <li>
    if( pid && fid ){
      @ <b>Modified:</b>
    }else if( fid ){
      @ <b>Added:</b>
    }else{
      @ <b>Deleted:</b>
    }
    @ <a href="%s(g.zBaseURL)/finfo?name=%T(zName)">%h(zName)</a></li>
  }
  @ </ul>
  compute_leaves(rid);
  showDescendents(rid, 2, "Descendents");
  showLeaves();
  showAncestors(rid, 2, "Ancestors");
  style_footer();
}

/*
** WEBPAGE: winfo
** URL:  /winfo?name=RID
**
** Return information about a wiki page.
*/
void winfo_page(void){
  Stmt q;
  int rid;

  login_check_credentials();
  if( !g.okHistory ){ login_needed(); return; }
  rid = name_to_rid(PD("name","0"));
  if( rid==0 ){
    style_header("Wiki Page Information Error");
    @ No such object: %h(g.argv[2])
    style_footer();
    return;
  }
  db_prepare(&q, 
     "SELECT substr(tagname, 6, 1000), uuid,"
     "       datetime(event.mtime, 'localtime'), user"
     "  FROM tagxref, tag, blob, event"
     " WHERE tagxref.rid=%d"
     "   AND tag.tagid=tagxref.tagid"
     "   AND tag.tagname LIKE 'wiki-%%'"
     "   AND blob.rid=%d"
     "   AND event.objid=%d",
     rid, rid, rid
  );
  if( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    const char *zUuid = db_column_text(&q, 1);
    char *zTitle = mprintf("Wiki Page %s", zName);
    style_header(zTitle);
    free(zTitle);
    @ <div class="section">Overview</div>
    @ <p><table class="label-value">
    @ <tr><th>Version:</th><td>%s(zUuid)</td></tr>
    @ <tr><th>Date:</th><td>%s(db_column_text(&q, 2))</td></tr>
    if( g.okSetup ){
      @ <tr><th>Record ID:</th><td>%d(rid)</td></tr>
    }
    @ <tr><th>Original&nbsp;User:</th><td>%s(db_column_text(&q, 3))</td></tr>
    @ <tr><th>Commands:</th>
    @   <td>
/*    @     <a href="%s(g.zBaseURL)/wdiff/%d(rid)">diff</a> | */
    @     <a href="%s(g.zBaseURL)/whistory?page=%t(zName)">history</a>
    @     | <a href="%s(g.zBaseURL)/fview/%d(rid)">raw-text</a>
    @   </td>
    @ </tr>
    @ </table></p>
  }else{
    style_header("Wiki Information");
  }
  db_finalize(&q);
  showTags(rid);
  style_footer();
}

/*
** WEBPAGE: finfo
** URL: /finfo?name=FILENAME
**
** Show the complete change history for a single file. 

*/
void finfo_page(void){
  Stmt q;
  const char *zFilename;
  char zPrevDate[20];
  login_check_credentials();
  if( !g.okHistory ){ login_needed(); return; }
  style_header("File History");

  zPrevDate[0] = 0;
  zFilename = PD("name","");
  db_prepare(&q,
    "SELECT a.uuid, substr(b.uuid,1,10), datetime(event.mtime,'localtime'),"
    "       coalesce(event.ecomment, event.comment),"
    "       coalesce(event.euser, event.user),"
    "       mlink.pid, mlink.fid"
    "  FROM mlink, blob a, blob b, event"
    " WHERE mlink.fnid=(SELECT fnid FROM filename WHERE name=%Q)"
    "   AND a.rid=mlink.mid"
    "   AND b.rid=mlink.fid"
    "   AND event.objid=mlink.mid"
    " ORDER BY event.mtime DESC",
    zFilename
  );
  @ <h2>History of %h(zFilename)</h2>
  @ <table cellspacing=0 border=0 cellpadding=0>
  while( db_step(&q)==SQLITE_ROW ){
    const char *zVers = db_column_text(&q, 0);
    const char *zUuid = db_column_text(&q, 1);
    const char *zDate = db_column_text(&q, 2);
    const char *zCom = db_column_text(&q, 3);
    const char *zUser = db_column_text(&q, 4);
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
    @ <tr><td valign="top">%s(&zDate[11])</td>
    @ <td width="20"></td>
    @ <td valign="top" align="left">
    hyperlink_to_uuid(zVers);
    @ %h(zCom) (By: %h(zUser))
    @ Id: %s(zUuid)/%d(frid)
    @ <a href="%s(g.zBaseURL)/fview/%d(frid)">[view]</a>

    @ <a href="%s(g.zBaseURL)/fdiff?v1=%d(fpid)&amp;v2=%d(frid)">[diff]</a>

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


/*
** Append the difference between two RIDs to the output
*/
static void append_diff(int fromid, int toid){
  Blob from, to, out;
  content_get(fromid, &from);
  content_get(toid, &to);
  blob_zero(&out);
  unified_diff(&from, &to, 5, &out);
  @ %h(blob_str(&out))
  blob_reset(&from);
  blob_reset(&to);
  blob_reset(&out);  
}

/*
** WEBPAGE: vdiff

**
** Show all differences for a particular check-in specified by g.zExtra
*/
void vdiff_page(void){
  int rid;
  Stmt q;
  char *zUuid;

  login_check_credentials();
  if( !g.okHistory ){ login_needed(); return; }
  style_header("Version Diff");

  rid = name_to_rid(g.zExtra);
  if( rid==0 ){
    cgi_redirect("index");
  }
  db_prepare(&q,
     "SELECT pid, fid, name"
     "  FROM mlink, filename"
     " WHERE mlink.mid=%d"
     "   AND filename.fnid=mlink.fnid"
     " ORDER BY name",
     rid
  );
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  @ <h2>All Changes In Version
  hyperlink_to_uuid(zUuid);
  @ </h2>
  while( db_step(&q)==SQLITE_ROW ){
    int pid = db_column_int(&q,0);
    int fid = db_column_int(&q,1);
    const char *zName = db_column_text(&q,2);
    @ <p><a href="%s(g.zBaseURL)/finfo/%T(zName)">%h(zName)</a></p>
    @ <blockquote><pre>
    append_diff(pid, fid);
    @ </pre></blockquote>
  }
  db_finalize(&q);
  style_footer();
}







>
|
>
















|








>

|










|



















|







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
556
557
558
559
560
561
562
563
564
565
    @ <tr><td valign="top">%s(&zDate[11])</td>
    @ <td width="20"></td>
    @ <td valign="top" align="left">
    hyperlink_to_uuid(zVers);
    @ %h(zCom) (By: %h(zUser))
    @ Id: %s(zUuid)/%d(frid)
    @ <a href="%s(g.zBaseURL)/fview/%d(frid)">[view]</a>
    if( fpid ){
      @ <a href="%s(g.zBaseURL)/fdiff?v1=%d(fpid)&amp;v2=%d(frid)">[diff]</a>
    }
    @ </td>
  }
  db_finalize(&q);
  @ </table>
  style_footer();
}


/*
** Append the difference between two RIDs to the output
*/
static void append_diff(int fromid, int toid){
  Blob from, to, out;
  content_get(fromid, &from);
  content_get(toid, &to);
  blob_zero(&out);
  text_diff(&from, &to, &out, 5);
  @ %h(blob_str(&out))
  blob_reset(&from);
  blob_reset(&to);
  blob_reset(&out);  
}

/*
** WEBPAGE: vdiff
** URL: /vdiff?name=RID
**
** Show all differences for a particular check-in.
*/
void vdiff_page(void){
  int rid;
  Stmt q;
  char *zUuid;

  login_check_credentials();
  if( !g.okHistory ){ login_needed(); return; }
  style_header("Version Diff");

  rid = name_to_rid(PD("name",""));
  if( rid==0 ){
    cgi_redirect("index");
  }
  db_prepare(&q,
     "SELECT pid, fid, name"
     "  FROM mlink, filename"
     " WHERE mlink.mid=%d"
     "   AND filename.fnid=mlink.fnid"
     " ORDER BY name",
     rid
  );
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  @ <h2>All Changes In Version
  hyperlink_to_uuid(zUuid);
  @ </h2>
  while( db_step(&q)==SQLITE_ROW ){
    int pid = db_column_int(&q,0);
    int fid = db_column_int(&q,1);
    const char *zName = db_column_text(&q,2);
    @ <p><a href="%s(g.zBaseURL)/finfo?name=%T(zName)">%h(zName)</a></p>
    @ <blockquote><pre>
    append_diff(pid, fid);
    @ </pre></blockquote>
  }
  db_finalize(&q);
  style_footer();
}
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
556
557
558
559
560
561
562
563
564





















































































**     * It's uuid
**     * date of check-in
**     * Comment & user
*/
static void object_description(int rid, int linkToView){
  Stmt q;
  int cnt = 0;

  db_prepare(&q,
    "SELECT filename.name, datetime(event.mtime), substr(a.uuid,1,10),"


    "       event.comment, event.user, b.uuid"
    "  FROM mlink, filename, event, blob a, blob b"
    " WHERE filename.fnid=mlink.fnid"
    "   AND event.objid=mlink.mid"
    "   AND a.rid=mlink.fid"
    "   AND b.rid=mlink.mid"
    "   AND mlink.fid=%d",
    rid
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    const char *zDate = db_column_text(&q, 1);
    const char *zFuuid = db_column_text(&q, 2);
    const char *zCom = db_column_text(&q, 3);
    const char *zUser = db_column_text(&q, 4);
    const char *zVers = db_column_text(&q, 5);
    @ File <a href="%s(g.zBaseURL)/finfo/%T(zName)">%h(zName)</a>
    @ uuid %s(zFuuid) part of check-in
    hyperlink_to_uuid(zVers);
    @ %s(zCom) by %s(zUser) on %s(zDate).
    cnt++;
  }
  db_finalize(&q);
























  db_prepare(&q,
    "SELECT datetime(mtime), user, comment, uuid"
    "  FROM event, blob"
    " WHERE event.objid=%d"
    "   AND blob.rid=%d",
    rid, rid
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zDate = db_column_text(&q, 0);
    const char *zUuid = db_column_text(&q, 3);
    const char *zCom = db_column_text(&q, 2);
    const char *zUser = db_column_text(&q, 1);

    @ Version
    hyperlink_to_uuid(zUuid);
    @ %s(zCom) by %s(zUser) on %s(zDate).
    cnt++;
  }
  db_finalize(&q);

  if( cnt==0 ){

    @ Empty file
  }else if( linkToView ){
    @ <a href="%s(g.zBaseURL)/fview/%d(rid)">[view]</a>
  }
}

/*
** WEBPAGE: fdiff
**
** Two arguments, v1 and v2, are integers.  Show the difference between
** the two records.
*/
void diff_page(void){
  int v1 = atoi(PD("v1","0"));
  int v2 = atoi(PD("v2","0"));
  Blob c1, c2, diff;

  login_check_credentials();
  if( !g.okHistory ){ login_needed(); return; }
  style_header("Diff");
  @ <h2>Differences From:</h2>
  @ <blockquote>
  object_description(v1, 1);
  @ </blockquote>
  @ <h2>To:</h2>
  @ <blockquote>
  object_description(v2, 1);
  @ </blockquote>
  @ <hr>
  @ <blockquote><pre>
  content_get(v1, &c1);
  content_get(v2, &c2);
  blob_zero(&diff);
  unified_diff(&c1, &c2, 4, &diff);
  blob_reset(&c1);
  blob_reset(&c2);
  @ %h(blob_str(&diff))
  @ </pre></blockquote>
  blob_reset(&diff);
  style_footer();
}

/*
** WEBPAGE: fview
** URL: /fview/UUID
** 
** Show the complete content of a file identified by UUID
** as preformatted text.
*/
void fview_page(void){
  int rid;
  Blob content;

  rid = name_to_rid(g.zExtra);
  login_check_credentials();
  if( !g.okHistory ){ login_needed(); return; }











  style_header("File Content");
  @ <h2>Content Of:</h2>
  @ <blockquote>
  object_description(rid, 0);
  @ </blockquote>
  @ <hr>
  @ <blockquote><pre>
  content_get(rid, &content);
  @ %h(blob_str(&content))
  @ </pre></blockquote>
  blob_reset(&content);
  style_footer();
}




























































































>


>
>
|















|


|



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

>
|












|
|


















|










|








|


>
>
>
>
>
>
>
>
>
>
>













>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
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
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
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
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
**     * It's uuid
**     * date of check-in
**     * Comment & user
*/
static void object_description(int rid, int linkToView){
  Stmt q;
  int cnt = 0;
  int nWiki = 0;
  db_prepare(&q,
    "SELECT filename.name, datetime(event.mtime), substr(a.uuid,1,10),"
    "       coalesce(event.comment,event.ecomment),"
    "       coalesce(event.euser,event.user),"
    "       b.uuid"
    "  FROM mlink, filename, event, blob a, blob b"
    " WHERE filename.fnid=mlink.fnid"
    "   AND event.objid=mlink.mid"
    "   AND a.rid=mlink.fid"
    "   AND b.rid=mlink.mid"
    "   AND mlink.fid=%d",
    rid
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    const char *zDate = db_column_text(&q, 1);
    const char *zFuuid = db_column_text(&q, 2);
    const char *zCom = db_column_text(&q, 3);
    const char *zUser = db_column_text(&q, 4);
    const char *zVers = db_column_text(&q, 5);
    @ File <a href="%s(g.zBaseURL)/finfo?name=%T(zName)">%h(zName)</a>
    @ uuid %s(zFuuid) part of check-in
    hyperlink_to_uuid(zVers);
    @ %w(zCom) by %h(zUser) on %s(zDate)
    cnt++;
  }
  db_finalize(&q);
  db_prepare(&q, 
    "SELECT substr(tagname, 6, 10000), datetime(event.mtime),"
    "       coalesce(event.euser, event.user), uuid"
    "  FROM tagxref, tag, event, blob"
    " WHERE tagxref.rid=%d"
    "   AND tag.tagid=tagxref.tagid" 
    "   AND tag.tagname LIKE 'wiki-%%'"
    "   AND event.objid=tagxref.rid"
    "   AND blob.rid=tagxref.rid",
    rid
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zPagename = db_column_text(&q, 0);
    const char *zDate = db_column_text(&q, 1);
    const char *zUser = db_column_text(&q, 2);
    const char *zUuid = db_column_text(&q, 3);
    @ Wiki page
    @ [<a href="%s(g.zBaseURL)/wiki?page=%t(zPagename)">%h(zPagename)</a>]
    @ uuid %s(zUuid) by %h(zUser) on %s(zDate)
    nWiki++;
    cnt++;
  }
  db_finalize(&q);
  if( nWiki==0 ){
    db_prepare(&q,
      "SELECT datetime(mtime), user, comment, uuid"
      "  FROM event, blob"
      " WHERE event.objid=%d"
      "   AND blob.rid=%d",
      rid, rid
    );
    while( db_step(&q)==SQLITE_ROW ){
      const char *zDate = db_column_text(&q, 0);
      const char *zUuid = db_column_text(&q, 3);

      const char *zUser = db_column_text(&q, 1);
      const char *zCom = db_column_text(&q, 2);
      @ Manifest of version
      hyperlink_to_uuid(zUuid);
      @ %w(zCom) by %h(zUser) on %s(zDate)
      cnt++;
    }
    db_finalize(&q);
  }
  if( cnt==0 ){
    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
    @ Control file %s(zUuid).
  }else if( linkToView ){
    @ <a href="%s(g.zBaseURL)/fview/%d(rid)">[view]</a>
  }
}

/*
** WEBPAGE: fdiff
**
** Two arguments, v1 and v2, are integers.  Show the difference between
** the two records.
*/
void diff_page(void){
  int v1 = name_to_rid(PD("v1","0"));
  int v2 = name_to_rid(PD("v2","0"));
  Blob c1, c2, diff;

  login_check_credentials();
  if( !g.okHistory ){ login_needed(); return; }
  style_header("Diff");
  @ <h2>Differences From:</h2>
  @ <blockquote>
  object_description(v1, 1);
  @ </blockquote>
  @ <h2>To:</h2>
  @ <blockquote>
  object_description(v2, 1);
  @ </blockquote>
  @ <hr>
  @ <blockquote><pre>
  content_get(v1, &c1);
  content_get(v2, &c2);
  blob_zero(&diff);
  text_diff(&c1, &c2, &diff, 4);
  blob_reset(&c1);
  blob_reset(&c2);
  @ %h(blob_str(&diff))
  @ </pre></blockquote>
  blob_reset(&diff);
  style_footer();
}

/*
** WEBPAGE: fview
** URL: /fview?name=UUID
** 
** Show the complete content of a file identified by UUID
** as preformatted text.
*/
void fview_page(void){
  int rid;
  Blob content;

  rid = name_to_rid(PD("name","0"));
  login_check_credentials();
  if( !g.okHistory ){ login_needed(); return; }
  if( g.zPath[0]=='i' ){
    if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)"
                  " WHERE rid=%d AND tagname LIKE 'wiki-%%'", rid) ){
      winfo_page();
      return;
    }
    if( db_exists("SELECT 1 FROM plink WHERE cid=%d", rid) ){
      vinfo_page();
      return;
    }
  }
  style_header("File Content");
  @ <h2>Content Of:</h2>
  @ <blockquote>
  object_description(rid, 0);
  @ </blockquote>
  @ <hr>
  @ <blockquote><pre>
  content_get(rid, &content);
  @ %h(blob_str(&content))
  @ </pre></blockquote>
  blob_reset(&content);
  style_footer();
}

/*
** WEBPAGE: info
** URL: info/UUID
**
** The argument is a UUID which might be a baseline or a file or
** a ticket or something else.  It might also be a wiki page name.
** Figure out what the UUID is an jump to it.  If there is ambiguity,
** draw a page and let the user select the interpretation.
*/
void info_page(void){
  const char *zName;
  int rc, nName, cnt;
  Stmt q;
  
  zName = P("name");
  if( zName==0 ) cgi_redirect("index");
  nName = strlen(zName);
  if( nName<4 || nName>UUID_SIZE || !validate16(zName, nName) ){
    cgi_redirect("index");
  }
  db_multi_exec(
     "CREATE TEMP TABLE refs(type,link);"
     "INSERT INTO refs "
     "  SELECT 'f', rid FROM blob WHERE uuid GLOB '%s*'"
     "  UNION ALL"
     "  SELECT 'w', substr(tagname,6) FROM tag"
     "   WHERE tagname='wiki-%q'"
     "  UNION ALL"
     "  SELECT 't', tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*';",
     zName, zName, zName
  );
  cnt = db_int(0, "SELECT count(*) FROM refs");
  if( cnt==0 ){
    style_header("Broken Link");
    @ <p>No such object: %h(zName)</p>
    style_footer();
    return;
  }
  db_prepare(&q, "SELECT type, link FROM refs");
  db_step(&q);
  if( cnt==1 ){
    int type = *db_column_text(&q, 0);
    int rid = db_column_int(&q, 1);
    db_finalize(&q);
    if( type=='w' ){
      wiki_page();
    }else if( type=='t' ){
      tktview_page();
    }else{
      cgi_replace_parameter("name", mprintf("%d", rid));
      if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
        vinfo_page();
      }else{
        finfo_page();
      }
    }
    return;
  }
  /* Multiple objects */
  style_header("Ambiguous Link");
  @ <h2>Ambiguous Link: %h(zName)</h2>
  @ <ul>
  while( rc==SQLITE_ROW ){
    int type = *db_column_text(&q, 0);
    if( type=='f' ){
      @ <li><p>
      object_description(db_column_int(&q, 1), 1);
      @ </p></li>
    }else if( type=='w' ){
      @ <li><p>
      @ Wiki page <a href="%s(g.zBaseURL)/wiki?name=%s(zName)">%s(zName)</a>.
      @ </li><p>
    }else if( type=='t' ){
      const char *zUuid = db_column_text(&q, 1);
      @ <li><p>
      @ Ticket <a href="%s(g.zBaseURL)/tktview?name=%s(zUuid)">%s(zUuid)</a>.
      @ </li><p>
    }
    rc = db_step(&q);
  }
  @ </ul>
  style_footer();
  db_finalize(&q);
}
Changes to src/login.c.
42
43
44
45
46
47
48




49
50
51
52
53
54





55



56
57
58
59
60
61
62
** not really the point.  The anonymous login keeps search-engine
** crawlers and site download tools like wget from walking change
** logs and downloading diffs of very version of the archive that
** has ever existed, and things like that.
*/
#include "config.h"
#include "login.h"




#include <time.h>

/*
** Return the name of the login cookie
*/
static char *login_cookie_name(void){





  return "fossil_login";



}

/*
** WEBPAGE: /login
** WEBPAGE: /logout
**
** Generate the login page







>
>
>
>






>
>
>
>
>
|
>
>
>







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
** not really the point.  The anonymous login keeps search-engine
** crawlers and site download tools like wget from walking change
** logs and downloading diffs of very version of the archive that
** has ever existed, and things like that.
*/
#include "config.h"
#include "login.h"
#ifdef __MINGW32__
#  include <windows.h>           /* for Sleep */
#  define sleep Sleep            /* windows does not have sleep, but Sleep */
#endif
#include <time.h>

/*
** Return the name of the login cookie
*/
static char *login_cookie_name(void){
  static char *zCookieName = 0;
  if( zCookieName==0 ){
    int n = strlen(g.zTop);
    zCookieName = malloc( n*2+16 );
                      /* 0123456789 12345 */
    strcpy(zCookieName, "fossil_login_");
    encode16((unsigned char*)g.zTop, (unsigned char*)&zCookieName[13], n);
  }
  return zCookieName;
}

/*
** WEBPAGE: /login
** WEBPAGE: /logout
**
** Generate the login page
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237


  /* If the HTTP connection is coming over 127.0.0.1 and if
  ** local login is disabled, then there is no need to check
  ** user credentials.
  */
  zRemoteAddr = PD("REMOTE_ADDR","nil");
  if( strcmp(zRemoteAddr, "127.0.0.1")==0
        && db_get_int("authenticate-localhost",1)==0 ){
    uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
    g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
    zCap = "s";
    g.noPswd = 1;
  }

  /* Check the login cookie to see if it matches a known valid user.







|
<







234
235
236
237
238
239
240
241

242
243
244
245
246
247
248


  /* If the HTTP connection is coming over 127.0.0.1 and if
  ** local login is disabled, then there is no need to check
  ** user credentials.
  */
  zRemoteAddr = PD("REMOTE_ADDR","nil");
  if( strcmp(zRemoteAddr, "127.0.0.1")==0 && db_get_int("localauth",0)==0 ){

    uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
    g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
    zCap = "s";
    g.noPswd = 1;
  }

  /* Check the login cookie to see if it matches a known valid user.
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
/*
** Set the global capability flags based on a capability string.
*/
void login_set_capabilities(const char *zCap){
  int i;
  for(i=0; zCap[i]; i++){
    switch( zCap[i] ){
      case 's':   g.okSetup = g.okDelete = 1;
      case 'a':   g.okAdmin = g.okRdTkt = g.okWrTkt = g.okQuery =
                              g.okRdWiki = g.okWrWiki = g.okHistory =

                              g.okNewTkt = g.okPassword = g.okClone = 1;
      case 'i':   g.okRead = g.okWrite = 1;                     break;
      case 'o':   g.okRead = 1;                                 break;

      case 'd':   g.okDelete = 1;                               break;
      case 'h':   g.okHistory = 1;                              break;
      case 'g':   g.okClone = 1;                                break;
      case 'p':   g.okPassword = 1;                             break;
      case 'q':   g.okQuery = 1;                                break;

      case 'j':   g.okRdWiki = 1;                               break;
      case 'k':   g.okWrWiki = g.okRdWiki = g.okApndWiki =1;    break;
      case 'm':   g.okApndWiki = 1;                             break;
      case 'f':   g.okNewWiki = 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;
    }
  }
}




































/*
** Call this routine when the credential check fails.  It causes
** a redirect to the "login" page.
*/
void login_needed(void){
  const char *zUrl = PD("REQUEST_URI", "index");
  cgi_redirect(mprintf("login?g=%T", zUrl));
  /* NOTREACHED */
  assert(0);
}







|

|
>
|














>








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











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
/*
** Set the global capability flags based on a capability string.
*/
void login_set_capabilities(const char *zCap){
  int i;
  for(i=0; zCap[i]; i++){
    switch( zCap[i] ){
      case 's':   g.okSetup = 1;
      case 'a':   g.okAdmin = g.okRdTkt = g.okWrTkt = g.okQuery =
                              g.okRdWiki = g.okWrWiki = g.okNewWiki =
                              g.okApndWiki = g.okHistory = g.okClone = 
                              g.okNewTkt = g.okPassword = g.okRdAddr = 1;
      case 'i':   g.okRead = g.okWrite = 1;                     break;
      case 'o':   g.okRead = 1;                                 break;

      case 'd':   g.okDelete = 1;                               break;
      case 'h':   g.okHistory = 1;                              break;
      case 'g':   g.okClone = 1;                                break;
      case 'p':   g.okPassword = 1;                             break;
      case 'q':   g.okQuery = 1;                                break;

      case 'j':   g.okRdWiki = 1;                               break;
      case 'k':   g.okWrWiki = g.okRdWiki = g.okApndWiki =1;    break;
      case 'm':   g.okApndWiki = 1;                             break;
      case 'f':   g.okNewWiki = 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;
    }
  }
}

/*
** If the current login lacks any of the capabilities listed in
** the input, then return 0.  If all capabilities are present, then
** return 1.
*/
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 '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;
      case 'j':  rc = g.okRdWiki;    break;
      case 'k':  rc = g.okWrWiki;    break;
      case 'm':  rc = g.okApndWiki;  break;
      case 'n':  rc = g.okNewTkt;    break;
      case 'o':  rc = g.okRead;      break;
      case 'p':  rc = g.okPassword;  break;
      case 'q':  rc = g.okQuery;     break;
      case 'r':  rc = g.okRdTkt;     break;
      case 's':  rc = g.okSetup;     break;
      case 'w':  rc = g.okWrTkt;     break;
      default:   rc = 0;             break;
    }
  }
  return rc;
}

/*
** Call this routine when the credential check fails.  It causes
** a redirect to the "login" page.
*/
void login_needed(void){
  const char *zUrl = PD("REQUEST_URI", "index");
  cgi_redirect(mprintf("login?g=%T", zUrl));
  /* NOTREACHED */
  assert(0);
}
Changes to src/main.c.
57
58
59
60
61
62
63

64
65
66
67
68
69
70
  int minPrefix;          /* Number of digits needed for a distinct UUID */
  int fSqlTrace;          /* True if -sqltrace flag is present */
  int fSqlPrint;          /* True if -sqlprint flag is present */
  int fHttpTrace;         /* Trace outbound HTTP requests */
  char *zPath;            /* Name of webpage being served */
  char *zExtra;           /* Extra path information past the webpage name */
  char *zBaseURL;         /* Full text of the URL being served */

  const char *zContentType;  /* The content type of the input HTTP request */
  int iErrPriority;       /* Priority of current error message */
  char *zErrMsg;          /* Text of an error message */
  Blob cgiIn;             /* Input to an xfer www method */
  int cgiPanic;           /* Write error messages to CGI */

  int *aCommitFile;







>







57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
  int minPrefix;          /* Number of digits needed for a distinct UUID */
  int fSqlTrace;          /* True if -sqltrace flag is present */
  int fSqlPrint;          /* True if -sqlprint flag is present */
  int fHttpTrace;         /* Trace outbound HTTP requests */
  char *zPath;            /* Name of webpage being served */
  char *zExtra;           /* Extra path information past the webpage name */
  char *zBaseURL;         /* Full text of the URL being served */
  char *zTop;             /* Parent directory of zPath */
  const char *zContentType;  /* The content type of the input HTTP request */
  int iErrPriority;       /* Priority of current error message */
  char *zErrMsg;          /* Text of an error message */
  Blob cgiIn;             /* Input to an xfer www method */
  int cgiPanic;           /* Write error messages to CGI */

  int *aCommitFile;
100
101
102
103
104
105
106

107
108
109
110
111
112
113
  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 */


  FILE *fDebug;           /* Write debug information here, if the file exists */
};

/*
** Macro for debugging:
*/







>







101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
  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 okRdAddr;           /* e: read email addresses on tickets */

  FILE *fDebug;           /* Write debug information here, if the file exists */
};

/*
** Macro for debugging:
*/
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
*/
void help_cmd(void){
  int rc, idx;
  const char *z;
  if( g.argc!=3 ){
    printf("Usage: %s help COMMAND.\nAvailable COMMANDs:\n", g.argv[0]);
    cmd_cmd_list();
    printf("You are running fossil baseline " MANIFEST_UUID "\n");
    return;
  }
  rc = name_search(g.argv[2], aCommand, count(aCommand), &idx);
  if( rc==1 ){
    fossil_fatal("unknown command: %s", g.argv[2]);
  }else if( rc==2 ){
    fossil_fatal("ambiguous command prefix: %s", g.argv[2]);







|







385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
*/
void help_cmd(void){
  int rc, idx;
  const char *z;
  if( g.argc!=3 ){
    printf("Usage: %s help COMMAND.\nAvailable COMMANDs:\n", g.argv[0]);
    cmd_cmd_list();
    printf("This is fossil version " MANIFEST_VERSION " " MANIFEST_DATE "\n");
    return;
  }
  rc = name_search(g.argv[2], aCommand, count(aCommand), &idx);
  if( rc==1 ){
    fossil_fatal("unknown command: %s", g.argv[2]);
  }else if( rc==2 ){
    fossil_fatal("ambiguous command prefix: %s", g.argv[2]);
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
      z++;
    }
  }
  putchar('\n');
}

/*
** RSS feeds need to reference absolute URLs so we need to calculate
** the base URL onto which we add components. This is basically
** cgi_redirect() stripped down and always returning an absolute URL.
*/
static char *get_base_url(void){
  int i;
  const char *zHost = PD("HTTP_HOST","");
  const char *zMode = PD("HTTPS","off");
  const char *zCur = PD("REQUEST_URI","/");

  for(i=0; zCur[i] && zCur[i]!='?' && zCur[i]!='#'; i++){}
  if( g.zExtra ){
    /* Skip to start of extra stuff, then pass over any /'s that might
    ** have separated the document root from the extra stuff. This
    ** ensures that the redirection actually redirects the root, not
    ** something deep down at the bottom of a URL.
    */
    i -= strlen(g.zExtra);
    while( i>0 && zCur[i-1]=='/' ){ i--; }
  }
  while( i>0 && zCur[i-1]!='/' ){ i--; }
  while( i>0 && zCur[i-1]=='/' ){ i--; }

  if( strcmp(zMode,"on")==0 ){
    return mprintf("https://%s%.*s", zHost, i, zCur);

  }
  return mprintf("http://%s%.*s", zHost, i, zCur);


}

/*
** Preconditions:
**
**    * Environment various are set up according to the CGI standard.
**    * The respository database has been located and opened.
** 
** Process the webpage specified by the PATH_INFO or REQUEST_URI
** environment variable.
*/
static void process_one_web_page(void){
  const char *zPathInfo;
  char *zPath;
  int idx;
  int i, j;

  /* Find the page that the user has requested, construct and deliver that
  ** page.
  */
  zPathInfo = P("PATH_INFO");
  if( zPathInfo==0 || zPathInfo[0]==0 ){
    const char *zUri;
    char *zBase;
    zUri = PD("REQUEST_URI","/");
    for(i=0; zUri[i] && zUri[i]!='?' && zUri[i]!='#'; i++){}
    for(j=i; j>0 && zUri[j-1]!='/'; j--){}
    zBase = mprintf("%.*s/index", i-j, &zUri[j]);
    cgi_redirect(zBase);
    cgi_reply();
    return;
  }else{
    zPath = mprintf("%s", zPathInfo);
  }

  /* Remove the leading "/" at the beginning of the path.
  */
  g.zPath = &zPath[1];
  for(i=1; zPath[i] && zPath[i]!='/'; i++){}
  if( zPath[i]=='/' ){
    zPath[i] = 0;
    g.zExtra = &zPath[i+1];





    /* CGI parameters get this treatment elsewhere, but places like getfile
    ** will use g.zExtra directly.
    */
    dehttpize(g.zExtra);
  }else{
    g.zExtra = 0;
  }
  g.zBaseURL = get_base_url();

  /* Prevent robots from indexing this site.
  */
  if( strcmp(g.zPath, "robots.txt")==0 ){
    cgi_set_content_type("text/plain");
    @ User-agent: *
    @ Disallow: /







|
|
|

|



















|
>
|
|
>
>





|

















<



<
|
<
<











>
>
|
>
>




<
|

<







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
      z++;
    }
  }
  putchar('\n');
}

/*
** Set the g.zBaseURL value to the full URL for the toplevel of
** the fossil tree.  Set g.zHomeURL to g.zBaseURL without the
** leading "http://" and the host and port.
*/
void set_base_url(void){
  int i;
  const char *zHost = PD("HTTP_HOST","");
  const char *zMode = PD("HTTPS","off");
  const char *zCur = PD("REQUEST_URI","/");

  for(i=0; zCur[i] && zCur[i]!='?' && zCur[i]!='#'; i++){}
  if( g.zExtra ){
    /* Skip to start of extra stuff, then pass over any /'s that might
    ** have separated the document root from the extra stuff. This
    ** ensures that the redirection actually redirects the root, not
    ** something deep down at the bottom of a URL.
    */
    i -= strlen(g.zExtra);
    while( i>0 && zCur[i-1]=='/' ){ i--; }
  }
  while( i>0 && zCur[i-1]!='/' ){ i--; }
  while( i>0 && zCur[i-1]=='/' ){ i--; }

  if( strcmp(zMode,"on")==0 ){
    g.zBaseURL = mprintf("https://%s%.*s", zHost, i, zCur);
    g.zTop = &g.zBaseURL[8+strlen(zHost)];
  }else{
    g.zBaseURL = mprintf("http://%s%.*s", zHost, i, zCur);
    g.zTop = &g.zBaseURL[7+strlen(zHost)];
  }
}

/*
** Preconditions:
**
**    * Environment variables are set up according to the CGI standard.
**    * The respository database has been located and opened.
** 
** Process the webpage specified by the PATH_INFO or REQUEST_URI
** environment variable.
*/
static void process_one_web_page(void){
  const char *zPathInfo;
  char *zPath;
  int idx;
  int i, j;

  /* Find the page that the user has requested, construct and deliver that
  ** page.
  */
  zPathInfo = P("PATH_INFO");
  if( zPathInfo==0 || zPathInfo[0]==0 ){
    const char *zUri;

    zUri = PD("REQUEST_URI","/");
    for(i=0; zUri[i] && zUri[i]!='?' && zUri[i]!='#'; i++){}
    for(j=i; j>0 && zUri[j-1]!='/'; j--){}

    cgi_redirectf("%.*s/index", i, zUri);


  }else{
    zPath = mprintf("%s", zPathInfo);
  }

  /* Remove the leading "/" at the beginning of the path.
  */
  g.zPath = &zPath[1];
  for(i=1; zPath[i] && zPath[i]!='/'; i++){}
  if( zPath[i]=='/' ){
    zPath[i] = 0;
    g.zExtra = &zPath[i+1];
  }else{
    g.zExtra = 0;
  }
  set_base_url();
  if( g.zExtra ){
    /* CGI parameters get this treatment elsewhere, but places like getfile
    ** will use g.zExtra directly.
    */
    dehttpize(g.zExtra);

    cgi_set_parameter_nocopy("name", g.zExtra);
  }


  /* Prevent robots from indexing this site.
  */
  if( strcmp(g.zPath, "robots.txt")==0 ){
    cgi_set_content_type("text/plain");
    @ User-agent: *
    @ Disallow: /
594
595
596
597
598
599
600









601
602
603
604
605
606
607
    db_open_repository(g.argv[2]);
  }else{
    db_must_be_within_tree();
  }
  cgi_handle_http_request();
  process_one_web_page();
}










/*
** COMMAND: server
**
** Usage: %fossil server ?-P|--port TCPPORT? ?REPOSITORY?
**
** Open a socket and begin listening and responding to HTTP requests on







>
>
>
>
>
>
>
>
>







597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
    db_open_repository(g.argv[2]);
  }else{
    db_must_be_within_tree();
  }
  cgi_handle_http_request();
  process_one_web_page();
}

/*
** COMMAND: test-http
** Works like the http command but gives setup permission to all users.
*/
void cmd_test_http(void){
  login_set_capabilities("s");
  cmd_http();
}

/*
** COMMAND: server
**
** Usage: %fossil server ?-P|--port TCPPORT? ?REPOSITORY?
**
** Open a socket and begin listening and responding to HTTP requests on
Changes to src/main.mk.
12
13
14
15
16
17
18

19
20
21
22
23
24
25
XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR)


SRC = \
  $(SRCDIR)/add.c \
  $(SRCDIR)/bag.c \
  $(SRCDIR)/blob.c \

  $(SRCDIR)/cgi.c \
  $(SRCDIR)/checkin.c \
  $(SRCDIR)/checkout.c \
  $(SRCDIR)/clearsign.c \
  $(SRCDIR)/clone.c \
  $(SRCDIR)/comformat.c \
  $(SRCDIR)/construct.c \







>







12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR)


SRC = \
  $(SRCDIR)/add.c \
  $(SRCDIR)/bag.c \
  $(SRCDIR)/blob.c \
  $(SRCDIR)/branch.c \
  $(SRCDIR)/cgi.c \
  $(SRCDIR)/checkin.c \
  $(SRCDIR)/checkout.c \
  $(SRCDIR)/clearsign.c \
  $(SRCDIR)/clone.c \
  $(SRCDIR)/comformat.c \
  $(SRCDIR)/construct.c \
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
  $(SRCDIR)/merge.c \
  $(SRCDIR)/merge3.c \
  $(SRCDIR)/name.c \
  $(SRCDIR)/pivot.c \
  $(SRCDIR)/pqueue.c \
  $(SRCDIR)/printf.c \
  $(SRCDIR)/rebuild.c \

  $(SRCDIR)/schema.c \
  $(SRCDIR)/setup.c \
  $(SRCDIR)/sha1.c \
  $(SRCDIR)/style.c \

  $(SRCDIR)/sync.c \

  $(SRCDIR)/timeline.c \



  $(SRCDIR)/undo.c \
  $(SRCDIR)/update.c \
  $(SRCDIR)/url.c \
  $(SRCDIR)/user.c \
  $(SRCDIR)/verify.c \
  $(SRCDIR)/vfile.c \
  $(SRCDIR)/wiki.c \
  $(SRCDIR)/wikiformat.c \
  $(SRCDIR)/xfer.c \
  $(SRCDIR)/zip.c

TRANS_SRC = \
  add_.c \
  bag_.c \
  blob_.c \

  cgi_.c \
  checkin_.c \
  checkout_.c \
  clearsign_.c \
  clone_.c \
  comformat_.c \
  construct_.c \







>




>

>

>
>
>















>







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
  $(SRCDIR)/merge.c \
  $(SRCDIR)/merge3.c \
  $(SRCDIR)/name.c \
  $(SRCDIR)/pivot.c \
  $(SRCDIR)/pqueue.c \
  $(SRCDIR)/printf.c \
  $(SRCDIR)/rebuild.c \
  $(SRCDIR)/rss.c \
  $(SRCDIR)/schema.c \
  $(SRCDIR)/setup.c \
  $(SRCDIR)/sha1.c \
  $(SRCDIR)/style.c \
  $(SRCDIR)/subscript.c \
  $(SRCDIR)/sync.c \
  $(SRCDIR)/tag.c \
  $(SRCDIR)/timeline.c \
  $(SRCDIR)/tkt.c \
  $(SRCDIR)/tktconfig.c \
  $(SRCDIR)/tktsetup.c \
  $(SRCDIR)/undo.c \
  $(SRCDIR)/update.c \
  $(SRCDIR)/url.c \
  $(SRCDIR)/user.c \
  $(SRCDIR)/verify.c \
  $(SRCDIR)/vfile.c \
  $(SRCDIR)/wiki.c \
  $(SRCDIR)/wikiformat.c \
  $(SRCDIR)/xfer.c \
  $(SRCDIR)/zip.c

TRANS_SRC = \
  add_.c \
  bag_.c \
  blob_.c \
  branch_.c \
  cgi_.c \
  checkin_.c \
  checkout_.c \
  clearsign_.c \
  clone_.c \
  comformat_.c \
  construct_.c \
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
  merge_.c \
  merge3_.c \
  name_.c \
  pivot_.c \
  pqueue_.c \
  printf_.c \
  rebuild_.c \

  schema_.c \
  setup_.c \
  sha1_.c \
  style_.c \

  sync_.c \

  timeline_.c \



  undo_.c \
  update_.c \
  url_.c \
  user_.c \
  verify_.c \
  vfile_.c \
  wiki_.c \
  wikiformat_.c \
  xfer_.c \
  zip_.c

OBJ = \
  add.o \
  bag.o \
  blob.o \

  cgi.o \
  checkin.o \
  checkout.o \
  clearsign.o \
  clone.o \
  comformat.o \
  construct.o \







>




>

>

>
>
>















>







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
  merge_.c \
  merge3_.c \
  name_.c \
  pivot_.c \
  pqueue_.c \
  printf_.c \
  rebuild_.c \
  rss_.c \
  schema_.c \
  setup_.c \
  sha1_.c \
  style_.c \
  subscript_.c \
  sync_.c \
  tag_.c \
  timeline_.c \
  tkt_.c \
  tktconfig_.c \
  tktsetup_.c \
  undo_.c \
  update_.c \
  url_.c \
  user_.c \
  verify_.c \
  vfile_.c \
  wiki_.c \
  wikiformat_.c \
  xfer_.c \
  zip_.c

OBJ = \
  add.o \
  bag.o \
  blob.o \
  branch.o \
  cgi.o \
  checkin.o \
  checkout.o \
  clearsign.o \
  clone.o \
  comformat.o \
  construct.o \
141
142
143
144
145
146
147

148
149
150
151

152

153



154
155
156
157
158
159
160
  merge.o \
  merge3.o \
  name.o \
  pivot.o \
  pqueue.o \
  printf.o \
  rebuild.o \

  schema.o \
  setup.o \
  sha1.o \
  style.o \

  sync.o \

  timeline.o \



  undo.o \
  update.o \
  url.o \
  user.o \
  verify.o \
  vfile.o \
  wiki.o \







>




>

>

>
>
>







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
  merge.o \
  merge3.o \
  name.o \
  pivot.o \
  pqueue.o \
  printf.o \
  rebuild.o \
  rss.o \
  schema.o \
  setup.o \
  sha1.o \
  style.o \
  subscript.o \
  sync.o \
  tag.o \
  timeline.o \
  tkt.o \
  tktconfig.o \
  tktsetup.o \
  undo.o \
  update.o \
  url.o \
  user.o \
  verify.o \
  vfile.o \
  wiki.o \
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
556
557
558
559
560
561
562










563
564
565
566
567
568
569
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
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

# WARNING. DANGER. Running the testsuite modifies the repository the
# build is done from, i.e. the checkout belongs to. Do not sync/push
# the repository after running the tests.
test:	$(APPNAME)
	$(TCLSH) test/tester.tcl $(APPNAME)

VERSION.h:	$(SRCDIR)/../manifest.uuid
	awk '{ printf "#define MANIFEST_UUID \"%s\"\n", $$1}'  $(SRCDIR)/../manifest.uuid >VERSION.h



$(APPNAME):	headers $(OBJ) sqlite3.o
	$(TCC) -o $(APPNAME) $(OBJ) sqlite3.o $(LIB)

clean:	
	rm -f *.o *_.c $(APPNAME) VERSION.h
	rm -f translate makeheaders mkindex page_index.h headers
	rm -f add.h bag.h blob.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h construct.h content.h db.h delta.h deltacmd.h descendents.h diff.h diffcmd.h encode.h file.h http.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 schema.h setup.h sha1.h style.h sync.h timeline.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h xfer.h zip.h

headers:	makeheaders mkindex $(TRANS_SRC) ./VERSION.h
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	./mkindex $(TRANS_SRC) >page_index.h
	touch headers

add_.c:	$(SRCDIR)/add.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/add.c | sed -f $(SRCDIR)/VERSION >add_.c

add.o:	add_.c add.h  $(SRCDIR)/config.h
	$(XTCC) -o add.o -c add_.c

add.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

bag_.c:	$(SRCDIR)/bag.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/bag.c | sed -f $(SRCDIR)/VERSION >bag_.c

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

bag.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

blob_.c:	$(SRCDIR)/blob.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/blob.c | sed -f $(SRCDIR)/VERSION >blob_.c

blob.o:	blob_.c blob.h  $(SRCDIR)/config.h
	$(XTCC) -o blob.o -c blob_.c

blob.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h










	touch headers

cgi_.c:	$(SRCDIR)/cgi.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/cgi.c | sed -f $(SRCDIR)/VERSION >cgi_.c

cgi.o:	cgi_.c cgi.h  $(SRCDIR)/config.h
	$(XTCC) -o cgi.o -c cgi_.c

cgi.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

checkin_.c:	$(SRCDIR)/checkin.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/checkin.c | sed -f $(SRCDIR)/VERSION >checkin_.c

checkin.o:	checkin_.c checkin.h  $(SRCDIR)/config.h
	$(XTCC) -o checkin.o -c checkin_.c

checkin.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

checkout_.c:	$(SRCDIR)/checkout.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/checkout.c | sed -f $(SRCDIR)/VERSION >checkout_.c

checkout.o:	checkout_.c checkout.h  $(SRCDIR)/config.h
	$(XTCC) -o checkout.o -c checkout_.c

checkout.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

clearsign_.c:	$(SRCDIR)/clearsign.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/clearsign.c | sed -f $(SRCDIR)/VERSION >clearsign_.c

clearsign.o:	clearsign_.c clearsign.h  $(SRCDIR)/config.h
	$(XTCC) -o clearsign.o -c clearsign_.c

clearsign.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

clone_.c:	$(SRCDIR)/clone.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/clone.c | sed -f $(SRCDIR)/VERSION >clone_.c

clone.o:	clone_.c clone.h  $(SRCDIR)/config.h
	$(XTCC) -o clone.o -c clone_.c

clone.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

comformat_.c:	$(SRCDIR)/comformat.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/comformat.c | sed -f $(SRCDIR)/VERSION >comformat_.c

comformat.o:	comformat_.c comformat.h  $(SRCDIR)/config.h
	$(XTCC) -o comformat.o -c comformat_.c

comformat.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

construct_.c:	$(SRCDIR)/construct.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/construct.c | sed -f $(SRCDIR)/VERSION >construct_.c

construct.o:	construct_.c construct.h  $(SRCDIR)/config.h
	$(XTCC) -o construct.o -c construct_.c

construct.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

content_.c:	$(SRCDIR)/content.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/content.c | sed -f $(SRCDIR)/VERSION >content_.c

content.o:	content_.c content.h  $(SRCDIR)/config.h
	$(XTCC) -o content.o -c content_.c

content.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

db_.c:	$(SRCDIR)/db.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/db.c | sed -f $(SRCDIR)/VERSION >db_.c

db.o:	db_.c db.h  $(SRCDIR)/config.h
	$(XTCC) -o db.o -c db_.c

db.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

delta_.c:	$(SRCDIR)/delta.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/delta.c | sed -f $(SRCDIR)/VERSION >delta_.c

delta.o:	delta_.c delta.h  $(SRCDIR)/config.h
	$(XTCC) -o delta.o -c delta_.c

delta.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

deltacmd_.c:	$(SRCDIR)/deltacmd.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/deltacmd.c | sed -f $(SRCDIR)/VERSION >deltacmd_.c

deltacmd.o:	deltacmd_.c deltacmd.h  $(SRCDIR)/config.h
	$(XTCC) -o deltacmd.o -c deltacmd_.c

deltacmd.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

descendents_.c:	$(SRCDIR)/descendents.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/descendents.c | sed -f $(SRCDIR)/VERSION >descendents_.c

descendents.o:	descendents_.c descendents.h  $(SRCDIR)/config.h
	$(XTCC) -o descendents.o -c descendents_.c

descendents.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

diff_.c:	$(SRCDIR)/diff.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/diff.c | sed -f $(SRCDIR)/VERSION >diff_.c

diff.o:	diff_.c diff.h  $(SRCDIR)/config.h
	$(XTCC) -o diff.o -c diff_.c

diff.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

diffcmd_.c:	$(SRCDIR)/diffcmd.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/diffcmd.c | sed -f $(SRCDIR)/VERSION >diffcmd_.c

diffcmd.o:	diffcmd_.c diffcmd.h  $(SRCDIR)/config.h
	$(XTCC) -o diffcmd.o -c diffcmd_.c

diffcmd.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

encode_.c:	$(SRCDIR)/encode.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/encode.c | sed -f $(SRCDIR)/VERSION >encode_.c

encode.o:	encode_.c encode.h  $(SRCDIR)/config.h
	$(XTCC) -o encode.o -c encode_.c

encode.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

file_.c:	$(SRCDIR)/file.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/file.c | sed -f $(SRCDIR)/VERSION >file_.c

file.o:	file_.c file.h  $(SRCDIR)/config.h
	$(XTCC) -o file.o -c file_.c

file.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

http_.c:	$(SRCDIR)/http.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/http.c | sed -f $(SRCDIR)/VERSION >http_.c

http.o:	http_.c http.h  $(SRCDIR)/config.h
	$(XTCC) -o http.o -c http_.c

http.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

info_.c:	$(SRCDIR)/info.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/info.c | sed -f $(SRCDIR)/VERSION >info_.c

info.o:	info_.c info.h  $(SRCDIR)/config.h
	$(XTCC) -o info.o -c info_.c

info.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

login_.c:	$(SRCDIR)/login.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/login.c | sed -f $(SRCDIR)/VERSION >login_.c

login.o:	login_.c login.h  $(SRCDIR)/config.h
	$(XTCC) -o login.o -c login_.c

login.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

main_.c:	$(SRCDIR)/main.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/main.c | sed -f $(SRCDIR)/VERSION >main_.c

main.o:	main_.c main.h page_index.h $(SRCDIR)/config.h
	$(XTCC) -o main.o -c main_.c

main.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

manifest_.c:	$(SRCDIR)/manifest.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/manifest.c | sed -f $(SRCDIR)/VERSION >manifest_.c

manifest.o:	manifest_.c manifest.h  $(SRCDIR)/config.h
	$(XTCC) -o manifest.o -c manifest_.c

manifest.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

md5_.c:	$(SRCDIR)/md5.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/md5.c | sed -f $(SRCDIR)/VERSION >md5_.c

md5.o:	md5_.c md5.h  $(SRCDIR)/config.h
	$(XTCC) -o md5.o -c md5_.c

md5.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

merge_.c:	$(SRCDIR)/merge.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/merge.c | sed -f $(SRCDIR)/VERSION >merge_.c

merge.o:	merge_.c merge.h  $(SRCDIR)/config.h
	$(XTCC) -o merge.o -c merge_.c

merge.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

merge3_.c:	$(SRCDIR)/merge3.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/merge3.c | sed -f $(SRCDIR)/VERSION >merge3_.c

merge3.o:	merge3_.c merge3.h  $(SRCDIR)/config.h
	$(XTCC) -o merge3.o -c merge3_.c

merge3.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

name_.c:	$(SRCDIR)/name.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/name.c | sed -f $(SRCDIR)/VERSION >name_.c

name.o:	name_.c name.h  $(SRCDIR)/config.h
	$(XTCC) -o name.o -c name_.c

name.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

pivot_.c:	$(SRCDIR)/pivot.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/pivot.c | sed -f $(SRCDIR)/VERSION >pivot_.c

pivot.o:	pivot_.c pivot.h  $(SRCDIR)/config.h
	$(XTCC) -o pivot.o -c pivot_.c

pivot.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

pqueue_.c:	$(SRCDIR)/pqueue.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/pqueue.c | sed -f $(SRCDIR)/VERSION >pqueue_.c

pqueue.o:	pqueue_.c pqueue.h  $(SRCDIR)/config.h
	$(XTCC) -o pqueue.o -c pqueue_.c

pqueue.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

printf_.c:	$(SRCDIR)/printf.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/printf.c | sed -f $(SRCDIR)/VERSION >printf_.c

printf.o:	printf_.c printf.h  $(SRCDIR)/config.h
	$(XTCC) -o printf.o -c printf_.c

printf.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

rebuild_.c:	$(SRCDIR)/rebuild.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/rebuild.c | sed -f $(SRCDIR)/VERSION >rebuild_.c

rebuild.o:	rebuild_.c rebuild.h  $(SRCDIR)/config.h
	$(XTCC) -o rebuild.o -c rebuild_.c

rebuild.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h










	touch headers

schema_.c:	$(SRCDIR)/schema.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/schema.c | sed -f $(SRCDIR)/VERSION >schema_.c

schema.o:	schema_.c schema.h  $(SRCDIR)/config.h
	$(XTCC) -o schema.o -c schema_.c

schema.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

setup_.c:	$(SRCDIR)/setup.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/setup.c | sed -f $(SRCDIR)/VERSION >setup_.c

setup.o:	setup_.c setup.h  $(SRCDIR)/config.h
	$(XTCC) -o setup.o -c setup_.c

setup.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

sha1_.c:	$(SRCDIR)/sha1.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/sha1.c | sed -f $(SRCDIR)/VERSION >sha1_.c

sha1.o:	sha1_.c sha1.h  $(SRCDIR)/config.h
	$(XTCC) -o sha1.o -c sha1_.c

sha1.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

style_.c:	$(SRCDIR)/style.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/style.c | sed -f $(SRCDIR)/VERSION >style_.c

style.o:	style_.c style.h  $(SRCDIR)/config.h
	$(XTCC) -o style.o -c style_.c

style.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h










	touch headers

sync_.c:	$(SRCDIR)/sync.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/sync.c | sed -f $(SRCDIR)/VERSION >sync_.c

sync.o:	sync_.c sync.h  $(SRCDIR)/config.h
	$(XTCC) -o sync.o -c sync_.c

sync.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h










	touch headers

timeline_.c:	$(SRCDIR)/timeline.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/timeline.c | sed -f $(SRCDIR)/VERSION >timeline_.c

timeline.o:	timeline_.c timeline.h  $(SRCDIR)/config.h
	$(XTCC) -o timeline.o -c timeline_.c

timeline.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h






























	touch headers

undo_.c:	$(SRCDIR)/undo.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/undo.c | sed -f $(SRCDIR)/VERSION >undo_.c

undo.o:	undo_.c undo.h  $(SRCDIR)/config.h
	$(XTCC) -o undo.o -c undo_.c

undo.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

update_.c:	$(SRCDIR)/update.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/update.c | sed -f $(SRCDIR)/VERSION >update_.c

update.o:	update_.c update.h  $(SRCDIR)/config.h
	$(XTCC) -o update.o -c update_.c

update.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

url_.c:	$(SRCDIR)/url.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/url.c | sed -f $(SRCDIR)/VERSION >url_.c

url.o:	url_.c url.h  $(SRCDIR)/config.h
	$(XTCC) -o url.o -c url_.c

url.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

user_.c:	$(SRCDIR)/user.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/user.c | sed -f $(SRCDIR)/VERSION >user_.c

user.o:	user_.c user.h  $(SRCDIR)/config.h
	$(XTCC) -o user.o -c user_.c

user.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

verify_.c:	$(SRCDIR)/verify.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/verify.c | sed -f $(SRCDIR)/VERSION >verify_.c

verify.o:	verify_.c verify.h  $(SRCDIR)/config.h
	$(XTCC) -o verify.o -c verify_.c

verify.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

vfile_.c:	$(SRCDIR)/vfile.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/vfile.c | sed -f $(SRCDIR)/VERSION >vfile_.c

vfile.o:	vfile_.c vfile.h  $(SRCDIR)/config.h
	$(XTCC) -o vfile.o -c vfile_.c

vfile.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

wiki_.c:	$(SRCDIR)/wiki.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/wiki.c | sed -f $(SRCDIR)/VERSION >wiki_.c

wiki.o:	wiki_.c wiki.h  $(SRCDIR)/config.h
	$(XTCC) -o wiki.o -c wiki_.c

wiki.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

wikiformat_.c:	$(SRCDIR)/wikiformat.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/wikiformat.c | sed -f $(SRCDIR)/VERSION >wikiformat_.c

wikiformat.o:	wikiformat_.c wikiformat.h  $(SRCDIR)/config.h
	$(XTCC) -o wikiformat.o -c wikiformat_.c

wikiformat.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

xfer_.c:	$(SRCDIR)/xfer.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/xfer.c | sed -f $(SRCDIR)/VERSION >xfer_.c

xfer.o:	xfer_.c xfer.h  $(SRCDIR)/config.h
	$(XTCC) -o xfer.o -c xfer_.c

xfer.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

zip_.c:	$(SRCDIR)/zip.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/zip.c | sed -f $(SRCDIR)/VERSION >zip_.c

zip.o:	zip_.c zip.h  $(SRCDIR)/config.h
	$(XTCC) -o zip.o -c zip_.c

zip.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h sync_.c:sync.h timeline_.c:timeline.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

sqlite3.o:	$(SRCDIR)/sqlite3.c
	$(XTCC) -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_PRIVATE= -DTHREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -c $(SRCDIR)/sqlite3.c -o sqlite3.o








|

>
>







|


|










|









|









|
>
>
>
>
>
>
>
>
>
>









|









|









|









|









|









|









|









|









|









|









|









|









|









|









|









|









|









|









|









|









|









|









|









|









|









|









|









|









|
>
>
>
>
>
>
>
>
>
>









|









|









|









|
>
>
>
>
>
>
>
>
>
>









|
>
>
>
>
>
>
>
>
>
>









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









|









|









|









|









|









|









|









|









|









|



|

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
556
557
558
559
560
561
562
563
564
565
566
567
568
569
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
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
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780

# WARNING. DANGER. Running the testsuite modifies the repository the
# build is done from, i.e. the checkout belongs to. Do not sync/push
# the repository after running the tests.
test:	$(APPNAME)
	$(TCLSH) test/tester.tcl $(APPNAME)

VERSION.h:	$(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest
	awk '{ printf "#define MANIFEST_UUID \"%s\"\n", $$1}'  $(SRCDIR)/../manifest.uuid >VERSION.h
	awk '{ printf "#define MANIFEST_VERSION \"[%.10s]\"\n", $$1}'  $(SRCDIR)/../manifest.uuid >>VERSION.h
	awk '$$1=="D"{printf "#define MANIFEST_DATE \"%s %s\"\n", substr($$2,1,10),substr($$2,12)}'  $(SRCDIR)/../manifest >>VERSION.h

$(APPNAME):	headers $(OBJ) sqlite3.o
	$(TCC) -o $(APPNAME) $(OBJ) sqlite3.o $(LIB)

clean:	
	rm -f *.o *_.c $(APPNAME) VERSION.h
	rm -f translate makeheaders mkindex page_index.h headers
	rm -f add.h bag.h blob.h branch.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h construct.h content.h db.h delta.h deltacmd.h descendents.h diff.h diffcmd.h encode.h file.h http.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 rss.h schema.h setup.h sha1.h style.h subscript.h sync.h tag.h timeline.h tkt.h tktconfig.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h xfer.h zip.h

headers:	makeheaders mkindex $(TRANS_SRC) ./VERSION.h
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	./mkindex $(TRANS_SRC) >page_index.h
	touch headers

add_.c:	$(SRCDIR)/add.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/add.c | sed -f $(SRCDIR)/VERSION >add_.c

add.o:	add_.c add.h  $(SRCDIR)/config.h
	$(XTCC) -o add.o -c add_.c

add.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

bag_.c:	$(SRCDIR)/bag.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/bag.c | sed -f $(SRCDIR)/VERSION >bag_.c

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

bag.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

blob_.c:	$(SRCDIR)/blob.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/blob.c | sed -f $(SRCDIR)/VERSION >blob_.c

blob.o:	blob_.c blob.h  $(SRCDIR)/config.h
	$(XTCC) -o blob.o -c blob_.c

blob.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

branch_.c:	$(SRCDIR)/branch.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/branch.c | sed -f $(SRCDIR)/VERSION >branch_.c

branch.o:	branch_.c branch.h  $(SRCDIR)/config.h
	$(XTCC) -o branch.o -c branch_.c

branch.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

cgi_.c:	$(SRCDIR)/cgi.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/cgi.c | sed -f $(SRCDIR)/VERSION >cgi_.c

cgi.o:	cgi_.c cgi.h  $(SRCDIR)/config.h
	$(XTCC) -o cgi.o -c cgi_.c

cgi.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

checkin_.c:	$(SRCDIR)/checkin.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/checkin.c | sed -f $(SRCDIR)/VERSION >checkin_.c

checkin.o:	checkin_.c checkin.h  $(SRCDIR)/config.h
	$(XTCC) -o checkin.o -c checkin_.c

checkin.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

checkout_.c:	$(SRCDIR)/checkout.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/checkout.c | sed -f $(SRCDIR)/VERSION >checkout_.c

checkout.o:	checkout_.c checkout.h  $(SRCDIR)/config.h
	$(XTCC) -o checkout.o -c checkout_.c

checkout.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

clearsign_.c:	$(SRCDIR)/clearsign.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/clearsign.c | sed -f $(SRCDIR)/VERSION >clearsign_.c

clearsign.o:	clearsign_.c clearsign.h  $(SRCDIR)/config.h
	$(XTCC) -o clearsign.o -c clearsign_.c

clearsign.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

clone_.c:	$(SRCDIR)/clone.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/clone.c | sed -f $(SRCDIR)/VERSION >clone_.c

clone.o:	clone_.c clone.h  $(SRCDIR)/config.h
	$(XTCC) -o clone.o -c clone_.c

clone.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

comformat_.c:	$(SRCDIR)/comformat.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/comformat.c | sed -f $(SRCDIR)/VERSION >comformat_.c

comformat.o:	comformat_.c comformat.h  $(SRCDIR)/config.h
	$(XTCC) -o comformat.o -c comformat_.c

comformat.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

construct_.c:	$(SRCDIR)/construct.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/construct.c | sed -f $(SRCDIR)/VERSION >construct_.c

construct.o:	construct_.c construct.h  $(SRCDIR)/config.h
	$(XTCC) -o construct.o -c construct_.c

construct.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

content_.c:	$(SRCDIR)/content.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/content.c | sed -f $(SRCDIR)/VERSION >content_.c

content.o:	content_.c content.h  $(SRCDIR)/config.h
	$(XTCC) -o content.o -c content_.c

content.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

db_.c:	$(SRCDIR)/db.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/db.c | sed -f $(SRCDIR)/VERSION >db_.c

db.o:	db_.c db.h  $(SRCDIR)/config.h
	$(XTCC) -o db.o -c db_.c

db.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

delta_.c:	$(SRCDIR)/delta.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/delta.c | sed -f $(SRCDIR)/VERSION >delta_.c

delta.o:	delta_.c delta.h  $(SRCDIR)/config.h
	$(XTCC) -o delta.o -c delta_.c

delta.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

deltacmd_.c:	$(SRCDIR)/deltacmd.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/deltacmd.c | sed -f $(SRCDIR)/VERSION >deltacmd_.c

deltacmd.o:	deltacmd_.c deltacmd.h  $(SRCDIR)/config.h
	$(XTCC) -o deltacmd.o -c deltacmd_.c

deltacmd.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

descendents_.c:	$(SRCDIR)/descendents.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/descendents.c | sed -f $(SRCDIR)/VERSION >descendents_.c

descendents.o:	descendents_.c descendents.h  $(SRCDIR)/config.h
	$(XTCC) -o descendents.o -c descendents_.c

descendents.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

diff_.c:	$(SRCDIR)/diff.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/diff.c | sed -f $(SRCDIR)/VERSION >diff_.c

diff.o:	diff_.c diff.h  $(SRCDIR)/config.h
	$(XTCC) -o diff.o -c diff_.c

diff.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

diffcmd_.c:	$(SRCDIR)/diffcmd.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/diffcmd.c | sed -f $(SRCDIR)/VERSION >diffcmd_.c

diffcmd.o:	diffcmd_.c diffcmd.h  $(SRCDIR)/config.h
	$(XTCC) -o diffcmd.o -c diffcmd_.c

diffcmd.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

encode_.c:	$(SRCDIR)/encode.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/encode.c | sed -f $(SRCDIR)/VERSION >encode_.c

encode.o:	encode_.c encode.h  $(SRCDIR)/config.h
	$(XTCC) -o encode.o -c encode_.c

encode.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

file_.c:	$(SRCDIR)/file.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/file.c | sed -f $(SRCDIR)/VERSION >file_.c

file.o:	file_.c file.h  $(SRCDIR)/config.h
	$(XTCC) -o file.o -c file_.c

file.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

http_.c:	$(SRCDIR)/http.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/http.c | sed -f $(SRCDIR)/VERSION >http_.c

http.o:	http_.c http.h  $(SRCDIR)/config.h
	$(XTCC) -o http.o -c http_.c

http.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

info_.c:	$(SRCDIR)/info.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/info.c | sed -f $(SRCDIR)/VERSION >info_.c

info.o:	info_.c info.h  $(SRCDIR)/config.h
	$(XTCC) -o info.o -c info_.c

info.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

login_.c:	$(SRCDIR)/login.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/login.c | sed -f $(SRCDIR)/VERSION >login_.c

login.o:	login_.c login.h  $(SRCDIR)/config.h
	$(XTCC) -o login.o -c login_.c

login.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

main_.c:	$(SRCDIR)/main.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/main.c | sed -f $(SRCDIR)/VERSION >main_.c

main.o:	main_.c main.h page_index.h $(SRCDIR)/config.h
	$(XTCC) -o main.o -c main_.c

main.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

manifest_.c:	$(SRCDIR)/manifest.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/manifest.c | sed -f $(SRCDIR)/VERSION >manifest_.c

manifest.o:	manifest_.c manifest.h  $(SRCDIR)/config.h
	$(XTCC) -o manifest.o -c manifest_.c

manifest.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

md5_.c:	$(SRCDIR)/md5.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/md5.c | sed -f $(SRCDIR)/VERSION >md5_.c

md5.o:	md5_.c md5.h  $(SRCDIR)/config.h
	$(XTCC) -o md5.o -c md5_.c

md5.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

merge_.c:	$(SRCDIR)/merge.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/merge.c | sed -f $(SRCDIR)/VERSION >merge_.c

merge.o:	merge_.c merge.h  $(SRCDIR)/config.h
	$(XTCC) -o merge.o -c merge_.c

merge.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

merge3_.c:	$(SRCDIR)/merge3.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/merge3.c | sed -f $(SRCDIR)/VERSION >merge3_.c

merge3.o:	merge3_.c merge3.h  $(SRCDIR)/config.h
	$(XTCC) -o merge3.o -c merge3_.c

merge3.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

name_.c:	$(SRCDIR)/name.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/name.c | sed -f $(SRCDIR)/VERSION >name_.c

name.o:	name_.c name.h  $(SRCDIR)/config.h
	$(XTCC) -o name.o -c name_.c

name.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

pivot_.c:	$(SRCDIR)/pivot.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/pivot.c | sed -f $(SRCDIR)/VERSION >pivot_.c

pivot.o:	pivot_.c pivot.h  $(SRCDIR)/config.h
	$(XTCC) -o pivot.o -c pivot_.c

pivot.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

pqueue_.c:	$(SRCDIR)/pqueue.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/pqueue.c | sed -f $(SRCDIR)/VERSION >pqueue_.c

pqueue.o:	pqueue_.c pqueue.h  $(SRCDIR)/config.h
	$(XTCC) -o pqueue.o -c pqueue_.c

pqueue.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

printf_.c:	$(SRCDIR)/printf.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/printf.c | sed -f $(SRCDIR)/VERSION >printf_.c

printf.o:	printf_.c printf.h  $(SRCDIR)/config.h
	$(XTCC) -o printf.o -c printf_.c

printf.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

rebuild_.c:	$(SRCDIR)/rebuild.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/rebuild.c | sed -f $(SRCDIR)/VERSION >rebuild_.c

rebuild.o:	rebuild_.c rebuild.h  $(SRCDIR)/config.h
	$(XTCC) -o rebuild.o -c rebuild_.c

rebuild.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

rss_.c:	$(SRCDIR)/rss.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/rss.c | sed -f $(SRCDIR)/VERSION >rss_.c

rss.o:	rss_.c rss.h  $(SRCDIR)/config.h
	$(XTCC) -o rss.o -c rss_.c

rss.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

schema_.c:	$(SRCDIR)/schema.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/schema.c | sed -f $(SRCDIR)/VERSION >schema_.c

schema.o:	schema_.c schema.h  $(SRCDIR)/config.h
	$(XTCC) -o schema.o -c schema_.c

schema.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

setup_.c:	$(SRCDIR)/setup.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/setup.c | sed -f $(SRCDIR)/VERSION >setup_.c

setup.o:	setup_.c setup.h  $(SRCDIR)/config.h
	$(XTCC) -o setup.o -c setup_.c

setup.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

sha1_.c:	$(SRCDIR)/sha1.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/sha1.c | sed -f $(SRCDIR)/VERSION >sha1_.c

sha1.o:	sha1_.c sha1.h  $(SRCDIR)/config.h
	$(XTCC) -o sha1.o -c sha1_.c

sha1.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

style_.c:	$(SRCDIR)/style.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/style.c | sed -f $(SRCDIR)/VERSION >style_.c

style.o:	style_.c style.h  $(SRCDIR)/config.h
	$(XTCC) -o style.o -c style_.c

style.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

subscript_.c:	$(SRCDIR)/subscript.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/subscript.c | sed -f $(SRCDIR)/VERSION >subscript_.c

subscript.o:	subscript_.c subscript.h  $(SRCDIR)/config.h
	$(XTCC) -o subscript.o -c subscript_.c

subscript.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

sync_.c:	$(SRCDIR)/sync.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/sync.c | sed -f $(SRCDIR)/VERSION >sync_.c

sync.o:	sync_.c sync.h  $(SRCDIR)/config.h
	$(XTCC) -o sync.o -c sync_.c

sync.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

tag_.c:	$(SRCDIR)/tag.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/tag.c | sed -f $(SRCDIR)/VERSION >tag_.c

tag.o:	tag_.c tag.h  $(SRCDIR)/config.h
	$(XTCC) -o tag.o -c tag_.c

tag.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

timeline_.c:	$(SRCDIR)/timeline.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/timeline.c | sed -f $(SRCDIR)/VERSION >timeline_.c

timeline.o:	timeline_.c timeline.h  $(SRCDIR)/config.h
	$(XTCC) -o timeline.o -c timeline_.c

timeline.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

tkt_.c:	$(SRCDIR)/tkt.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/tkt.c | sed -f $(SRCDIR)/VERSION >tkt_.c

tkt.o:	tkt_.c tkt.h  $(SRCDIR)/config.h
	$(XTCC) -o tkt.o -c tkt_.c

tkt.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

tktconfig_.c:	$(SRCDIR)/tktconfig.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/tktconfig.c | sed -f $(SRCDIR)/VERSION >tktconfig_.c

tktconfig.o:	tktconfig_.c tktconfig.h  $(SRCDIR)/config.h
	$(XTCC) -o tktconfig.o -c tktconfig_.c

tktconfig.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

tktsetup_.c:	$(SRCDIR)/tktsetup.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/tktsetup.c | sed -f $(SRCDIR)/VERSION >tktsetup_.c

tktsetup.o:	tktsetup_.c tktsetup.h  $(SRCDIR)/config.h
	$(XTCC) -o tktsetup.o -c tktsetup_.c

tktsetup.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

undo_.c:	$(SRCDIR)/undo.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/undo.c | sed -f $(SRCDIR)/VERSION >undo_.c

undo.o:	undo_.c undo.h  $(SRCDIR)/config.h
	$(XTCC) -o undo.o -c undo_.c

undo.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

update_.c:	$(SRCDIR)/update.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/update.c | sed -f $(SRCDIR)/VERSION >update_.c

update.o:	update_.c update.h  $(SRCDIR)/config.h
	$(XTCC) -o update.o -c update_.c

update.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

url_.c:	$(SRCDIR)/url.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/url.c | sed -f $(SRCDIR)/VERSION >url_.c

url.o:	url_.c url.h  $(SRCDIR)/config.h
	$(XTCC) -o url.o -c url_.c

url.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

user_.c:	$(SRCDIR)/user.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/user.c | sed -f $(SRCDIR)/VERSION >user_.c

user.o:	user_.c user.h  $(SRCDIR)/config.h
	$(XTCC) -o user.o -c user_.c

user.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

verify_.c:	$(SRCDIR)/verify.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/verify.c | sed -f $(SRCDIR)/VERSION >verify_.c

verify.o:	verify_.c verify.h  $(SRCDIR)/config.h
	$(XTCC) -o verify.o -c verify_.c

verify.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

vfile_.c:	$(SRCDIR)/vfile.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/vfile.c | sed -f $(SRCDIR)/VERSION >vfile_.c

vfile.o:	vfile_.c vfile.h  $(SRCDIR)/config.h
	$(XTCC) -o vfile.o -c vfile_.c

vfile.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

wiki_.c:	$(SRCDIR)/wiki.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/wiki.c | sed -f $(SRCDIR)/VERSION >wiki_.c

wiki.o:	wiki_.c wiki.h  $(SRCDIR)/config.h
	$(XTCC) -o wiki.o -c wiki_.c

wiki.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

wikiformat_.c:	$(SRCDIR)/wikiformat.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/wikiformat.c | sed -f $(SRCDIR)/VERSION >wikiformat_.c

wikiformat.o:	wikiformat_.c wikiformat.h  $(SRCDIR)/config.h
	$(XTCC) -o wikiformat.o -c wikiformat_.c

wikiformat.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

xfer_.c:	$(SRCDIR)/xfer.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/xfer.c | sed -f $(SRCDIR)/VERSION >xfer_.c

xfer.o:	xfer_.c xfer.h  $(SRCDIR)/config.h
	$(XTCC) -o xfer.o -c xfer_.c

xfer.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

zip_.c:	$(SRCDIR)/zip.c $(SRCDIR)/VERSION translate
	./translate $(SRCDIR)/zip.c | sed -f $(SRCDIR)/VERSION >zip_.c

zip.o:	zip_.c zip.h  $(SRCDIR)/config.h
	$(XTCC) -o zip.o -c zip_.c

zip.h:	makeheaders
	./makeheaders  add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
	touch headers

sqlite3.o:	$(SRCDIR)/sqlite3.c
	$(XTCC) -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_PRIVATE= -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_FTS3=1 -c $(SRCDIR)/sqlite3.c -o sqlite3.o

Changes to src/makemake.tcl.
1
2
3
4
5
6
7
8
9
10
11

12
13
14
15
16
17
18
#!/usr/bin/tclsh
#
# Run this TCL script to generate the "main.mk" makefile.
#

# Basenames of all source files:
#
set src {
  add
  bag
  blob

  cgi
  checkin
  checkout
  clearsign
  clone
  comformat
  construct











>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/tclsh
#
# Run this TCL script to generate the "main.mk" makefile.
#

# Basenames of all source files:
#
set src {
  add
  bag
  blob
  branch
  cgi
  checkin
  checkout
  clearsign
  clone
  comformat
  construct
34
35
36
37
38
39
40

41
42
43
44

45

46



47
48
49
50
51
52
53
  merge
  merge3
  name
  pivot
  pqueue
  printf
  rebuild

  schema
  setup
  sha1
  style

  sync

  timeline



  undo
  update
  url
  user
  verify
  vfile
  wiki







>




>

>

>
>
>







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
  merge
  merge3
  name
  pivot
  pqueue
  printf
  rebuild
  rss
  schema
  setup
  sha1
  style
  subscript
  sync
  tag
  timeline
  tkt
  tktconfig
  tktsetup
  undo
  update
  url
  user
  verify
  vfile
  wiki
109
110
111
112
113
114
115
116
117
118





119
120
121
122
123
124
125

# WARNING. DANGER. Running the testsuite modifies the repository the
# build is done from, i.e. the checkout belongs to. Do not sync/push
# the repository after running the tests.
test:	$(APPNAME)
	$(TCLSH) test/tester.tcl $(APPNAME)

VERSION.h:	$(SRCDIR)/../manifest.uuid
	awk '{ printf "#define MANIFEST_UUID \"%s\"\n", $$1}' \
		$(SRCDIR)/../manifest.uuid >VERSION.h






$(APPNAME):	headers $(OBJ) sqlite3.o
	$(TCC) -o $(APPNAME) $(OBJ) sqlite3.o $(LIB)

clean:	
	rm -f *.o *_.c $(APPNAME) VERSION.h
	rm -f translate makeheaders mkindex page_index.h headers}







|


>
>
>
>
>







116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137

# WARNING. DANGER. Running the testsuite modifies the repository the
# build is done from, i.e. the checkout belongs to. Do not sync/push
# the repository after running the tests.
test:	$(APPNAME)
	$(TCLSH) test/tester.tcl $(APPNAME)

VERSION.h:	$(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest
	awk '{ printf "#define MANIFEST_UUID \"%s\"\n", $$1}' \
		$(SRCDIR)/../manifest.uuid >VERSION.h
	awk '{ printf "#define MANIFEST_VERSION \"[%.10s]\"\n", $$1}' \
		$(SRCDIR)/../manifest.uuid >>VERSION.h
	awk '$$1=="D"{printf "#define MANIFEST_DATE \"%s %s\"\n",\
		substr($$2,1,10),substr($$2,12)}' \
		$(SRCDIR)/../manifest >>VERSION.h

$(APPNAME):	headers $(OBJ) sqlite3.o
	$(TCC) -o $(APPNAME) $(OBJ) sqlite3.o $(LIB)

clean:	
	rm -f *.o *_.c $(APPNAME) VERSION.h
	rm -f translate makeheaders mkindex page_index.h headers}
148
149
150
151
152
153
154
155

156
  puts "$s.h:\tmakeheaders"
  puts "\t./makeheaders $mhargs\n\ttouch headers\n"
}


puts "sqlite3.o:\t\$(SRCDIR)/sqlite3.c"
set opt {-DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_PRIVATE=}
append opt " -DTHREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4"

puts "\t\$(XTCC) $opt -c \$(SRCDIR)/sqlite3.c -o sqlite3.o\n"







|
>

160
161
162
163
164
165
166
167
168
169
  puts "$s.h:\tmakeheaders"
  puts "\t./makeheaders $mhargs\n\ttouch headers\n"
}


puts "sqlite3.o:\t\$(SRCDIR)/sqlite3.c"
set opt {-DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_PRIVATE=}
append opt " -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4"
append opt " -DSQLITE_ENABLE_FTS3=1"
puts "\t\$(XTCC) $opt -c \$(SRCDIR)/sqlite3.c -o sqlite3.o\n"
Changes to src/manifest.c.
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
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to cross link manifests



*/
#include "config.h"
#include "manifest.h"
#include <assert.h>

#if INTERFACE
















/*
** A parsed manifest or cluster.
*/
struct Manifest {
  Blob content;         /* The original content blob */


  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 */



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




















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



  memset(p, 0, sizeof(*p));
}

/*
** Parse a manifest 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
** as string terminators so that blob should not be used again.
**
** Return TRUE if the content really is a manifest.  Return FALSE
** if there are syntax errors.





**
** The pContent is reset.  If TRUE is returned, then pContent will
** be reset when the Manifest object is cleared.  If FALSE is
** returned then the Manifest object is cleared automatically
** and pContent is reset before the return.








*/
int manifest_parse(Manifest *p, Blob *pContent){
  int seenHeader = 0;

  int i;
  Blob line, token, a1, a2, a3;



  memset(p, 0, sizeof(*p));
  memcpy(&p->content, pContent, sizeof(p->content));



  blob_zero(pContent);
  pContent = &p->content;

  blob_zero(&a1);
  blob_zero(&a2);
  md5sum_init();
  while( blob_line(pContent, &line) ){
    char *z = blob_buffer(&line);

    if( z[0]=='-' ){
      if( strncmp(z, "-----BEGIN PGP ", 15)!=0 ){
        goto manifest_syntax_error;
      }
      if( seenHeader ){
        break;
      }
      while( blob_line(pContent, &line)>2 ){}
      if( blob_line(pContent, &line)==0 ) break;
      z = blob_buffer(&line);
    }





    seenHeader = 1;
    if( blob_token(&line, &token)!=1 ) goto manifest_syntax_error;
    if( z[0]=='F' ){








































































































      char *zName, *zUuid;
      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;
      zName = blob_terminate(&a1);
      zUuid = blob_terminate(&a2);
      if( blob_size(&a2)!=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;
      }
      if( p->nFile>=p->nFileAlloc ){
        p->nFileAlloc = p->nFileAlloc*2 + 10;
        p->aFile = realloc(p->aFile, p->nFileAlloc*sizeof(p->aFile[0]) );
        if( p->aFile==0 ) fossil_panic("out of memory");
      }
      i = p->nFile++;
      p->aFile[i].zName = zName;
      p->aFile[i].zUuid = zUuid;
      if( i>0 && strcmp(p->aFile[i-1].zName, zName)>=0 ){
        goto manifest_syntax_error;
      }


    }else if( z[0]=='C' ){









      md5sum_step_text(blob_buffer(&line), blob_size(&line));
      if( p->zComment!=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;
      p->zComment = blob_terminate(&a1);






















      defossilize(p->zComment);

    }else if( z[0]=='D' ){
      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);






    }else if( z[0]=='M' ){
      char *zUuid;
      md5sum_step_text(blob_buffer(&line), blob_size(&line));
      if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
      zUuid = blob_terminate(&a1);
      if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error;
      if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
      if( p->nCChild>=p->nCChildAlloc ){
        p->nCChildAlloc = p->nCChildAlloc*2 + 10;
        p->azCChild = 
           realloc(p->azCChild, p->nCChildAlloc*sizeof(p->azCChild[0]) );
        if( p->azCChild==0 ) fossil_panic("out of memory");
      }
      i = p->nCChild++;
      p->azCChild[i] = zUuid;
      if( i>0 && strcmp(p->azCChild[i-1], zUuid)>=0 ){
        goto manifest_syntax_error;
      }
    }else if( z[0]=='U' ){

      md5sum_step_text(blob_buffer(&line), blob_size(&line));
      if( p->zUser!=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;
      p->zUser = blob_terminate(&a1);

      defossilize(p->zUser);
    }else if( z[0]=='R' ){
      md5sum_step_text(blob_buffer(&line), blob_size(&line));
      if( p->zRepoCksum!=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_size(&a1)!=32 ) goto manifest_syntax_error;
      p->zRepoCksum = blob_terminate(&a1);
      if( !validate16(p->zRepoCksum, 32) ) goto manifest_syntax_error;

    }else if( z[0]=='P' ){
      md5sum_step_text(blob_buffer(&line), blob_size(&line));
      while( blob_token(&line, &a1) ){
        char *zUuid;
        if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error;
        zUuid = blob_terminate(&a1);
        if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
        if( p->nParent>=p->nParentAlloc ){
          p->nParentAlloc = p->nParentAlloc*2 + 5;
          p->azParent = realloc(p->azParent, p->nParentAlloc*sizeof(char*));
          if( p->azParent==0 ) fossil_panic("out of memory");
        }
        i = p->nParent++;
        p->azParent[i] = zUuid;
      }
















































    }else if( z[0]=='Z' ){

























































































      int rc;
      Blob hash;
      if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
      if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
      if( blob_size(&a1)!=32 ) goto manifest_syntax_error;
      if( !validate16(blob_buffer(&a1), 32) ) goto manifest_syntax_error;
      md5sum_finish(&hash);
      rc = blob_compare(&hash, &a1);
      blob_reset(&hash);
      if( rc!=0 ) goto manifest_syntax_error;
    }else{



      goto manifest_syntax_error;

    }
  }
  if( !seenHeader ) goto manifest_syntax_error;




























































  md5sum_init();
  return 1;

manifest_syntax_error:

  md5sum_init();
  manifest_clear(p);
  return 0;
}

/*
** Add a single entry to the mlink table.  Also add the filename to







|
>
>
>






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





>
>

>



>
>
>












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












>
>
>




|
|



|
|
>
>
>
>
>





>
>
>
>
>
>
>
>



>
|

>
>



>
>
>








>











>
>
>
>
>


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



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




>







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
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
556
557
558
559
560
561
562
563
564
565
566
567
568
569
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
649
650
651
652
653
654
655
656
657
658
659
660
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to cross link control files and
** manifests.  The file is named "manifest.c" because it was
** original only used to parse manifests.  Then later clusters
** and control files and wiki pages and tickets were added.
*/
#include "config.h"
#include "manifest.h"
#include <assert.h>

#if INTERFACE
/*
** Types of control files
*/
#define CFTYPE_MANIFEST   1
#define CFTYPE_CLUSTER    2
#define CFTYPE_CONTROL    3
#define CFTYPE_WIKI       4
#define CFTYPE_TICKET     5

/*
** 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 */
  char zUuid[UUID_SIZE+1];  /* Self UUID */
  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 */
  } *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
** as string terminators so that blob should not be used again.
**
** Return TRUE if the content really is a control file of some
** kind.  Return FALSE if there are syntax errors.
**
** This routine is strict about the format of a control file.
** The format must match exactly or else it is rejected.  This
** rule minimizes the risk that a content file will be mistaken
** for a control file simply because they look the same.
**
** The pContent is reset.  If TRUE is returned, then pContent will
** be reset when the Manifest object is cleared.  If FALSE is
** returned then the Manifest object is cleared automatically
** and pContent is reset before the return.
**
** The entire file can be PGP clear-signed.  The signature is ignored.
** The file consists of zero or more cards, one card per line.
** (Except: the content of the W card can extend of multiple lines.)
** Each card is divided into tokens by a single space character.
** The first token is a single upper-case letter which is the card type.
** The card type determines the other parameters to the card.
** Cards must occur in lexicographical order.
*/
int manifest_parse(Manifest *p, Blob *pContent){
  int seenHeader = 0;
  int seenZ = 0;
  int i, lineNo=0;
  Blob line, token, a1, a2, a3;
  Blob selfuuid;
  char cPrevType = 0;

  memset(p, 0, sizeof(*p));
  memcpy(&p->content, pContent, sizeof(p->content));
  sha1sum_blob(&p->content, &selfuuid);
  memcpy(p->zUuid, blob_buffer(&selfuuid), UUID_SIZE);
  p->zUuid[UUID_SIZE] = 0;
  blob_zero(pContent);
  pContent = &p->content;

  blob_zero(&a1);
  blob_zero(&a2);
  md5sum_init();
  while( blob_line(pContent, &line) ){
    char *z = blob_buffer(&line);
    lineNo++;
    if( z[0]=='-' ){
      if( strncmp(z, "-----BEGIN PGP ", 15)!=0 ){
        goto manifest_syntax_error;
      }
      if( seenHeader ){
        break;
      }
      while( blob_line(pContent, &line)>2 ){}
      if( blob_line(pContent, &line)==0 ) break;
      z = blob_buffer(&line);
    }
    if( z[0]<cPrevType ){
      /* Lines of a manifest must occur in lexicographical order */
      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
      ** one C line.  C lines are required for manifests and are
      ** disallowed on all other control files.
      */
      case 'C': {
        md5sum_step_text(blob_buffer(&line), blob_size(&line));
        if( p->zComment!=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;
        p->zComment = blob_terminate(&a1);
        defossilize(p->zComment);
        break;
      }

      /*
      **     D <timestamp>
      **
      ** The timestamp should be ISO 8601.   YYYY-MM-DDtHH:MM:SS
      ** There can be no more than 1 D line.  D lines are required
      ** for all control files except for clusters.
      */
      case 'D': {
        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>
      **
      ** Identifies a file in a manifest.  Multiple F lines are
      ** allowed in a manifest.  F lines are not allowed in any
      ** other control file.  The filename is fossil-encoded.
      */
      case 'F': {
        char *zName, *zUuid;
        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;
        zName = blob_terminate(&a1);
        zUuid = blob_terminate(&a2);
        if( blob_size(&a2)!=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;
        }
        if( p->nFile>=p->nFileAlloc ){
          p->nFileAlloc = p->nFileAlloc*2 + 10;
          p->aFile = realloc(p->aFile, p->nFileAlloc*sizeof(p->aFile[0]) );
          if( p->aFile==0 ) fossil_panic("out of memory");
        }
        i = p->nFile++;
        p->aFile[i].zName = zName;
        p->aFile[i].zUuid = zUuid;
        if( i>0 && strcmp(p->aFile[i-1].zName, zName)>=0 ){
          goto manifest_syntax_error;
        }
        break;
      }

      /*
      **     J '+'?<name> <value>
      **
      ** Specifies a name value pair for ticket.  If the first character
      ** of <name> is "+" then the value is appended to any preexisting
      ** value.
      */
      case 'J': {
        char *zName, *zValue;
        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;
        zName = blob_terminate(&a1);
        zValue = blob_terminate(&a2);
        defossilize(zValue);
        if( p->nField>=p->nFieldAlloc ){
          p->nFieldAlloc = p->nFieldAlloc*2 + 10;
          p->aField = realloc(p->aField,
                               p->nFieldAlloc*sizeof(p->aField[0]) );
          if( p->aField==0 ) fossil_panic("out of memory");
        }
        i = p->nField++;
        p->aField[i].zName = zName;
        p->aField[i].zValue = zValue;
        if( i>0 && strcmp(p->aField[i-1].zName, zName)>=0 ){
          goto manifest_syntax_error;
        }
        break;
      }


      /*
      **    K <uuid>
      **
      ** A K-line gives the UUID for the ticket which this control file
      ** is amending.
      */
      case 'K': {
        char *zUuid;
        md5sum_step_text(blob_buffer(&line), blob_size(&line));
        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
        zUuid = blob_terminate(&a1);
        if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error;
        if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
        if( p->zTicketUuid!=0 ) goto manifest_syntax_error;
        p->zTicketUuid = zUuid;
        break;
      }

      /*
      **     L <wikititle>
      **
      ** The wiki page title is fossil-encoded.  There may be no more than
      ** one L line.
      */
      case 'L': {
        md5sum_step_text(blob_buffer(&line), blob_size(&line));
        if( p->zWikiTitle!=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;
        p->zWikiTitle = blob_terminate(&a1);
        defossilize(p->zWikiTitle);
        if( !wiki_name_is_wellformed(p->zWikiTitle) ){
          goto manifest_syntax_error;
        }
        break;
      }

      /*
      **    M <uuid>
      **
      ** An M-line identifies another artifact by its UUID.  M-lines
      ** occur in clusters only.
      */
      case 'M': {
        char *zUuid;
        md5sum_step_text(blob_buffer(&line), blob_size(&line));
        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
        zUuid = blob_terminate(&a1);
        if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error;
        if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
        if( p->nCChild>=p->nCChildAlloc ){
          p->nCChildAlloc = p->nCChildAlloc*2 + 10;
          p->azCChild = 
             realloc(p->azCChild, p->nCChildAlloc*sizeof(p->azCChild[0]) );
          if( p->azCChild==0 ) fossil_panic("out of memory");
        }
        i = p->nCChild++;
        p->azCChild[i] = zUuid;
        if( i>0 && strcmp(p->azCChild[i-1], zUuid)>=0 ){
          goto manifest_syntax_error;
        }
        break;
      }



      /*
      **     P <uuid> ...

      **
      ** Specify one or more other artifacts where are the parents of
      ** this artifact.  The first parent is the primary parent.  All
      ** others are parents by merge.






      */
      case 'P': {
        md5sum_step_text(blob_buffer(&line), blob_size(&line));
        while( blob_token(&line, &a1) ){
          char *zUuid;
          if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error;
          zUuid = blob_terminate(&a1);
          if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
          if( p->nParent>=p->nParentAlloc ){
            p->nParentAlloc = p->nParentAlloc*2 + 5;
            p->azParent = realloc(p->azParent, p->nParentAlloc*sizeof(char*));
            if( p->azParent==0 ) fossil_panic("out of memory");
          }
          i = p->nParent++;
          p->azParent[i] = zUuid;
        }
        break;
      }

      /*
      **     R <md5sum>
      **
      ** Specify the MD5 checksum of the entire baseline in a
      ** manifest.
      */
      case 'R': {
        md5sum_step_text(blob_buffer(&line), blob_size(&line));
        if( p->zRepoCksum!=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_size(&a1)!=32 ) goto manifest_syntax_error;
        p->zRepoCksum = blob_terminate(&a1);
        if( !validate16(p->zRepoCksum, 32) ) goto manifest_syntax_error;
        break;
      }

      /*
      **    T (+|*|-)<tagname> <uuid> ?<value>?
      **
      ** Create or cancel a tag or property.  The tagname is fossil-encoded.
      ** The first character of the name must be either "+" to create a
      ** singleton tag, "*" to create a propagating tag, or "-" to create
      ** anti-tag that undoes a prior "+" or blocks propagation of of
      ** a "*".
      **
      ** The tag is applied to <uuid>.  If <uuid> is "*" then the tag is
      ** applied to the current manifest.  If <value> is provided then 
      ** the tag is really a property with the given value.
      **
      ** Tags are not allowed in clusters.  Multiple T lines are allowed.
      */
      case 'T': {
        char *zName, *zUuid, *zValue;
        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;
        }
        zName = blob_terminate(&a1);
        zUuid = blob_terminate(&a2);
        if( blob_token(&line, &a3)==0 ){
          zValue = 0;
        }else{
          zValue = blob_terminate(&a3);
          defossilize(zValue);
        }
        if( blob_size(&a2)==UUID_SIZE && validate16(zUuid, UUID_SIZE) ){
          /* A valid uuid */
        }else if( blob_size(&a2)==1 && zUuid[0]=='*' ){
          zUuid = p->zUuid;
        }else{
          goto manifest_syntax_error;
        }
        defossilize(zName);
        if( zName[0]!='-' && zName[0]!='+' && zName[0]!='*' ){
          goto manifest_syntax_error;
        }
        if( validate16(&zName[1], strlen(&zName[1])) ){
          /* Do not allow tags whose names look like UUIDs */
          goto manifest_syntax_error;
        }
        if( p->nTag>=p->nTagAlloc ){
          p->nTagAlloc = p->nTagAlloc*2 + 10;
          p->aTag = realloc(p->aTag, p->nTagAlloc*sizeof(p->aTag[0]) );
          if( p->aTag==0 ) fossil_panic("out of memory");
        }
        i = p->nTag++;
        p->aTag[i].zName = zName;
        p->aTag[i].zUuid = zUuid;
        p->aTag[i].zValue = zValue;
        if( i>0 && strcmp(p->aTag[i-1].zName, zName)>=0 ){
          goto manifest_syntax_error;
        }
        break;
      }

      /*
      **     U <login>
      **
      ** Identify the user who created this control file by their
      ** login.  Only one U line is allowed.  Prohibited in clusters.
      */
      case 'U': {
        md5sum_step_text(blob_buffer(&line), blob_size(&line));
        if( p->zUser!=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;
        p->zUser = blob_terminate(&a1);
        defossilize(p->zUser);
        break;
      }

      /*
      **     W <size>
      **
      ** The next <size> bytes of the file contain the text of the wiki
      ** page.  There is always an extra \n before the start of the next
      ** record.
      */
      case 'W': {
        int size;
        Blob wiki;
        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_is_int(&a1, &size) ) goto manifest_syntax_error;
        if( size<0 ) goto manifest_syntax_error;
        if( p->zWiki!=0 ) goto manifest_syntax_error;
        blob_zero(&wiki);
        if( blob_extract(pContent, size+1, &wiki)!=size+1 ){
          goto manifest_syntax_error;
        }
        p->zWiki = blob_buffer(&wiki);
        md5sum_step_text(p->zWiki, size+1);
        if( p->zWiki[size]!='\n' ) goto manifest_syntax_error;
        p->zWiki[size] = 0;
        break;
      }


      /*
      **     Z <md5sum>
      **
      ** MD5 checksum on this control file.  The checksum is over all
      ** lines (other than PGP-signature lines) prior to the current
      ** line.  This must be the last record.
      **
      ** This card is required for all control file types except for
      ** Manifest.  It is not required for manifest only for historical
      ** compatibility reasons.
      */
      case 'Z': {
        int rc;
        Blob hash;
        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
        if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
        if( blob_size(&a1)!=32 ) goto manifest_syntax_error;
        if( !validate16(blob_buffer(&a1), 32) ) goto manifest_syntax_error;
        md5sum_finish(&hash);
        rc = blob_compare(&hash, &a1);
        blob_reset(&hash);
        if( rc!=0 ) goto manifest_syntax_error;
        seenZ = 1;
        break;
      }
      default: {
        goto manifest_syntax_error;
      }
    }
  }
  if( !seenHeader ) goto manifest_syntax_error;

  if( p->nFile>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{
    goto manifest_syntax_error;
  }
    
  md5sum_init();
  return 1;

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

/*
** Add a single entry to the mlink table.  Also add the filename to
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
** is a cluster then remove all referenced elements from the
** unclustered table and create phantoms for any unknown elements.
*/
int manifest_crosslink(int rid, Blob *pContent){
  int i;
  Manifest m;
  Stmt q;


  if( manifest_parse(&m, pContent)==0 ){
    return 0;
  }
  db_begin_transaction();

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

      }
    }
    db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d AND isprim", rid);
    while( db_step(&q)==SQLITE_ROW ){
      int cid = db_column_int(&q, 0);
      add_mlink(rid, &m, cid, 0);
    }
    db_finalize(&q);























































































    db_multi_exec(
      "INSERT INTO event(type,mtime,objid,user,comment)"
      "VALUES('ci',%.17g,%d,%Q,%Q)",
      m.rDate, rid, m.zUser, m.zComment
    );
  }
  for(i=0; i<m.nCChild; i++){
    int rid;
    rid = uuid_to_rid(m.azCChild[i], 1);
    if( rid>0 ){
      db_multi_exec("DELETE FROM unclustered WHERE rid=%d", rid);
    }
  }
  db_end_transaction(0);
  manifest_clear(&m);
  return 1;
}







>





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


|
|

<
<
|
<
<
<
<





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
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878


879




880
881
882
883
884
** is a cluster then remove all referenced elements from the
** unclustered table and create phantoms for any unknown elements.
*/
int manifest_crosslink(int rid, Blob *pContent){
  int i;
  Manifest m;
  Stmt q;
  int parentid = 0;

  if( manifest_parse(&m, pContent)==0 ){
    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;
        }
      }
      db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d AND isprim", rid);
      while( db_step(&q)==SQLITE_ROW ){
        int cid = db_column_int(&q, 0);
        add_mlink(rid, &m, cid, 0);
      }
      db_finalize(&q);
      db_multi_exec(
        "INSERT INTO event(type,mtime,objid,user,comment,"
        "                  bgcolor,brbgcolor,euser,ecomment)"
        "VALUES('ci',%.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 AND tagtype!=1),"
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
        m.rDate, rid, m.zUser, m.zComment, 
        TAG_BGCOLOR, rid,
        TAG_BGCOLOR, rid,
        TAG_USER, rid,
        TAG_COMMENT, rid
      );
    }
  }
  if( m.type==CFTYPE_CLUSTER ){
    for(i=0; i<m.nCChild; i++){
      int mid;
      mid = uuid_to_rid(m.azCChild[i], 1);
      if( mid>0 ){
        db_multi_exec("DELETE FROM unclustered WHERE rid=%d", mid);
      }
    }
  }
  if( m.type==CFTYPE_CONTROL || m.type==CFTYPE_MANIFEST ){
    for(i=0; i<m.nTag; i++){
      int tid;
      int type;
      tid = uuid_to_rid(m.aTag[i].zUuid, 1);
      switch( m.aTag[i].zName[0] ){
        case '+':  type = 1; break;
        case '*':  type = 2; break;
        case '-':  type = 0; break;
        default:
          fossil_fatal("unknown tag type in manifest: %s", m.aTag);
          return 0;
      }
      tag_insert(&m.aTag[i].zName[1], type, m.aTag[i].zValue, 
                 rid, m.rDate, tid);
    }
    if( parentid ){
      tag_propagate_all(parentid);
    }
  }
  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(
      "INSERT INTO event(type,mtime,objid,user,comment,"
      "                  bgcolor,brbgcolor,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 AND tagtype!=1),"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
      m.rDate, rid, m.zUser, zComment, 
      TAG_BGCOLOR, rid,
      TAG_BGCOLOR, rid,
      TAG_USER, rid,
      TAG_COMMENT, rid
    );
    free(zComment);
  }
  if( m.type==CFTYPE_TICKET ){
    char *zTag;
    char *zComment;

    ticket_insert(&m, 1, 1);
    zTag = mprintf("tkt-%s", m.zTicketUuid);
    tag_insert(zTag, 1, 0, rid, m.rDate, rid);
    free(zTag);
    zComment = mprintf("Changes to ticket [%.10s]", m.zTicketUuid);
    db_multi_exec(
      "INSERT 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.
42
43
44
45
46
47
48

49

50
51
52
53
54
55
56
** 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 */
  Stmt q;



  if( g.argc!=3 ){
    usage("VERSION");
  }
  db_must_be_within_tree();
  vid = db_lget_int("checkout", 0);
  if( vid==0 ){
    fossil_panic("nothing is checked out");







>

>







42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
** 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 */
  Stmt q;
  int detailFlag;

  detailFlag = find_option("detail",0,0)!=0;
  if( g.argc!=3 ){
    usage("VERSION");
  }
  db_must_be_within_tree();
  vid = db_lget_int("checkout", 0);
  if( vid==0 ){
    fossil_panic("nothing is checked out");
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
  }
  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 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);


    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. */



    printf("MERGE %s\n", zName);

    undo_save(zName);
    zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
    free(zName);
    content_get(ridp, &p);
    content_get(ridm, &m);
    blob_zero(&v);
    blob_read_from_file(&v, zFullPath);
    blob_merge(&p, &m, &v, &r);

    blob_write_to_file(&r, zFullPath);







    blob_reset(&p);
    blob_reset(&m);
    blob_reset(&v);
    blob_reset(&r);
    db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(%d,%d)",
                  idv,ridm);
  }







|







>
>




>
>
>
|
>


<




|
>
|
>
>
>
>
>
>
>







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
  }
  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);
    }else{
      printf("MERGE %s\n", zName);
    }
    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);
    }
    free(zName);
    blob_reset(&p);
    blob_reset(&m);
    blob_reset(&v);
    blob_reset(&r);
    db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(%d,%d)",
                  idv,ridm);
  }
Changes to src/merge3.c.
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
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
556
557
558
559
560
561

562
563
564
565

566
567
568
569
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
**
** This module implements a 3-way merge
*/
#include "config.h"
#include "merge3.h"

#if 0
# define DEBUG1(X) X
#else
# define DEBUG1(X)
#endif
#if 0
#define DEBUG2(X) X
/*
** For debugging:
** Print 16 characters of text from zBuf
*/
static const char *print16(const char *z){
  int i;
  static char zBuf[20];
  for(i=0; i<16; i++){
    if( z[i]>=0x20 && z[i]<=0x7e ){
      zBuf[i] = z[i];
    }else{
      zBuf[i] = '.';
    }
  }
  zBuf[i] = 0;
  return zBuf;
}
#else
# define DEBUG2(X)
#endif

/*
** Must be a 32-bit integer
*/
typedef unsigned int u32;

/*
** Must be a 16-bit value 
*/
typedef unsigned short int u16;

/*
** The width of a hash window in bytes.  The algorithm only works if this
** is a power of 2.
*/
#define NHASH 16

/*
** The current state of the rolling hash.
**
** z[] holds the values that have been hashed.  z[] is a circular buffer.
** z[i] is the first entry and z[(i+NHASH-1)%NHASH] is the last entry of 
** the window.
**
** Hash.a is the sum of all elements of hash.z[].  Hash.b is a weighted
** sum.  Hash.b is z[i]*NHASH + z[i+1]*(NHASH-1) + ... + z[i+NHASH-1]*1.
** (Each index for z[] should be module NHASH, of course.  The %NHASH operator
** is omitted in the prior expression for brevity.)
*/
typedef struct hash hash;
struct hash {
  u16 a, b;         /* Hash values */
  u16 i;            /* Start of the hash window */
  char z[NHASH];    /* The values that have been hashed */
};

/*
** Initialize the rolling hash using the first NHASH characters of z[]
*/
static void hash_init(hash *pHash, const char *z){
  u16 a, b, i;
  a = b = 0;
  for(i=0; i<NHASH; i++){
    a += z[i];
    b += (NHASH-i)*z[i];
    pHash->z[i] = z[i];
  }
  pHash->a = a & 0xffff;
  pHash->b = b & 0xffff;
  pHash->i = 0;
}

/*
** Advance the rolling hash by a single character "c"
*/
static void hash_next(hash *pHash, int c){
  u16 old = pHash->z[pHash->i];
  pHash->z[pHash->i] = c;
  pHash->i = (pHash->i+1)&(NHASH-1);
  pHash->a = pHash->a - old + c;
  pHash->b = pHash->b - NHASH*old + pHash->a;
}

/*
** Return a 32-bit hash value
*/
static u32 hash_32bit(hash *pHash){
  return (pHash->a & 0xffff) | (((u32)(pHash->b & 0xffff))<<16);
}

/*
** Maximum number of landmarks to set in the source file.
*/
#define MX_LANDMARK (1024*128)

/*
** A mapping structure is used to record which parts of two
** files contain the same text.  There are zero or more mapping
** entries in a mapping.  Each entry maps a segment of text in
** the source file into a segment of the output file.
**
**     fromFirst...fromLast ->  toFirst...toLast
**
** Extra text might be inserted in the output file after a 
** mapping.  The nExtra parameter records the number of bytes
** of extra text to insert.
*/
typedef struct Mapping Mapping;
struct Mapping {
  int nMap;
  int nUsed;
  struct Mapping_entry {
    int fromFirst, fromLast;
    int toFirst, toLast;
    int nExtra;
  } *aMap;
};

/*
** Free malloced memory associated with a mapping.
*/
static void MappingClear(Mapping *p){
  free(p->aMap);
  memset(p, 0, sizeof(*p));
}

/*
** Add an entry to a mapping structure.  The mapping is:
**
**     a...b  ->   c...d
**
** The nExtra parameter is initially zero.  It will be changed
** later if necessary.
*/
static void MappingInsert(Mapping *p, int a, int b, int c, int d){
  struct Mapping_entry *pEntry;
  int i;
  for(i=0, pEntry=p->aMap; i<p->nUsed; i++, pEntry++){
    if( pEntry->fromFirst==a && pEntry->fromLast==b && pEntry->toFirst==c ){
      DEBUG2( printf("DUPLICATE: %6d..%-6d %6d..%d\n", a, b, c, d); )
      return;
    }
  }
  if( p->nUsed>=p->nMap ){
    p->nMap = p->nMap * 2 + 10;
    p->aMap = realloc(p->aMap, p->nMap*sizeof(p->aMap[0]) );
    if( p->aMap==0 ) exit(1);
  }
  pEntry = &p->aMap[p->nUsed];
  pEntry->fromFirst = a;
  pEntry->fromLast = b;
  pEntry->toFirst = c;
  pEntry->toLast = d;
  pEntry->nExtra = 0;
  p->nUsed++;
}

DEBUG1(
/*
** For debugging purposes:
** Print the content of a mapping.
*/
static void MappingPrint(Mapping *pMap){
  int i;
  struct Mapping_entry *p;
  for(i=0, p=pMap->aMap; i<pMap->nUsed; i++, p++){
    printf("%6d..%-6d %6d..%-6d  %d\n",
       p->fromFirst, p->fromLast,
       p->toFirst, p->toLast, p->nExtra);
  }
}
)

/*
** Remove deleted entries from a mapping.  Deleted enties have 
** an fromFirst of less than 0.
*/
static void MappingPurgeDeletedEntries(Mapping *p){
  int i, j;
  for(i=j=0; i<p->nUsed; i++){
    if( p->aMap[i].fromFirst<0 ) continue;
    if( j<i ){
      p->aMap[j] = p->aMap[i];
    }
    j++;
  }
  p->nUsed = j;
}

/*
** Comparisons functions used for sorting elements of a Mapping
*/
static int intAbs(int x){ return x<0 ? -x : x; }
static int compareSize(const void *a, const void *b){
  const struct Mapping_entry *A = (const struct Mapping_entry*)a;
  const struct Mapping_entry *B = (const struct Mapping_entry*)b;
  int rc;
  rc = (B->fromLast - B->fromFirst) - (A->fromLast - A->fromFirst);
  if( rc==0 ){
    rc = intAbs(A->toFirst - A->fromFirst) -
              intAbs(B->toFirst - B->fromFirst);
  }
  return rc;
}
static int compareFrom(const void *a, const void *b){
  const struct Mapping_entry *A = (const struct Mapping_entry*)a;
  const struct Mapping_entry *B = (const struct Mapping_entry*)b;
  return A->fromFirst - B->fromFirst;
}
static int compareTo(const void *a, const void *b){
  const struct Mapping_entry *A = (const struct Mapping_entry*)a;
  const struct Mapping_entry *B = (const struct Mapping_entry*)b;
  return A->toFirst - B->toFirst;
}

/*
** Routines for sorting the entries of a mapping.  SortSize sorts
** the entries in order of decreasing size (largest first.)  
** SortFrom and SortTo sort the entries in order of increasing
** fromFirst and toFirst.
*/
static void MappingSortSize(Mapping *p){
  qsort(p->aMap, p->nUsed, sizeof(p->aMap[0]), compareSize);
}
static void MappingSortFrom(Mapping *p){
  qsort(p->aMap, p->nUsed, sizeof(p->aMap[0]), compareFrom);
}
static void MappingSortTo(Mapping *p){
  qsort(p->aMap, p->nUsed, sizeof(p->aMap[0]), compareTo);
}

/*
** Initialize pMap to contain a set of similarities between two files.
*/
static void MappingInit(
  const char *zSrc,      /* The source or pattern file */
  int lenSrc,            /* Length of the source file */
  const char *zOut,      /* The target file */
  int lenOut,            /* Length of the target file */
  Mapping *pMap          /* Write the map of similaries here */
){
  int i, j, base, prefix;
  hash h;
  int *collide;
  int origLenOut = lenOut;
  struct Mapping_entry *aMap;
  int landmark[MX_LANDMARK];

  /*
  ** Initialize the map
  */
  memset(pMap, 0, sizeof(*pMap));

  /*
  ** Find common prefix and suffix
  */
  if( lenSrc<=NHASH || lenOut<=NHASH ){
    MappingInsert(pMap, 0, 0, 0, 0);
    goto add_nextra;
  }
  for(i=0; i<lenSrc && i<lenOut && zSrc[i]==zOut[i]; i++){}
  if( i>=NHASH ){
    MappingInsert(pMap, 0, i-1, 0, i-1);
    lenSrc -= i;
    zSrc += i;
    lenOut -= i;
    zOut += i;
    if( lenSrc<=0 || lenOut<=0 ) goto add_nextra;
    prefix = i;
  }else{
    prefix = 0;
  }
  for(i=1; i<=lenSrc && i<=lenOut && zSrc[lenSrc-i]==zOut[lenOut-i]; i++){}
  if( i>NHASH ){
    MappingInsert(pMap, prefix+lenSrc-i+1, prefix+lenSrc-1,
                        prefix+lenOut-i+1, prefix+lenOut-1);
    lenSrc -= i;
    lenOut -= i;
  }

  /* If the source file is very small, it means that we have no
  ** chance of ever finding any matches.  We can leave early.
  */
  if( lenSrc<=NHASH ) goto add_nextra;

  /* Compute the hash table used to locate matching sections in the
  ** source file.
  */
  collide = malloc( lenSrc*sizeof(int)/NHASH );
  if( collide==0 ) return;
  memset(landmark, -1, sizeof(landmark));
  memset(collide, -1, lenSrc*sizeof(int)/NHASH );
  for(i=0; i<lenSrc-NHASH; i+=NHASH){
    int hv;
    hash_init(&h, &zSrc[i]);
    hv = hash_32bit(&h) & (MX_LANDMARK-1);
    collide[i/NHASH] = landmark[hv];
    landmark[hv] = i/NHASH;
  }

  /* Begin scanning the target file and generating mappings.  In this
  ** step, we generate as many mapping entries is we can.  Many of these
  ** entries might overlap.  The overlapping entries are removed by
  ** the loop the follows.
  */
  base = 0;    /* We have already checked everything before zOut[base] */
  while( base<lenOut-NHASH ){
    int iSrc, iBlock, nextBase, nextBaseI;
    hash_init(&h, &zOut[base]);
    i = 0;     /* Trying to match a landmark against zOut[base+i] */
    nextBaseI = NHASH;
    nextBase = base;
    while(1){
      int hv;

      hv = hash_32bit(&h) & (MX_LANDMARK-1);
      DEBUG2( printf("LOOKING: %d+%d+%d=%d [%s]\n",
              prefix,base,i,prefix+base+i, print16(&zOut[base+i])); )
      iBlock = landmark[hv];
      while( iBlock>=0 ){
        /*
        ** The hash window has identified a potential match against 
        ** landmark block iBlock.  But we need to investigate further.


        ** 
        ** Look for a region in zOut that matches zSrc. Anchor the search
        ** at zSrc[iSrc] and zOut[base+i].
        **
        ** Set cnt equal to the length of the match and set ofst so that
        ** zSrc[ofst] is the first element of the match. 
        */
        int cnt, ofstSrc;
        int j, k, x, y;

        /* Beginning at iSrc, match forwards as far as we can.  j counts
        ** the number of characters that match */
        iSrc = iBlock*NHASH;
        for(j=0, x=iSrc, y=base+i; x<lenSrc && y<lenOut; j++, x++, y++){
          if( zSrc[x]!=zOut[y] ) break;
        }
        j--;

        /* Beginning at iSrc-1, match backwards as far as we can.  k counts
        ** the number of characters that match */
        for(k=1; k<iSrc && k<=base+i; k++){
          if( zSrc[iSrc-k]!=zOut[base+i-k] ) break;
        }
        k--;

        /* Compute the offset and size of the matching region zSrc */
        ofstSrc = iSrc-k;
        cnt = j+k+1;
        DEBUG2( printf("MATCH %d bytes at SRC[%d..%d]: [%s]\n",
                 cnt, ofstSrc, ofstSrc+cnt-1, print16(&zSrc[ofstSrc])); )
        if( cnt>NHASH ){
          int ofstOut = base+i-k;
          DEBUG2( printf("COPY %6d..%-6d %6d..%d\n",
            prefix+ofstSrc, prefix+ofstSrc+cnt-1,
            prefix+ofstOut, prefix+ofstOut+cnt-1); )
          MappingInsert(pMap,
            prefix+ofstSrc, prefix+ofstSrc+cnt-1,
            prefix+ofstOut, prefix+ofstOut+cnt-1);
          if( nextBase < ofstOut+cnt-1 ){
            nextBase = ofstOut+cnt-1;
            nextBaseI = i+NHASH;
          }
        }

        /* Check the next matching block */
        iBlock = collide[iBlock];
      }

      /* If we found a match, then jump out to the outer loop and begin
      ** a new cycle.
      */
      if( nextBase>base && i>=nextBaseI ){
        base = nextBase;
        break;
      }

      /* Advance the hash by one character.  Keep looking for a match */
      if( base+i+NHASH>=lenOut ){
        base = lenOut;
        break;
      }
      hash_next(&h, zOut[base+i+NHASH]);
      i++;
    }
  }
  free(collide);
  DEBUG1(
   printf("after creation:\n");
   MappingPrint(pMap);
  )

  /* In this step, we will remove overlapping entries from the mapping.
  **
  ** We use a greedy algorithm.  Select the largest mapping first and
  ** remove all overlapping mappings.  Then take the next largest
  ** mapping and remove others that overlap with it.  Keep going until
  ** all mappings have been processed.
  */
  MappingSortSize(pMap);
  for(i=0; i<pMap->nUsed; i++){
    int sortNeeded = 0;
    int purgeNeeded = 0;
    struct Mapping_entry *pA;
    pA = &pMap->aMap[i];
    for(j=i+1; j<pMap->nUsed; j++){
      int diff;
      struct Mapping_entry *pB;
      pB = &pMap->aMap[j];
      if( pB->fromLast<pA->fromFirst || pB->fromFirst>pA->fromLast ){
        /* No overlap.  Do nothing */
      }else if( pB->fromFirst>=pA->fromFirst && pB->fromLast<=pA->fromLast ){
        /* B is contained entirely within A.  Drop B */
        pB->fromFirst = -1;
        purgeNeeded = 1;
        continue;
      }else if( pB->fromFirst<pA->fromFirst ){
        /* The tail B overlaps the head of A */
        assert( pB->fromLast>=pA->fromFirst && pB->fromLast<=pA->fromLast );
        diff = pB->fromLast + 1 - pA->fromFirst;
        pB->fromLast -= diff;
        pB->toLast -= diff;
        sortNeeded = 1;
      }else{
        /* The head of B overlaps the tail of A */
        assert( pB->fromFirst<=pA->fromLast && pB->fromLast>pA->fromLast );
        diff = pA->fromLast + 1 - pB->fromFirst;
        pB->fromFirst += diff;
        pB->toFirst += diff;
        sortNeeded = 1;
      }
      if( pB->toLast<pA->toFirst || pB->toFirst>pA->toLast ){
        /* No overlap.  Do nothing */
      }else if( pB->toFirst>=pA->toFirst && pB->toLast<=pA->toLast ){
        /* B is contained entirely within A.  Drop B */
        pB->fromFirst = -1;
        purgeNeeded = 1;
      }else if( pB->toFirst<pA->toFirst ){
        /* The tail of B overlaps the head of A */
        assert( pB->toLast>=pA->toFirst && pB->toLast<=pA->toLast );
        diff = pB->toLast + 1 - pA->toFirst;
        pB->fromLast -= diff;
        pB->toLast -= diff;
        sortNeeded = 1;
      }else{
        /* The head of B overlaps the tail of A */
        assert( pB->toFirst<=pA->toLast && pB->toLast>pA->toLast );
        diff = pA->toLast + 1 - pB->toFirst;
        pB->fromFirst += diff;
        pB->toFirst += diff;
        sortNeeded = 1;
      }
    }
    if( purgeNeeded ){
      MappingPurgeDeletedEntries(pMap);
    }
    if( sortNeeded && i<pMap->nUsed-2 ){
      MappingSortSize(pMap);
    }
  }

  /* Final step:  Arrange the mapping entires so that they are in the
  ** order of the output file.  Then fill in the nExtra values.
  */
add_nextra:
  MappingSortTo(pMap);
  aMap = pMap->aMap;
  for(i=0; i<pMap->nUsed-1; i++){
    aMap[i].nExtra = aMap[i+1].toFirst - aMap[i].toLast - 1;
  }
  if( pMap->nUsed>0 && origLenOut > aMap[i].toLast+1 ){
    aMap[i].nExtra = origLenOut - aMap[i].toLast - 1;
  }
}

/*
** Translate an index into a file using a mapping.
**
** The mapping "p" shows how blocks in the input file map into blocks
** of the output file.  The index iFrom is an index into the input file.
** This routine returns the index into the output file of the corresponding
** character.
**
** If pInserted!=0 and iFrom points to the last character before a
** insert in the output file, then the return value is adjusted forward
** so that it points to the end of the insertion and the number of
** bytes inserted is written into *pInserted.  If pInserted==0 then
** iFrom always maps directly in the corresponding output file
** index regardless of whether or not it points to the last character
** before an insertion.
*/
static int MappingTranslate(Mapping *p, int iFrom, int *pInserted){
  int i;
  for(i=0; i<p->nUsed; i++){
    if( iFrom>p->aMap[i].fromLast ) continue;
    if( iFrom<=p->aMap[i].fromFirst ){
      return p->aMap[i].toFirst;
    }
    if( pInserted && iFrom==p->aMap[i].fromLast ){
      int n = p->aMap[i].nExtra;
      *pInserted = n;
      return p->aMap[i].toLast + n;
    }else{
      return p->aMap[i].toFirst + iFrom - p->aMap[i].fromFirst;
    }
  }
  i--;
  return p->aMap[i].toLast + p->aMap[i].nExtra;
}

/*
** Do a three-way merge.  Initialize pOut to contain the result.









*/
void blob_merge(Blob *pPivot, Blob *pV1, Blob *pV2, Blob *pOut){



  Mapping map1, map2;

  int i;



  const char *zV1, *zV2;








  blob_zero(pOut);
  DEBUG1( printf("map1:\n"); )
  MappingInit(
    blob_buffer(pPivot), blob_size(pPivot),
    blob_buffer(pV1), blob_size(pV1),
    &map1);
  MappingSortFrom(&map1);

  DEBUG1( 
    printf("map1-final:\n");
    MappingPrint(&map1);
    printf("map2:\n");

  )
  MappingInit(
    blob_buffer(pPivot), blob_size(pPivot),
    blob_buffer(pV2), blob_size(pV2),
    &map2);
  DEBUG1(

    printf("map2-final:\n");


    MappingPrint(&map2);

  )
  zV1 = blob_buffer(pV1);

  zV2 = blob_buffer(pV2);
  if( map2.nUsed==0 ) return;
  if( map1.aMap[0].toFirst>0 ){
    blob_append(pOut, zV1, map1.aMap[0].toFirst);




    DEBUG1( printf("INSERT %d bytes from V1[0..%d]\n",
















            map1.aMap[0].toFirst, map1.aMap[0].toFirst-1); )

  }


  if( map2.aMap[0].toFirst>0 ){

    blob_append(pOut, zV2, map2.aMap[0].toFirst);
    DEBUG1( printf("INSERT %d bytes from V2[0..%d]\n",
            map2.aMap[0].toFirst, map2.aMap[0].toFirst-1); )



  }
  for(i=0; i<map2.nUsed; i++){


    int iFirst, iLast, nInsert;
    struct Mapping_entry *p = &map2.aMap[i];
    iFirst = MappingTranslate(&map1, p->fromFirst, 0);
    iLast = MappingTranslate(&map1, p->fromLast, &nInsert);





    blob_append(pOut, &zV1[iFirst], iLast - iFirst + 1);

    DEBUG1(
      printf("COPY %d bytes from V1[%d..%d]\n", iLast-iFirst+1, iFirst, iLast);
    )


    if( p->nExtra>0 ){
      if( p->nExtra==nInsert
          && memcmp(&zV2[p->toLast+1], &zV1[iLast-nInsert+1], nInsert)==0 ){
        /* Omit a duplicate insert */
        DEBUG1( printf("OMIT duplicate insert\n"); )











      }else{


        blob_append(pOut, &zV2[p->toLast+1], p->nExtra);

        DEBUG1(
          printf("INSERT %d bytes from V2[%d..%d]\n",
                  p->nExtra, p->toLast+1, p->toLast+p->nExtra);

        )

      }


    }





  }







  MappingClear(&map1);

  MappingClear(&map2);

}

/*
** COMMAND:  test-3-way-merge
**
** Combine change in going from PIVOT->VERSION1 with the change going
** from PIVOT->VERSION2 and write the combined changes into MERGED.







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

|

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

<
|
<
<
<
<

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




>
>
>
>
>
>
>
>
>

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

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

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







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
**
** This module implements a 3-way merge
*/
#include "config.h"
#include "merge3.h"

#if 0





#define DEBUG(X)  X

















#else
#define DEBUG(X)
#endif










































































































/*

** Opcodes




*/






















#define CPY 0















#define DEL 1















#define INS 2

























#define END 3















#define UNK 4

























































































/*


** Compare a single line of text from pV1 and pV2.  If the lines
** are the same, return true.  Return false if they are different.
**

** The cursor on both pV1 and pV2 is unchanged.



*/


static int sameLine(Blob *pV1, Blob *pV2){







  char *z1, *z2;






  int i;













































  z1 = &blob_buffer(pV1)[blob_tell(pV1)];












  z2 = &blob_buffer(pV2)[blob_tell(pV2)];
  for(i=0; z1[i]!='\n' && z1[i]==z2[i]; i++){}






















































  return z2[i]=='\n' || (z2[i]=='\r' && z2[i+1]=='\n')













          || (z1[i]=='\r' && z2[i]=='\n' && z1[i+1]=='\n');

































}

/*
** Do a three-way merge.  Initialize pOut to contain the result.
**
** The merge is an edit against pV2.  Both pV1 and pV2 have a
** common origin at pPivot.  Apply the changes of pPivot ==> pV1
** to pV2.
**
** The return is 0 upon complete success. If any input file is binary,
** -1 is returned and pOut is unmodified.  If there are merge
** conflicts, the merge proceeds as best as it can and the number 
** of conflicts is returns
*/
int blob_merge(Blob *pPivot, Blob *pV1, Blob *pV2, Blob *pOut){
  int *aC1;  /* Changes from pPivot to pV1 */
  int *aC2;  /* Changes from pPivot to pV2 */
  int i1, i2;
  int op1, op2;
  int limit1, limit2;
  int inConflict = 0;
  int nConflict = 0;
  static const char zBegin[] = ">>>>>>>> BEGIN MERGE CONFLICT <<<<<<<<\n";
  static const char zEnd[]   = ">>>>>>>>> END MERGE CONFLICT <<<<<<<<<\n";

  aC1 = text_diff(pPivot, pV1, 0, 0);
  aC2 = text_diff(pPivot, pV2, 0, 0);

  if( aC2==0 || aC2==0 ){
    free(aC1);
    free(aC2);
    return -1;
  }
  blob_zero(pOut);

  blob_rewind(pV1);
  blob_rewind(pV2);
  blob_rewind(pPivot);


  for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){}
  limit1 = i1;


  for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){}
  limit2 = i2;





  DEBUG(
    for(i1=0; i1<limit1; i1+=3){
      printf("c1: %4d %4d %4d\n", aC1[i1], aC1[i1+1], aC1[i1+2]);
    }
    for(i2=0; i2<limit2; i2+=3){
     printf("c2: %4d %4d %4d\n", aC2[i2], aC2[i2+1], aC2[i2+2]);
    }
  )

  op1 = op2 = UNK;
  i1 = i2 = 0;
  while( i1<limit1 && aC1[i1]==0 ){ i1++; }
  while( i2<limit2 && aC2[i2]==0 ){ i2++; }

  while(1){
    if( op1==UNK ){
      if( i1>=limit1 ){
        op1 = END;
        DEBUG( printf("get op1=END\n"); )
      }else{
        op1 = i1 % 3;
        aC1[i1]--;
        DEBUG( printf("get op1=%d from %d (%d->%d)\n",
               op1,i1,aC1[i1]+1,aC1[i1]); )
        while( i1<limit1 && aC1[i1]==0 ){ i1++; }
      }
    }
    if( op2==UNK ){
      if( i2>=limit2 ){
        op2 = END;
        DEBUG( printf("get op2=END\n"); )
      }else{
        op2 = i2 % 3;
        aC2[i2]--;
        DEBUG( printf("get op2=%d from %d (%d->%d)\n",
               op2,i2,aC2[i2]+1,aC2[i2]); )
        while( i2<limit2 && aC2[i2]==0 ){ i2++; }
      }
    }
    DEBUG( printf("op1=%d op2=%d\n", op1, op2); )
    if( op1==END ){
      if( op2==INS ){
        blob_copy_lines(pOut, pV2, 1);


        op2 = UNK;
      }else{
        break;
      }

    }else if( op2==END ){
      if( op1==INS ){
        blob_copy_lines(pOut, pV1, 1);
        op1 = UNK;


      }else{
        break;
      }
    }else if( op1==INS && op2==INS ){
      if( !inConflict && sameLine(pV1, pV2) ){
        blob_copy_lines(pOut, pV2, 1);
        blob_copy_lines(0, pV1, 0);
        op1 = UNK;


        op2 = UNK;
      }else{
        if( !inConflict ){


          inConflict = 1;
          nConflict++;
          blob_appendf(pOut, zBegin);
        }
        blob_copy_lines(pOut, pV1, 1);
        op1 = UNK;
      }
    }else if( op1==INS ){
      blob_copy_lines(pOut, pV1, 1);
      op1 = UNK;
    }else if( op2==INS ){
      blob_copy_lines(pOut, pV2, 1);
      op2 = UNK;
    }else{
      if( inConflict ){
        inConflict = 0;
        blob_appendf(pOut, zEnd);
      }
      if( op1==DEL || op2==DEL ){


        blob_copy_lines(0, pPivot, 1);
        if( op2==CPY ){
          blob_copy_lines(0, pV2, 1);
        }
        if( op1==CPY ){
          blob_copy_lines(0, pV1, 1);
        }
      }else{
        assert( op1==CPY && op2==CPY );
        blob_copy_lines(pOut, pPivot, 1);
        blob_copy_lines(0, pV1, 1);
        blob_copy_lines(0, pV2, 1);
      }
      op1 = UNK;
      op2 = UNK;
    }
  }
  if( inConflict ){
    blob_appendf(pOut, zEnd);
  }

  free(aC1);
  free(aC2);
  return nConflict;
}

/*
** COMMAND:  test-3-way-merge
**
** Combine change in going from PIVOT->VERSION1 with the change going
** from PIVOT->VERSION2 and write the combined changes into MERGED.
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
    exit(1);
  }
  blob_reset(&pivot);
  blob_reset(&v1);
  blob_reset(&v2);
  blob_reset(&merged);
}


/*
** COMMAND:  test-mapping
*/
void mapping_test(void){
  int i;
  const char *z;
  Blob a, b;
  Mapping map;
  if( g.argc!=4 ){
    usage("FILE1 FILE2");
  }
  blob_read_from_file(&a, g.argv[2]);
  blob_read_from_file(&b, g.argv[3]);
  memset(&map, 0, sizeof(map));
  MappingInit(blob_buffer(&a), blob_size(&a),
              blob_buffer(&b), blob_size(&b),
              &map);
  z = blob_buffer(&a);
  for(i=0; i<map.nUsed; i++){
    printf("======= %6d..%-6d %6d..%-6d %d\n", 
         map.aMap[i].fromFirst, map.aMap[i].fromLast,
         map.aMap[i].toFirst, map.aMap[i].toLast,
         map.aMap[i].nExtra);
    printf("%.*s\n", map.aMap[i].fromLast - map.aMap[i].fromFirst + 1, 
                     &z[map.aMap[i].fromFirst]);
  }
}







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
236
237
238
239
240
241
242





























    exit(1);
  }
  blob_reset(&pivot);
  blob_reset(&v1);
  blob_reset(&v2);
  blob_reset(&merged);
}





























Changes to src/name.c.
31
32
33
34
35
36
37



38
39
40
41
42
43
44
45


















46

47





48
49
50
51
52
53
54
#include "name.h"
#include <assert.h>

/*
** This routine takes a user-entered UUID which might be in mixed
** case and might only be a prefix of the full UUID and converts it
** into the full-length UUID in canonical form.



**
** Return the number of errors.
*/
int name_to_uuid(Blob *pName, int iErrPriority){
  int rc;
  int sz;
  sz = blob_size(pName);
  if( sz>UUID_SIZE || sz<4 || !validate16(blob_buffer(pName), sz) ){


















    fossil_error(iErrPriority, "not a valid object name: %b", pName);

    return 1;





  }
  blob_materialize(pName);
  canonical16(blob_buffer(pName), sz);
  if( sz==UUID_SIZE ){
    rc = db_int(1, "SELECT 0 FROM blob WHERE uuid=%B", pName);
    if( rc ){
      fossil_error(iErrPriority, "unknown object: %b", pName);







>
>
>








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







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
#include "name.h"
#include <assert.h>

/*
** This routine takes a user-entered UUID which might be in mixed
** case and might only be a prefix of the full UUID and converts it
** into the full-length UUID in canonical form.
**
** If the input is not a UUID or a UUID prefix, then try to resolve
** the name as a tag.
**
** Return the number of errors.
*/
int name_to_uuid(Blob *pName, int iErrPriority){
  int rc;
  int sz;
  sz = blob_size(pName);
  if( sz>UUID_SIZE || sz<4 || !validate16(blob_buffer(pName), sz) ){
    Stmt q;
    Blob uuid;

    db_prepare(&q,
      "SELECT (SELECT uuid FROM blob WHERE rid=objid)"
      "  FROM tagxref JOIN event ON rid=objid"
      " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%B)"
      "   AND tagtype>0"
      "   AND value IS NULL"
      " ORDER BY event.mtime DESC",
      pName
    );
    blob_zero(&uuid);
    if( db_step(&q)==SQLITE_ROW ){
      db_column_blob(&q, 0, &uuid);
    }
    db_finalize(&q);
    if( blob_size(&uuid)==0 ){
      fossil_error(iErrPriority, "not a valid object name: %b", pName);
      blob_reset(&uuid);
      return 1;
    }else{
      blob_reset(pName);
      *pName = uuid;
      return 0;
    }
  }
  blob_materialize(pName);
  canonical16(blob_buffer(pName), sz);
  if( sz==UUID_SIZE ){
    rc = db_int(1, "SELECT 0 FROM blob WHERE uuid=%B", pName);
    if( rc ){
      fossil_error(iErrPriority, "unknown object: %b", pName);
70
71
72
73
74
75
76


77
78
79
80
81
82
83
         zOrig[sz-1]--;
         fossil_error(iErrPriority, "non-unique name prefix: %s", zOrig);
         rc = 1;
      }else{
         rc = 0;
      }
    }


  }
  return rc;
}

/*
** COMMAND:  test-name-to-uuid
**







>
>







97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
         zOrig[sz-1]--;
         fossil_error(iErrPriority, "non-unique name prefix: %s", zOrig);
         rc = 1;
      }else{
         rc = 0;
      }
    }
  }else{
    rc = 0;
  }
  return rc;
}

/*
** COMMAND:  test-name-to-uuid
**
118
119
120
121
122
123
124
125
126
127
128
129
130
    rid = atoi(zName);
    if( db_exists("SELECT 1 FROM blob WHERE rid=%d", rid) ){
      return rid;
    }
  }
  blob_init(&name, zName, -1);
  if( name_to_uuid(&name, 1) ){
    fossil_panic("%s", g.zErrMsg);
  }
  rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &name);
  blob_reset(&name);
  return rid;
}







|





147
148
149
150
151
152
153
154
155
156
157
158
159
    rid = atoi(zName);
    if( db_exists("SELECT 1 FROM blob WHERE rid=%d", rid) ){
      return rid;
    }
  }
  blob_init(&name, zName, -1);
  if( name_to_uuid(&name, 1) ){
    fossil_fatal("%s", g.zErrMsg);
  }
  rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &name);
  blob_reset(&name);
  return rid;
}
Changes to src/pivot.c.
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/*
** Find the most recent common ancestor of the primary and one of
** the secondaries.  Return its rid.  Return 0 if no common ancestor
** can be found.
*/
int pivot_find(void){
  Stmt q1, q2, u1, i1;
  int rid;
  
  /* aqueue must contain at least one primary and one other.  Otherwise
  ** we abort early
  */
  if( db_int(0, "SELECT count(distinct src) FROM aqueue")<2 ){
    fossil_panic("lack both primary and secondary files");
  }







|







80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/*
** Find the most recent common ancestor of the primary and one of
** the secondaries.  Return its rid.  Return 0 if no common ancestor
** can be found.
*/
int pivot_find(void){
  Stmt q1, q2, u1, i1;
  int rid = 0;
  
  /* aqueue must contain at least one primary and one other.  Otherwise
  ** we abort early
  */
  if( db_int(0, "SELECT count(distinct src) FROM aqueue")<2 ){
    fossil_panic("lack both primary and secondary files");
  }
Changes to src/printf.c.
47
48
49
50
51
52
53



54
55
56
57
58
59
60
#define etSQLESCAPE2 14 /* Strings with '\'' doubled and enclosed in '',
                          NULL pointers replaced by SQL NULL.  %Q */
#define etPOINTER    15 /* The %p conversion */
#define etHTMLIZE    16 /* Make text safe for HTML */
#define etHTTPIZE    17 /* Make text safe for HTTP.  "/" encoded as %2f */
#define etURLIZE     18 /* Make text safe for HTTP.  "/" not encoded */
#define etFOSSILIZE  19 /* The fossil header encoding format. */





/*
** An "etByte" is an 8-bit unsigned value.
*/
typedef unsigned char etByte;








>
>
>







47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#define etSQLESCAPE2 14 /* Strings with '\'' doubled and enclosed in '',
                          NULL pointers replaced by SQL NULL.  %Q */
#define etPOINTER    15 /* The %p conversion */
#define etHTMLIZE    16 /* Make text safe for HTML */
#define etHTTPIZE    17 /* Make text safe for HTTP.  "/" encoded as %2f */
#define etURLIZE     18 /* Make text safe for HTTP.  "/" not encoded */
#define etFOSSILIZE  19 /* The fossil header encoding format. */
#define etPATH       20 /* Path type */
#define etWIKISTR    21 /* Wiki text rendered from a char* */
#define etWIKIBLOB   22 /* Wiki text rendered from a Blob* */


/*
** An "etByte" is an 8-bit unsigned value.
*/
typedef unsigned char etByte;

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
  {  's',  0, 4, etSTRING,     0,  0 },
  {  'g',  0, 1, etGENERIC,    30, 0 },
  {  'z',  0, 6, etDYNSTRING,  0,  0 },
  {  'q',  0, 4, etSQLESCAPE,  0,  0 },
  {  'Q',  0, 4, etSQLESCAPE2, 0,  0 },
  {  'b',  0, 2, etBLOB,       0,  0 },
  {  'B',  0, 2, etBLOBSQL,    0,  0 },


  {  'h',  0, 4, etHTMLIZE,    0,  0 },
  {  't',  0, 4, etHTTPIZE,    0,  0 },  /* "/" -> "%2F" */
  {  'T',  0, 4, etURLIZE,     0,  0 },  /* "/" unchanged */
  {  'F',  0, 4, etFOSSILIZE,  0,  0 },
  {  'c',  0, 0, etCHARX,      0,  0 },
  {  'o',  8, 0, etRADIX,      0,  2 },
  {  'u', 10, 0, etRADIX,      0,  0 },
  {  'x', 16, 0, etRADIX,      16, 1 },
  {  'X', 16, 0, etRADIX,      0,  4 },
  {  'f',  0, 1, etFLOAT,      0,  0 },
  {  'e',  0, 1, etEXP,        30, 0 },
  {  'E',  0, 1, etEXP,        14, 0 },
  {  'G',  0, 1, etGENERIC,    14, 0 },
  {  'i', 10, 1, etRADIX,      0,  0 },
  {  'n',  0, 0, etSIZE,       0,  0 },
  {  '%',  0, 0, etPERCENT,    0,  0 },
  {  'p', 16, 0, etPOINTER,    0,  1 },

};
#define etNINFO  (sizeof(fmtinfo)/sizeof(fmtinfo[0]))

/*
** "*val" is a double such that 0.1 <= *val < 10.0
** Return the ascii code for the leading digit of *val, then
** multiply "*val" by 10.0 to renormalize.







>
>

















>







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
  {  's',  0, 4, etSTRING,     0,  0 },
  {  'g',  0, 1, etGENERIC,    30, 0 },
  {  'z',  0, 6, etDYNSTRING,  0,  0 },
  {  'q',  0, 4, etSQLESCAPE,  0,  0 },
  {  'Q',  0, 4, etSQLESCAPE2, 0,  0 },
  {  'b',  0, 2, etBLOB,       0,  0 },
  {  'B',  0, 2, etBLOBSQL,    0,  0 },
  {  'w',  0, 2, etWIKISTR,    0,  0 },
  {  'W',  0, 2, etWIKIBLOB,   0,  0 },
  {  'h',  0, 4, etHTMLIZE,    0,  0 },
  {  't',  0, 4, etHTTPIZE,    0,  0 },  /* "/" -> "%2F" */
  {  'T',  0, 4, etURLIZE,     0,  0 },  /* "/" unchanged */
  {  'F',  0, 4, etFOSSILIZE,  0,  0 },
  {  'c',  0, 0, etCHARX,      0,  0 },
  {  'o',  8, 0, etRADIX,      0,  2 },
  {  'u', 10, 0, etRADIX,      0,  0 },
  {  'x', 16, 0, etRADIX,      16, 1 },
  {  'X', 16, 0, etRADIX,      0,  4 },
  {  'f',  0, 1, etFLOAT,      0,  0 },
  {  'e',  0, 1, etEXP,        30, 0 },
  {  'E',  0, 1, etEXP,        14, 0 },
  {  'G',  0, 1, etGENERIC,    14, 0 },
  {  'i', 10, 1, etRADIX,      0,  0 },
  {  'n',  0, 0, etSIZE,       0,  0 },
  {  '%',  0, 0, etPERCENT,    0,  0 },
  {  'p', 16, 0, etPOINTER,    0,  1 },
  {  '/',  0, 0, etPATH,       0,  0 },
};
#define etNINFO  (sizeof(fmtinfo)/sizeof(fmtinfo[0]))

/*
** "*val" is a double such that 0.1 <= *val < 10.0
** Return the ascii code for the leading digit of *val, then
** multiply "*val" by 10.0 to renormalize.
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
**          the function "func".  Returns -1 on a error.
**
** Note that the order in which automatic variables are declared below
** seems to make a big difference in determining how fast this beast
** will run.
*/
int vxprintf(
  void (*func)(void*,const char*,int),     /* Consumer of text */
  void *arg,                         /* First argument to the consumer */
  const char *fmt,                   /* Format string */
  va_list ap                         /* arguments */
){
  int c;                     /* Next character in the format string */
  char *bufpt;               /* Pointer to the conversion buffer */
  int precision;             /* Precision of the current field */
  int length;                /* Length of the field */







<
|







173
174
175
176
177
178
179

180
181
182
183
184
185
186
187
**          the function "func".  Returns -1 on a error.
**
** Note that the order in which automatic variables are declared below
** seems to make a big difference in determining how fast this beast
** will run.
*/
int vxprintf(

  Blob *pBlob,                       /* Append output to this blob */
  const char *fmt,                   /* Format string */
  va_list ap                         /* arguments */
){
  int c;                     /* Next character in the format string */
  char *bufpt;               /* Pointer to the conversion buffer */
  int precision;             /* Precision of the current field */
  int length;                /* Length of the field */
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
  int  exp, e2;              /* exponent of real numbers */
  double rounder;            /* Used for rounding floating point values */
  etByte flag_dp;            /* True if decimal point should be shown */
  etByte flag_rtz;           /* True if trailing zeros should be removed */
  etByte flag_exp;           /* True to force display of the exponent */
  int nsd;                   /* Number of significant digits returned */

  func(arg,"",0);
  count = length = 0;
  bufpt = 0;
  for(; (c=(*fmt))!=0; ++fmt){
    if( c!='%' ){
      int amt;
      bufpt = (char *)fmt;
      amt = 1;
      while( (c=(*++fmt))!='%' && c!=0 ) amt++;
      (*func)(arg,bufpt,amt);
      count += amt;
      if( c==0 ) break;
    }
    if( (c=(*++fmt))==0 ){
      errorflag = 1;
      (*func)(arg,"%",1);
      count++;
      break;
    }
    /* Find out what flags are present */
    flag_leftjustify = flag_plussign = flag_blanksign = 
     flag_alternateform = flag_altform2 = flag_zeropad = 0;
    done = 0;







<








|





|







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
  int  exp, e2;              /* exponent of real numbers */
  double rounder;            /* Used for rounding floating point values */
  etByte flag_dp;            /* True if decimal point should be shown */
  etByte flag_rtz;           /* True if trailing zeros should be removed */
  etByte flag_exp;           /* True to force display of the exponent */
  int nsd;                   /* Number of significant digits returned */


  count = length = 0;
  bufpt = 0;
  for(; (c=(*fmt))!=0; ++fmt){
    if( c!='%' ){
      int amt;
      bufpt = (char *)fmt;
      amt = 1;
      while( (c=(*++fmt))!='%' && c!=0 ) amt++;
      blob_append(pBlob,bufpt,amt);
      count += amt;
      if( c==0 ) break;
    }
    if( (c=(*++fmt))==0 ){
      errorflag = 1;
      blob_append(pBlob,"%",1);
      count++;
      break;
    }
    /* Find out what flags are present */
    flag_leftjustify = flag_plussign = flag_blanksign = 
     flag_alternateform = flag_altform2 = flag_zeropad = 0;
    done = 0;
539
540
541
542
543
544
545
















546
547
548
549
550
551
552
          for(idx=1; idx<precision; idx++) buf[idx] = c;
          length = precision;
        }else{
          length =1;
        }
        bufpt = buf;
        break;
















      case etSTRING:
      case etDYNSTRING:
        bufpt = va_arg(ap,char*);
        if( bufpt==0 ){
          bufpt = "";
        }else if( xtype==etDYNSTRING ){
          zExtra = bufpt;







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







543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
          for(idx=1; idx<precision; idx++) buf[idx] = c;
          length = precision;
        }else{
          length =1;
        }
        bufpt = buf;
        break;
      case etPATH: {
        int i;
        char *e = va_arg(ap,char*);
        if( e==0 ){e="";}
        length = strlen(e);
        zExtra = bufpt = malloc(length+1);
        for( i=0; i<length; i++ ){
          if( e[i]=='\\' ){
            bufpt[i]='/';
          }else{
            bufpt[i]=e[i];
          }
        }
        bufpt[length]='\0';
        break;
      }
      case etSTRING:
      case etDYNSTRING:
        bufpt = va_arg(ap,char*);
        if( bufpt==0 ){
          bufpt = "";
        }else if( xtype==etDYNSTRING ){
          zExtra = bufpt;
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
      case etFOSSILIZE: {
        char *zMem = va_arg(ap,char*);
        if( zMem==0 ) zMem = "";
        zExtra = bufpt = fossilize(zMem, -1);
        length = strlen(bufpt);
        if( precision>=0 && precision<length ) length = precision;
        break;















      }
      case etERROR:
        buf[0] = '%';
        buf[1] = c;
        errorflag = 0;
        idx = 1+(c!=0);
        (*func)(arg,"%",idx);
        count += idx;
        if( c==0 ) fmt--;
        break;
    }/* End switch over the format type */
    /*
    ** The text of the conversion is pointed to by "bufpt" and is
    ** "length" characters long.  The field width is "width".  Do
    ** the output.
    */
    if( !flag_leftjustify ){
      register int nspace;
      nspace = width-length;
      if( nspace>0 ){
        count += nspace;
        while( nspace>=etSPACESIZE ){
          (*func)(arg,spaces,etSPACESIZE);
          nspace -= etSPACESIZE;
        }
        if( nspace>0 ) (*func)(arg,spaces,nspace);
      }
    }
    if( length>0 ){
      (*func)(arg,bufpt,length);
      count += length;
    }
    if( flag_leftjustify ){
      register int nspace;
      nspace = width-length;
      if( nspace>0 ){
        count += nspace;
        while( nspace>=etSPACESIZE ){
          (*func)(arg,spaces,etSPACESIZE);
          nspace -= etSPACESIZE;
        }
        if( nspace>0 ) (*func)(arg,spaces,nspace);
      }
    }
    if( zExtra ){
      free(zExtra);
    }
  }/* End for loop over the format string */
  return errorflag ? -1 : count;







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






|















|


|



|








|


|







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
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
      case etFOSSILIZE: {
        char *zMem = va_arg(ap,char*);
        if( zMem==0 ) zMem = "";
        zExtra = bufpt = fossilize(zMem, -1);
        length = strlen(bufpt);
        if( precision>=0 && precision<length ) length = precision;
        break;
      }
      case etWIKISTR: {
        char *zWiki = va_arg(ap, char*);
        Blob wiki;
        blob_init(&wiki, zWiki, -1);
        wiki_convert(&wiki, pBlob, WIKI_INLINE);
        blob_reset(&wiki);
        length = width = 0;
        break;
      }
      case etWIKIBLOB: {
        Blob *pWiki = va_arg(ap, Blob*);
        wiki_convert(pWiki, pBlob, WIKI_INLINE);
        length = width = 0;
        break;
      }
      case etERROR:
        buf[0] = '%';
        buf[1] = c;
        errorflag = 0;
        idx = 1+(c!=0);
        blob_append(pBlob,"%",idx);
        count += idx;
        if( c==0 ) fmt--;
        break;
    }/* End switch over the format type */
    /*
    ** The text of the conversion is pointed to by "bufpt" and is
    ** "length" characters long.  The field width is "width".  Do
    ** the output.
    */
    if( !flag_leftjustify ){
      register int nspace;
      nspace = width-length;
      if( nspace>0 ){
        count += nspace;
        while( nspace>=etSPACESIZE ){
          blob_append(pBlob,spaces,etSPACESIZE);
          nspace -= etSPACESIZE;
        }
        if( nspace>0 ) blob_append(pBlob,spaces,nspace);
      }
    }
    if( length>0 ){
      blob_append(pBlob,bufpt,length);
      count += length;
    }
    if( flag_leftjustify ){
      register int nspace;
      nspace = width-length;
      if( nspace>0 ){
        count += nspace;
        while( nspace>=etSPACESIZE ){
          blob_append(pBlob,spaces,etSPACESIZE);
          nspace -= etSPACESIZE;
        }
        if( nspace>0 ) blob_append(pBlob,spaces,nspace);
      }
    }
    if( zExtra ){
      free(zExtra);
    }
  }/* End for loop over the format string */
  return errorflag ? -1 : count;
Changes to src/rebuild.c.
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
*******************************************************************************
**
** This file contains code used to rebuild the database.
*/
#include "config.h"
#include "rebuild.h"
#include <assert.h>




























/*
** Core function to rebuild the infomration in the derived tables of a
** fossil repository from the blobs. This function is shared between
** 'rebuild_database' ('rebuild') and 'reconstruct_cmd'
** ('reconstruct'), both of which have to regenerate this information
** from scratch.





*/

int rebuild_db(void){
  Stmt s;
  int errCnt = 0;
  char *zTable;


  db_multi_exec(
    "CREATE INDEX IF NOT EXISTS delta_i1 ON delta(srcid);"
  );
  for(;;){
    zTable = db_text(0,
       "SELECT name FROM sqlite_master"
       " WHERE type='table'"
       " AND name NOT IN ('blob','delta','rcvfrom','user','config')");
    if( zTable==0 ) break;
    db_multi_exec("DROP TABLE %Q", zTable);
    free(zTable);
  }
  db_multi_exec(zRepositorySchema2);


  db_multi_exec("INSERT INTO unclustered SELECT rid FROM blob");




  db_multi_exec(
    "DELETE FROM config WHERE name IN ('remote-code', 'remote-maxid')"
  );
  db_prepare(&s, "SELECT rid, size FROM blob");




  while( db_step(&s)==SQLITE_ROW ){
    int rid = db_column_int(&s, 0);
    int size = db_column_int(&s, 1);
    if( size>=0 ){
      Blob content;





      content_get(rid, &content);
      manifest_crosslink(rid, &content);
      blob_reset(&content);
    }else{
      db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid);
    }




  }
  return errCnt;
}

/*
** COMMAND:  rebuild
**
** Usage: %fossil rebuild REPOSITORY
**
** Reconstruct the named repository database from the core
** records.  Run this command after updating the fossil
** executable in a way that changes the database schema.
*/
void rebuild_database(void){
  int forceFlag;

  int errCnt;

  forceFlag = find_option("force","f",0)!=0;

  if( g.argc!=3 ){
    usage("REPOSITORY-FILENAME");
  }
  db_open_repository(g.argv[2]);
  db_begin_transaction();
  errCnt = rebuild_db();
  if( errCnt && !forceFlag ){
    printf("%d errors. Rolling back changes. Use --force to force a commit.\n",
            errCnt);
    db_end_transaction(1);
  }else{
    db_end_transaction(0);
  }
}







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







>
>
>
>
>

<
|



>

|
<
<




|





>


>
>
>
>



|
>
>
>
>





>
>
>
>
>






>
>
>
>















>



>





|








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
*******************************************************************************
**
** This file contains code used to rebuild the database.
*/
#include "config.h"
#include "rebuild.h"
#include <assert.h>

/*
** Schema changes
*/
static const char zSchemaUpdates[] =
@ -- Index on the delta table
@ --
@ CREATE INDEX IF NOT EXISTS delta_i1 ON delta(srcid);
@
@ -- Artifacts that should not be processed are identified in the
@ -- "shun" table.  Artifacts that are control-file forgeries or
@ -- spam can be shunned in order to prevent them from contaminating
@ -- the repository.
@ --
@ CREATE TABLE IF NOT EXISTS shun(uuid UNIQUE);
@
@ -- An entry in this table describes a database query that generates a
@ -- table of tickets.
@ --
@ CREATE TABLE IF NOT EXISTS reportfmt(
@    rn integer primary key,  -- Report number
@    owner text,              -- Owner of this report format (not used)
@    title text,              -- Title of this report
@    cols text,               -- A color-key specification
@    sqlcode text             -- An SQL SELECT statement for this report
@ );
;

/*
** Core function to rebuild the infomration in the derived tables of a
** fossil repository from the blobs. This function is shared between
** 'rebuild_database' ('rebuild') and 'reconstruct_cmd'
** ('reconstruct'), both of which have to regenerate this information
** from scratch.
**
** If the randomize parameter is true, then the BLOBs are deliberately
** extracted in a random order.  This feature is used to test the
** ability of fossil to accept records in any order and still
** construct a sane repository.
*/

int rebuild_db(int randomize, int ttyOutput){
  Stmt s;
  int errCnt = 0;
  char *zTable;
  int cnt = 0;

  db_multi_exec(zSchemaUpdates);


  for(;;){
    zTable = db_text(0,
       "SELECT name FROM sqlite_master"
       " WHERE type='table'"
       " AND name NOT IN ('blob','delta','rcvfrom','user','config','shun')");
    if( zTable==0 ) break;
    db_multi_exec("DROP TABLE %Q", zTable);
    free(zTable);
  }
  db_multi_exec(zRepositorySchema2);
  ticket_create_table(0);

  db_multi_exec("INSERT INTO unclustered SELECT rid FROM blob");
  db_multi_exec(
     "DELETE FROM unclustered"
     " WHERE rid IN (SELECT rid FROM shun JOIN blob USING(uuid))"
  );
  db_multi_exec(
    "DELETE FROM config WHERE name IN ('remote-code', 'remote-maxid')"
  );
  db_prepare(&s,
     "SELECT rid, size FROM blob %s"
     " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)",
     randomize ? "ORDER BY random()" : ""
  );
  while( db_step(&s)==SQLITE_ROW ){
    int rid = db_column_int(&s, 0);
    int size = db_column_int(&s, 1);
    if( size>=0 ){
      Blob content;
      if( ttyOutput ){
        cnt++;
        printf("%d...\r", cnt);
        fflush(stdout);
      }
      content_get(rid, &content);
      manifest_crosslink(rid, &content);
      blob_reset(&content);
    }else{
      db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid);
    }
  }
  db_finalize(&s);
  if( ttyOutput ){
    printf("\n");
  }
  return errCnt;
}

/*
** COMMAND:  rebuild
**
** Usage: %fossil rebuild REPOSITORY
**
** Reconstruct the named repository database from the core
** records.  Run this command after updating the fossil
** executable in a way that changes the database schema.
*/
void rebuild_database(void){
  int forceFlag;
  int randomizeFlag;
  int errCnt;

  forceFlag = find_option("force","f",0)!=0;
  randomizeFlag = find_option("randomize", 0, 0)!=0;
  if( g.argc!=3 ){
    usage("REPOSITORY-FILENAME");
  }
  db_open_repository(g.argv[2]);
  db_begin_transaction();
  errCnt = rebuild_db(randomizeFlag, 1);
  if( errCnt && !forceFlag ){
    printf("%d errors. Rolling back changes. Use --force to force a commit.\n",
            errCnt);
    db_end_transaction(1);
  }else{
    db_end_transaction(0);
  }
}
Added src/report.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
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
556
557
558
559
560
561
562
563
564
565
566
567
568
569
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
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
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
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
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
/*
** Copyright (c) 2007 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 as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** 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/
**
*******************************************************************************
**  
** Code to generate the bug report listings
*/
#include "config.h"
#include "report.h"
#include <assert.h>

/* Forward references to static routines */
static void report_format_hints(void);

/*
** WEBPAGE: /reportlist
*/
void view_list(void){
  Stmt q;

  login_check_credentials();
  if( !g.okRdTkt ){ login_needed(); return; }
  style_header("Available Report Formats");
  db_prepare(&q, "SELECT rn, title, owner FROM reportfmt ORDER BY title");
  @ <p>Choose a report format from the following list:</p>
  @ <ol>
  while( db_step(&q)==SQLITE_ROW ){
    int rn = db_column_int(&q, 0);
    const char *zTitle = db_column_text(&q, 1);
    const char *zOwner = db_column_text(&q, 2);
    @ <li><a href="rptview?rn=%d(rn)"
    @        rel="nofollow">%h(zTitle)</a>&nbsp;&nbsp;&nbsp;
    if( g.okWrite && zOwner && zOwner[0] ){
      @ (by <i>%h(zOwner)</i>)
    }
    if( g.okWrTkt ){
      @ [<a href="rptedit?rn=%d(rn)&amp;copy=1" rel="nofollow">copy</a>]
    }
    if( g.okAdmin || (g.okWrTkt && zOwner && strcmp(g.zLogin,zOwner)==0) ){
      @ [<a href="rptedit?rn=%d(rn)" rel="nofollow">edit</a>]
    }
    @ [<a href="rptsql?rn=%d(rn)" rel="nofollow">sql</a>]
    @ </li>
  }
  if( g.okWrTkt ){
    @ <li><a href="rptnew">Create a new report format</a></li>
  }
  @ </ol>
  common_footer();
}

/*
** Remove whitespace from both ends of a string.
*/
char *trim_string(const char *zOrig){
  int i;
  while( isspace(*zOrig) ){ zOrig++; }
  i = strlen(zOrig);
  while( i>0 && isspace(zOrig[i-1]) ){ i--; }
  return mprintf("%.*s", i, zOrig);
}

/*
** Extract a numeric (integer) value from a string.
*/
char *extract_integer(const char *zOrig){
  if( zOrig == NULL || zOrig[0] == 0 ) return "";
  while( *zOrig && !isdigit(*zOrig) ){ zOrig++; }
  if( *zOrig ){
    /* we have a digit. atoi() will get as much of the number as it
    ** can. We'll run it through mprintf() to get a string. Not
    ** an efficient way to do it, but effective.
    */
    return mprintf("%d", atoi(zOrig));
  }
  return "";
}

/*
** Remove blank lines from the beginning of a string and
** all whitespace from the end. Removes whitespace preceeding a NL,
** which also converts any CRNL sequence into a single NL.
*/
char *remove_blank_lines(const char *zOrig){
  int i, j, n;
  char *z;
  for(i=j=0; isspace(zOrig[i]); i++){ if( zOrig[i]=='\n' ) j = i+1; }
  n = strlen(&zOrig[j]);
  while( n>0 && isspace(zOrig[j+n-1]) ){ n--; }
  z = mprintf("%.*s", n, &zOrig[j]);
  for(i=j=0; z[i]; i++){
    if( z[i+1]=='\n' && z[i]!='\n' && isspace(z[i]) ){
      z[j] = z[i];
      while(isspace(z[j]) && z[j] != '\n' ){ j--; }
      j++;
      continue;
    }

    z[j++] = z[i];
  }
  z[j] = 0;
  return z;
}


/*********************************************************************/

/*
** This is the SQLite authorizer callback used to make sure that the
** SQL statements entered by users do not try to do anything untoward.
** If anything suspicious is tried, set *(char**)pError to an error
** message obtained from malloc.
*/
static int report_query_authorizer(
  void *pError,
  int code,
  const char *zArg1,
  const char *zArg2,
  const char *zArg3,
  const char *zArg4
){
  char *zError = *(char**)pError;
  if( zError ){
    /* We've already seen an error.  No need to continue. */
    return SQLITE_OK;
  }
  switch( code ){
    case SQLITE_SELECT:
    case SQLITE_FUNCTION: {
      break;
    }
    case SQLITE_READ: {
      static const char *azAllowed[] = {
         "ticket",
         "blob",
         "filename",
         "mlink",
         "plink",
         "event",
         "tag",
         "tagxref",
      }
      int i;
      for(i=0; i<sizeof(azAllowed)/sizeof(azAllowed[0]); i++){
        if( strcasecmp(zArg1, azAllowed[i])==0 ) break;
      }
      if( i>=sizeof(azAllowed)/sizeof(azAllowed[0]) ){
        zError = mprintf("cannot access table %s", zArg1);
      }
      break;
    }
    default: {
      zError = mprintf("only SELECT statements are allowed");
      break;
    }
  }
  return SQLITE_OK;
}


/*
** Check the given SQL to see if is a valid query that does not
** attempt to do anything dangerous.  Return 0 on success and a
** pointer to an error message string (obtained from malloc) if
** there is a problem.
*/
char *verify_sql_statement(char *zSql){
  int i;
  char *zErr1 = 0;
  char *zErr2 = 0;
  char *zTail;

  /* First make sure the SQL is a single query command by verifying that
  ** the first token is "SELECT" and that there are no unquoted semicolons.
  */
  for(i=0; isspace(zSql[i]); i++){}
  if( strncasecmp(&zSql[i],"select",6)!=0 ){
    return mprintf("The SQL must be a SELECT statement");
  }
  for(i=0; zSql[i]; i++){
    if( zSql[i]==';' ){
      int bad;
      int c = zSql[i+1];
      zSql[i+1] = 0;
      bad = sqlite3_complete(zSql);
      zSql[i+1] = c;
      if( bad ){
        /* A complete statement basically means that an unquoted semi-colon
        ** was found. We don't actually check what's after that.
        */
        return mprintf("Semi-colon detected! "
                       "Only a single SQL statement is allowed");
      }
    }
  }
  
  /* Compile the statement and check for illegal accesses or syntax errors. */
  sqlite3_set_authorizer(g.db, report_query_authorizer, (void*)&zErr);
  rc = sqlite3_prepare(g.db, zSql, -1, &pStmt, &zTail);
  if( rc!=SQLITE_OK ){
    free(zErr);
    zErr = mprintf("Syntax error: %s", sqlite3_errmsg(g.db));
  }
  if( pStmt ){
    sqlite3_finalize(pStmt);
  }
  sqlite3_set_authorizer(g.db, 0, 0);
  return zErr;
}

/*
** WEBPAGE: /rptsql
*/
void view_see_sql(void){
  int rn, rc;
  char *zTitle;
  char *zSQL;
  char *zOwner;
  char *zClrKey;
  Stmt q;

  login_check_credentials();
  if( !g.okQuery ){
    login_needed();
    return;
  }
  rn = atoi(PD("rn","0"));
  db_prepare(&q, "SELECT title, sqlcode, owner, cols "
                   "FROM reportfmt WHERE rn=%d",rn);
  style_header("SQL For Report Format Number %d", rn);
  if( db_step(&q)!=SQLITE_ROW ){
    @ <p>Unknown report number: %d(rn)</p>
    style_footer();
    return;
  }
  zTitle = db_column_text(&q, 0);
  zSQL = db_column_text(&q, 1);
  zOwner = db_column_text(&q, 2);
  zClrKey = db_column_text(&q, 3);
  @ <table cellpadding=0 cellspacing=0 border=0>
  @ <tr><td valign="top" align="right">Title:</td><td width=15></td>
  @ <td colspan=3>%h(zTitle)</td></tr>
  @ <tr><td valign="top" align="right">Owner:</td><td></td>
  @ <td colspan=3>%h(zOwner)</td></tr>
  @ <tr><td valign="top" align="right">SQL:</td><td></td>
  @ <td valign="top"><pre>
  @ %h(zSQL)
  @ </pre></td>
  @ <td width=15></td><td valign="top">
  output_color_key(zClrKey, 0, "border=0 cellspacing=0 cellpadding=3");
  @ </td>
  @ </tr></table>
  report_format_hints();
  style_footer();
}

/*
** WEBPAGE: /rptnew
** WEBPAGE: /rptedit
*/
void view_edit(void){
  int rn;
  const char *zTitle;
  const char *z;
  const char *zOwner;
  char *zClrKey;
  char *zSQL;
  char *zErr = 0;

  login_check_credentials();
  if( !g.okQuery ){
    login_needed();
    return;
  }
  view_add_functions(0);
  rn = atoi(PD("rn","0"));
  zTitle = P("t");
  zOwner = PD("w",g.zLogin);
  z = P("s");
  zSQL = z ? trim_string(z) : 0;
  zClrKey = trim_string(PD("k",""));
  if( rn>0 && P("del2") ){
    db_multi_exec("DELETE FROM reportfmt WHERE rn=%d", rn);
    cgi_redirect("reportlist");
    return;
  }else if( rn>0 && P("del1") ){
    zTitle = db_text(0, "SELECT title FROM reportfmt "
                         "WHERE rn=%d", rn);
    if( zTitle==0 ) cgi_redirect("reportlist");

    style_header("Are You Sure?");
    @ <form action="rptedit" method="POST">
    @ <p>You are about to delete all traces of the report
    @ <strong>%h(zTitle)</strong> from
    @ the database.  This is an irreversible operation.  All records
    @ related to this report will be removed and cannot be recovered.</p>
    @
    @ <input type="hidden" name="rn" value="%d(rn)">
    @ <input type="submit" name="del2" value="Delete The Report">
    @ <input type="submit" name="can" value="Cancel">
    @ </form>
    style_footer();
    return;
  }else if( P("can") ){
    /* user cancelled */
    cgi_redirect("reportlist");
    return;
  }
  if( zTitle && zSQL ){
    if( zSQL[0]==0 ){
      zErr = "Please supply an SQL query statement";
    }else if( (zTitle = trim_string(zTitle))[0]==0 ){
      zErr = "Please supply a title"; 
    }else{
      zErr = verify_sql_statement(zSQL);
    }
    if( zErr==0 ){
      if( rn>0 ){
        db_multi_exec("UPDATE reportfmt SET title=%Q, sqlcode=%Q,"
                      " owner=%Q, cols=%Q WHERE rn=%d",
           zTitle, zSQL, zOwner, zClrKey, rn);
      }else{
        db_multi_exec("INSERT INTO reportfmt(title,sqlcode,owner,cols) "
           "VALUES(%Q,%Q,%Q,%Q)",
           zTitle, zSQL, zOwner, zClrKey);
        rn = db_last_insert_rowid();
      }
      cgi_redirect(mprintf("rptview?rn=%d", rn));
      return;
    }
  }else if( rn==0 ){
    zTitle = "";
    zSQL = 
      @ SELECT
      @   CASE WHEN status IN ('new','active') THEN '#f2dcdc'
      @        WHEN status='review' THEN '#e8e8bd'
      @        WHEN status='fixed' THEN '#cfe8bd'
      @        WHEN status='tested' THEN '#bde5d6'
      @        WHEN status='defer' THEN '#cacae5'
      @        ELSE '#c8c8c8' END AS 'bgcolor',
      @   tn AS '#',
      @   type AS 'Type',
      @   status AS 'Status',
      @   sdate(origtime) AS 'Created',
      @   owner AS 'By',
      @   subsystem AS 'Subsys',
      @   sdate(changetime) AS 'Changed',
      @   assignedto AS 'Assigned',
      @   severity AS 'Svr',
      @   priority AS 'Pri',
      @   title AS 'Title'
      @ FROM ticket
    ;
    zClrKey = 
      @ #ffffff Key:
      @ #f2dcdc Active
      @ #e8e8e8 Review
      @ #cfe8bd Fixed
      @ #bde5d6 Tested
      @ #cacae5 Deferred
      @ #c8c8c8 Closed
    ;
  }else{
    db_prepare(&q, "SELECT title, sqlcode, owner, cols "
                     "FROM reportfmt WHERE rn=%d",rn);
    if( db_step(&q)==SQLITE_ROW ){
      zTitle = db_column_malloc(&q, 0);
      zSQL = db_column_malloc(&q, 1);
      zOwner = db_column_malloc(&q, 2);
      zClrKey = db_column_malloc(&q, 3);
    }
    if( P("copy") ){
      rn = 0;
      zTitle = mprintf("Copy Of %s", zTitle);
      zOwner = g.zLogin;
    }
  }
  if( zOwner==0 ) zOwner = g.zLogin;
  style_submenu_element("Cancel", "Cancel", "reportlist");
  if( rn>0 ){
    style_submenu_element("Delete", "Delete", "rptedit?rn=%d&del1=1", rn);
  }
  style_header(rn>0 ? "Edit Report Format":"Create New Report Format");
  if( zErr ){
    @ <blockquote><font color="#ff0000"><b>%h(zErr)</b></font></blockquote>
  }
  @ <form action="rptedit" method="POST">
  @ <input type="hidden" name="rn" value="%d(rn)">
  @ <p>Report Title:<br>
  @ <input type="text" name="t" value="%h(zTitle)" size="60"></p>
  @ <p>Enter a complete SQL query statement against the "TICKET" table:<br>
  @ <textarea name="s" rows="20" cols="80">%h(zSQL)</textarea>
  @ </p>
  if( g.okAdmin ){
    @ <p>Report owner:
    @ <input type="text" name="w" size="20" value="%h(zOwner)">
    @ </p>
  } else {
    @ <input type="hidden" name="w" value="%h(zOwner)">
  }
  @ <p>Enter an optional color key in the following box.  (If blank, no
  @ color key is displayed.)  Each line contains the text for a single
  @ entry in the key.  The first token of each line is the background
  @ color for that line.<br>
  @ <textarea name="k" rows="8" cols="50">%h(zClrKey)</textarea>
  @ </p>
  if( !g.okAdmin && strcmp(zOwner,g.zLogin)!=0 ){
    @ <p>This report format is owned by %h(zOwner).  You are not allowed
    @ to change it.</p>
    @ </form>
    report_format_hints();
    style_footer();
    return;
  }
  @ <input type="submit" value="Apply Changes">
  if( rn>0 ){
    @ <input type="submit" value="Delete This Report" name="del1">
  }
  @ </form>
  report_format_hints();
  style_footer();
}

/*
** Output a bunch of text that provides information about report
** formats
*/
static void report_format_hints(void){
  char *zSchema;
  zSchema = db_text(0,"SELECT sql FROM sqlite_master WHERE name='ticket'");
  @ <hr><h3>TICKET Schema</h3>
  @ <blockquote><pre>
  @ %h(zSchema)
  @ </pre></blockquote>
  @ <h3>Notes</h3>
  @ <ul>
  @ <li><p>The SQL must consist of a single SELECT statement</p></li>
  @
  @ <li><p>If a column of the result set is named "#" then that column
  @ is assumed to hold a ticket number.  A hyperlink will be created from
  @ that column to a detailed view of the ticket.</p></li>
  @
  @ <li><p>If a column of the result set is named "bgcolor" then the content
  @ of that column determines the background color of the row.</p></li>
  @
  @ <li><p>The <b>user()</b> SQL function returns a string
  @ which is the login of the current user.</p></li>
  @
  @ <li><p>The first column whose name begins with underscore ("_") and all
  @ subsequent columns are shown on their own rows in the table.  This might
  @ be useful for displaying the description of tickets.
  @ </p></li>
  @
  @ <li><p>The <b>aux()</b> SQL function takes a parameter name as an argument
  @ and returns the value that the user enters in the resulting HTML form. A
  @ second optional parameter provides a default value for the field.</p></li>
  @
  @ <li><p>The <b>option()</b> SQL function takes a parameter name
  @ and a quoted SELECT statement as parameters. The query results are
  @ presented as an HTML dropdown menu and the function returns
  @ the currently selected value. Results may be a single value column or
  @ two <b>value,description</b> columns. The first row is the default.</p></li>
  @
  @ <li><p>The <b>cgi()</b> SQL function takes a parameter name as an argument
  @ and returns the value of a corresponding CGI query value. If the CGI
  @ parameter doesn't exist, an optional second argument will be returned
  @ instead.</p></li>
  @
  @ <li><p>If a column is wrapped by the <b>wiki()</b> SQL function, it will
  @ be rendered as wiki formatted content.</p></li>
  @
  @ <li><p>If a column is wrapped by the <b>tkt()</b> SQL function, it will
  @ be shown as a ticket id with a link to the appropriate page</p></li>
  @
  @ <li><p>If a column is wrapped by the <b>chng()</b> SQL function, it will
  @ be shown as a baseline id with a link to the appropriate page.</p></li>
  @
  @ <li><p>The <b>search()</b> SQL function takes a keyword pattern and
  @ a search text. The function returns an integer score which is
  @ higher depending on how well the search went.</p></li>
  @
  @ <li><p>The query can join other tables in the database besides TICKET.
  @ </p></li>
  @ </ul>
  @
  @ <h3>Examples</h3>
  @ <p>In this example, the first column in the result set is named
  @ "bgcolor".  The value of this column is not displayed.  Instead, it
  @ selects the background color of each row based on the TICKET.STATUS
  @ field of the database.  The color key at the right shows the various
  @ color codes.</p>
  @ <table align="right" style="margin: 0 5px;" border=1 cellspacing=0 width=125>
  @ <tr bgcolor="#f2dcdc"><td align="center">new or active</td></tr>
  @ <tr bgcolor="#e8e8bd"><td align="center">review</td></tr>
  @ <tr bgcolor="#cfe8bd"><td align="center">fixed</td></tr>
  @ <tr bgcolor="#bde5d6"><td align="center">tested</td></tr>
  @ <tr bgcolor="#cacae5"><td align="center">defer</td></tr>
  @ <tr bgcolor="#c8c8c8"><td align="center">closed</td></tr>
  @ </table>
  @ <blockquote><pre>
  @ SELECT
  @   CASE WHEN status IN ('new','active') THEN '#f2dcdc'
  @        WHEN status='review' THEN '#e8e8bd'
  @        WHEN status='fixed' THEN '#cfe8bd'
  @        WHEN status='tested' THEN '#bde5d6'
  @        WHEN status='defer' THEN '#cacae5'
  @        ELSE '#c8c8c8' END as 'bgcolor',
  @   tn AS '#',
  @   type AS 'Type',
  @   status AS 'Status',
  @   sdate(origtime) AS 'Created',
  @   owner AS 'By',
  @   subsystem AS 'Subsys',
  @   sdate(changetime) AS 'Changed',
  @   assignedto AS 'Assigned',
  @   severity AS 'Svr',
  @   priority AS 'Pri',
  @   title AS 'Title'
  @ FROM ticket
  @ </pre></blockquote>
  @ <p>To base the background color on the TICKET.PRIORITY or
  @ TICKET.SEVERITY fields, substitute the following code for the
  @ first column of the query:</p>
  @ <table align="right" style="margin: 0 5px;" border=1 cellspacing=0 width=125>
  @ <tr bgcolor="#f2dcdc"><td align="center">1</td></tr>
  @ <tr bgcolor="#e8e8bd"><td align="center">2</td></tr>
  @ <tr bgcolor="#cfe8bd"><td align="center">3</td></tr>
  @ <tr bgcolor="#cacae5"><td align="center">4</td></tr>
  @ <tr bgcolor="#c8c8c8"><td align="center">5</td></tr>
  @ </table>
  @ <blockquote><pre>
  @ SELECT
  @   CASE priority WHEN 1 THEN '#f2dcdc'
  @        WHEN 2 THEN '#e8e8bd'
  @        WHEN 3 THEN '#cfe8bd'
  @        WHEN 4 THEN '#cacae5'
  @        ELSE '#c8c8c8' END as 'bgcolor',
  @ ...
  @ FROM ticket
  @ </pre></blockquote>
#if 0
  @ <p>You can, of course, substitute different colors if you choose.
  @ Here is a palette of suggested background colors:</p>
  @ <blockquote>
  @ <table border=1 cellspacing=0 width=300>
  @ <tr><td align="center" bgcolor="#ffbdbd">#ffbdbd</td>
  @     <td align="center" bgcolor="#f2dcdc">#f2dcdc</td></tr>
  @ <tr><td align="center" bgcolor="#ffffbd">#ffffbd</td>
  @     <td align="center" bgcolor="#e8e8bd">#e8e8bd</td></tr>
  @ <tr><td align="center" bgcolor="#c0ebc0">#c0ebc0</td>
  @     <td align="center" bgcolor="#cfe8bd">#cfe8bd</td></tr>
  @ <tr><td align="center" bgcolor="#c0c0f4">#c0c0f4</td>
  @     <td align="center" bgcolor="#d6d6e8">#d6d6e8</td></tr>
  @ <tr><td align="center" bgcolor="#d0b1ff">#d0b1ff</td>
  @     <td align="center" bgcolor="#d2c0db">#d2c0db</td></tr>
  @ <tr><td align="center" bgcolor="#bbbbbb">#bbbbbb</td>
  @     <td align="center" bgcolor="#d0d0d0">#d0d0d0</td></tr>
  @ </table>
  @ </blockquote>
#endif
  @ <p>To see the TICKET.DESCRIPTION and TICKET.REMARKS fields, include
  @ them as the last two columns of the result set and given them names
  @ that begin with an underscore.  Like this:</p>
  @ <blockquote><pre>
  @  SELECT
  @    tn AS '#',
  @    type AS 'Type',
  @    status AS 'Status',
  @    sdate(origtime) AS 'Created',
  @    owner AS 'By',
  @    subsystem AS 'Subsys',
  @    sdate(changetime) AS 'Changed',
  @    assignedto AS 'Assigned',
  @    severity AS 'Svr',
  @    priority AS 'Pri',
  @    title AS 'Title',
  @    description AS '_Description',   -- When the column name begins with '_'
  @    remarks AS '_Remarks'            -- the data is shown on a separate row.
  @  FROM ticket
  @ </pre></blockquote>
  @
  @ <p>Or, to see part of the description on the same row, use the
  @ <b>wiki()</b> function with some string manipulation. Using the
  @ <b>tkt()</b> function on the ticket number will also generate a linked
  @ field, but without the extra <i>edit</i> column:
  @ </p>
  @ <blockquote><pre>
  @  SELECT
  @    tkt(tn) AS '',
  @    title AS 'Title',
  @    wiki(substr(description,0,80)) AS 'Description'
  @  FROM ticket
  @ </pre></blockquote>
  @
}

/*********************************************************************/
static void output_report_field(const char *zData,int rn){
  const char *zWkey = wiki_key();
  const char *zTkey = tkt_key();
  const char *zCkey = chng_key();

  if( !strncmp(zData,zWkey,strlen(zWkey)) ){
    output_formatted(&zData[strlen(zWkey)],0);
  }else if( !strncmp(zData,zTkey,strlen(zTkey)) ){
    output_ticket(atoi(&zData[strlen(zTkey)]),rn);
  }else if( !strncmp(zData,zCkey,strlen(zCkey)) ){
    output_chng(atoi(&zData[strlen(zCkey)]));
  }else{
    @ %h(zData)
  }
}

static void column_header(int rn,const char *zCol, int nCol, int nSorted,
    const char *zDirection, const char *zExtra
){
  int set = (nCol==nSorted);
  int desc = !strcmp(zDirection,"DESC");

  /*
  ** Clicking same column header 3 times in a row resets any sorting.
  ** Note that we link to rptview, which means embedded reports will get
  ** sent to the actual report view page as soon as a user tries to do
  ** any sorting. I don't see that as a Bad Thing.
  */
  if(set && desc){
    @ <th bgcolor="%s(BG1)" class="bkgnd1">
    @   <a href="rptview?rn=%d(rn)%s(zExtra)">%h(zCol)</a></th>
  }else{
    if(set){
      @ <th bgcolor="%s(BG1)" class="bkgnd1"><a
    }else{
      @ <th><a
    }
    @ href="rptview?rn=%d(rn)&amp;order_by=%d(nCol)&amp;\
    @ order_dir=%s(desc?"ASC":"DESC")\
    @ %s(zExtra)">%h(zCol)</a></th>
  }
}

/*********************************************************************/
struct GenerateHTML {
  int rn;
  int nCount;
};

/*
** The callback function for db_query
*/
static int generate_html(
  void* pUser,     /* Pointer to output state */
  int nArg,        /* Number of columns in this result row */
  char **azArg,    /* Text of data in all columns */
  char **azName    /* Names of the columns */
){
  struct GenerateHTML* pState = (struct GenerateHTML*)pUser;
  int i;
  int tn;            /* Ticket number.  (value of column named '#') */
  int rn;            /* Report number */
  int ncol;          /* Number of columns in the table */
  int multirow;      /* True if multiple table rows per line of data */
  int newrowidx;     /* Index of first column that goes on a separate row */
  int iBg = -1;      /* Index of column that determines background color */
  char *zBg = 0;     /* Use this background color */
  char zPage[30];    /* Text version of the ticket number */

  /* Get the report number
  */
  rn = pState->rn;

  /* Figure out the number of columns, the column that determines background
  ** color, and whether or not this row of data is represented by multiple
  ** rows in the table.
  */
  ncol = 0;
  multirow = 0;
  newrowidx = -1;
  for(i=0; i<nArg; i++){
    if( azName[i][0]=='b' && strcmp(azName[i],"bgcolor")==0 ){
      zBg = azArg ? azArg[i] : 0;
      iBg = i;
      continue;
    }
    if( g.okWrite && azName[i][0]=='#' ){
      ncol++;
    }
    if( !multirow ){
      if( azName[i][0]=='_' ){
        multirow = 1;
        newrowidx = i;
      }else{
        ncol++;
      }
    }
  }

  /* The first time this routine is called, output a table header
  */
  if( pState->nCount==0 ){
    char zExtra[2000];
    int nField = atoi(PD("order_by","0"));
    const char* zDir = PD("order_dir","");
    zDir = !strcmp("ASC",zDir) ? "ASC" : "DESC";
    zExtra[0] = 0;

    if( g.nAux ){
      @ <tr>
      @ <td colspan=%d(ncol)><form action="rptview" method="GET">
      @ <input type="hidden" name="rn" value="%d(rn)">
      for(i=0; i<g.nAux; i++){
        const char *zN = g.azAuxName[i];
        const char *zP = g.azAuxParam[i];
        if( g.azAuxVal[i] && g.azAuxVal[i][0] ){
          appendf(zExtra,0,sizeof(zExtra),
                  "&amp;%t=%t",g.azAuxParam[i],g.azAuxVal[i]);
        }
        if( g.azAuxOpt[i] ){
          @ %h(zN): 
          if( g.anAuxCols[i]==1 ) {
            cgi_v_optionmenu( 0, zP, g.azAuxVal[i], g.azAuxOpt[i] );
          }else if( g.anAuxCols[i]==2 ){
            cgi_v_optionmenu2( 0, zP, g.azAuxVal[i], g.azAuxOpt[i] );
          }
        }else{
          @ %h(zN): <input type="text" name="%h(zP)" value="%h(g.azAuxVal[i])">
        }
      }
      @ <input type="submit" value="Go">
      @ </form></td></tr> 
    }
    @ <tr>
    tn = -1;
    for(i=0; i<nArg; i++){
      char *zName = azName[i];
      if( i==iBg ) continue;
      if( newrowidx>=0 && i>=newrowidx ){
        if( g.okWrite && tn>=0 ){
          @ <th>&nbsp;</th>
          tn = -1;
        }
        if( zName[0]=='_' ) zName++;
        @ </tr><tr><th colspan=%d(ncol)>%h(zName)</th>
      }else{
        if( zName[0]=='#' ){
          tn = i;
        }
        /*
        ** This handles any sorting related stuff. Note that we don't
        ** bother trying to sort on the "wiki format" columns. I don't
        ** think it makes much sense, visually.
        */
        column_header(rn,azName[i],i+1,nField,zDir,zExtra);
      }
    }
    if( g.okWrite && tn>=0 ){
      @ <th>&nbsp;</th>
    }
    @ </tr>
  }
  if( azArg==0 ){
    @ <tr><td colspan="%d(ncol)">
    @ <i>No records match the report criteria</i>
    @ </td></tr>
    return 0;
  }
  ++pState->nCount;

  /* Output the separator above each entry in a table which has multiple lines
  ** per database entry.
  */
  if( newrowidx>=0 ){
    @ <tr><td colspan=%d(ncol)><font size=1>&nbsp;</font></td></tr>
  }

  /* Output the data for this entry from the database
  */
  if( zBg==0 ) zBg = "white";
  @ <tr bgcolor="%h(zBg)">
  tn = 0;
  zPage[0] = 0;
  for(i=0; i<nArg; i++){
    char *zData;
    if( i==iBg ) continue;
    zData = azArg[i];
    if( zData==0 ) zData = "";
    if( newrowidx>=0 && i>=newrowidx ){
      if( tn>0 && g.okWrite ){
        @ <td valign="top"><a href="tktedit?tn=%d(tn),%d(rn)">edit</a></td>
        tn = 0;
      }
      if( zData[0] ){
        @ </tr><tr bgcolor="%h(zBg)"><td colspan=%d(ncol)>
        output_formatted(zData, zPage[0] ? zPage : 0);
      }
    }else if( azName[i][0]=='#' ){
      tn = atoi(zData);
      if( tn>0 ) bprintf(zPage, sizeof(zPage), "%d", tn);
      @ <td valign="top"><a href="tktview?tn=%d(tn),%d(rn)">%h(zData)</a></td>
    }else if( zData[0]==0 ){
      @ <td valign="top">&nbsp;</td>
    }else{
      @ <td valign="top">
      output_report_field(zData,rn);
      @ </td>
    }
  }
  if( tn>0 && g.okWrite ){
    @ <td valign="top"><a href="tktedit?tn=%d(tn),%d(rn)">edit</a></td>
  }
  @ </tr>
  return 0;
}

/*
** Output the text given in the argument.  Convert tabs and newlines into
** spaces.
*/
static void output_no_tabs(const char *z){
  while( z && z[0] ){
    int i, j;
    for(i=0; z[i] && (!isspace(z[i]) || z[i]==' '); i++){}
    if( i>0 ){
      cgi_printf("%.*s", i, z);
    }
    for(j=i; isspace(z[j]); j++){}
    if( j>i ){
      cgi_printf("%*s", j-i, "");
    }
    z += j;
  }
}

/*
** Output a row as a tab-separated line of text.
*/
static int output_tab_separated(
  void *pUser,     /* Pointer to row-count integer */
  int nArg,        /* Number of columns in this result row */
  char **azArg,    /* Text of data in all columns */
  char **azName    /* Names of the columns */
){
  int *pCount = (int*)pUser;
  int i;

  if( *pCount==0 ){
    for(i=0; i<nArg; i++){
      output_no_tabs(azName[i]);
      cgi_printf("%c", i<nArg-1 ? '\t' : '\n');
    }
  }
  ++*pCount;
  for(i=0; i<nArg; i++){
    output_no_tabs(azArg[i]);
    cgi_printf("%c", i<nArg-1 ? '\t' : '\n');
  }
  return 0;
}

/*
** Generate HTML that describes a color key.
*/
void output_color_key(const char *zClrKey, int horiz, char *zTabArgs){
  int i, j, k;
  char *zSafeKey, *zToFree;
  while( isspace(*zClrKey) ) zClrKey++;
  if( zClrKey[0]==0 ) return;
  @ <table %s(zTabArgs)>
  if( horiz ){
    @ <tr>
  }
  zToFree = zSafeKey = mprintf("%h", zClrKey);
  while( zSafeKey[0] ){
    while( isspace(*zSafeKey) ) zSafeKey++;
    for(i=0; zSafeKey[i] && !isspace(zSafeKey[i]); i++){}
    for(j=i; isspace(zSafeKey[j]); j++){}
    for(k=j; zSafeKey[k] && zSafeKey[k]!='\n' && zSafeKey[k]!='\r'; k++){}
    if( !horiz ){
      cgi_printf("<tr bgcolor=\"%.*s\"><td>%.*s</td></tr>\n",
        i, zSafeKey, k-j, &zSafeKey[j]);
    }else{
      cgi_printf("<td bgcolor=\"%.*s\">%.*s</td>\n",
        i, zSafeKey, k-j, &zSafeKey[j]);
    }
    zSafeKey += k;
  }
  free(zToFree);
  if( horiz ){
    @ </tr>
  }
  @ </table>
}


/*
** WEBPAGE: /rptview
**
** Generate a report.  The rn query parameter is the report number
** corresponding to REPORTFMT.RN.  If the tablist query parameter exists,
** then the output consists of lines of tab-separated fields instead of
** an HTML table.
*/
void rptview_page(void){
  int count = 0;
  int rn;
  char *zSql;
  char *zTitle;
  char *zOwner;
  char *zClrKey;
  int tabs;
  Stmt q;

  login_check_credentials();
  if( !g.okRead ){ login_needed(); return; }
  rn = atoi(PD("rn","0"));
  if( rn==0 ){
    cgi_redirect("reportlist");
    return;
  }
  tabs = P("tablist")!=0;
  view_add_functions(tabs);
  db_prepare(&q,
    "SELECT title, sqlcode, owner, cols FROM reportfmt WHERE rn=%d", rn);
  if( db_step(&q)!=SQLITE_ROW ){
    cgi_redirect("reportlist");
    return;
  }
  zTitle = db_column_malloc(&q, 0);
  zSql = db_column_malloc(&q, 1);
  zOwner = db_column_malloc(&q, 2);
  zClrKey = db_column_malloc(&q, 3);
  db_finalize(&q);

  if( P("order_by") ){
    /*
    ** If the user wants to do a column sort, wrap the query into a sub
    ** query and then sort the results. This is a whole lot easier than
    ** trying to insert an ORDER BY into the query itself, especially
    ** if the query is already ordered.
    */
    int nField = atoi(P("order_by"));
    if( nField > 0 ){
      const char* zDir = PD("order_dir","");
      zDir = !strcmp("ASC",zDir) ? "ASC" : "DESC";
      zSql = mprintf("SELECT * FROM (%s) ORDER BY %d %s", zSql, nField, zDir);
    }
  }

  count = 0;
  if( !tabs ){
    struct GenerateHTML sState;

    db_execute("PRAGMA empty_result_callbacks=ON");
    style_submenu_element("Raw", "Raw", 
      "rptview?tablist=1&%s", P("QUERY_STRING",""));
    if( g.okAdmin 
       || (g.okQuery && g.zLogin && zOwner && strcmp(g.zLogin,zOwner)==0) ){
      style_submentu_element("Edit", "Edit", "rptedit?rn=%d", rn);
    }
    style_submenu_element("SQL", "SQL", "rptsql?rn=%d",rn);
    style_header(zTitle);
    output_color_key(zClrKey, 1, 
        "border=0 cellpadding=3 cellspacing=0 class=\"report\"");
    @ <table border=1 cellpadding=2 cellspacing=0 class="report">
    sState.rn = rn;
    sState.nCount = 0;
    sqlite3_exec(g.db, zSql, generate_html, &sState, 0);
    @ </table>
    style_footer();
  }else{
    sqlite3_exec(g.db, zSql, output_tab_separated, &count, 0);
    cgi_set_content_type("text/plain");
  }
}
Added src/rss.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
/*
** Copyright (c) 2007 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 used to create a RSS feed for the CGI interface.
*/
#include "config.h"
#include "rss.h"
#include <assert.h>
#include <time.h>

time_t rss_datetime_to_time_t(const char *dt){
  struct tm the_tm;

  the_tm.tm_year = atoi(dt)-1900;
  the_tm.tm_mon  = atoi(&dt[5])-1;
  the_tm.tm_mday = atoi(&dt[8]);
  the_tm.tm_hour = atoi(&dt[11]);
  the_tm.tm_min  = atoi(&dt[14]);
  the_tm.tm_sec  = atoi(&dt[17]);

  return mktime(&the_tm);
}

/*
** WEBPAGE: timeline.rss
*/

void page_timeline_rss(void){
  Stmt q;
  int nLine=0;
  char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0;
  const char zSQL[] =
    @ SELECT
    @   blob.rid,
    @   uuid,
    @   datetime(event.mtime),
    @   coalesce(ecomment,comment),
    @   coalesce(euser,user),
    @   (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim),
    @   (SELECT count(*) FROM plink WHERE cid=blob.rid)
    @ FROM event, blob
    @ WHERE blob.rid=event.objid
    @ ORDER BY event.mtime DESC
  ;

  cgi_set_content_type("application/rss+xml");

  zProjectName = db_get("project-name", 0);
  if( zProjectName==0 ){
    zFreeProjectName = zProjectName = mprintf("Fossil source repository for: %s",
      g.zBaseURL);
  }
  zProjectDescr = db_get("project-description", 0);
  if( zProjectDescr==0 ){
    zProjectDescr = zProjectName;
  }

  zPubDate = cgi_rfc822_datestamp(time(NULL));

  @ <?xml version="1.0"?>
  @ <rss version="2.0">
  @   <channel>
  @     <title>%s(zProjectName)</title>
  @     <link>%s(g.zBaseURL)</link>
  @     <description>%s(zProjectDescr)</description>
  @     <pubDate>%s(zPubDate)</pubDate>
  @     <generator>Fossil version %s(MANIFEST_VERSION) %s(MANIFEST_DATE)</generator>
  db_prepare(&q, zSQL);
  while( db_step(&q)==SQLITE_ROW && nLine<=20 ){
    const char *zId = db_column_text(&q, 1);
    const char *zDate = db_column_text(&q, 2);
    const char *zCom = db_column_text(&q, 3);
    const char *zAuthor = db_column_text(&q, 4);
    char *zPrefix = "";
    int nChild = db_column_int(&q, 5);
    int nParent = db_column_int(&q, 6);

    zDate = cgi_rfc822_datestamp(rss_datetime_to_time_t(zDate));

    if( nParent>1 && nChild>1 ){
      zPrefix = "*MERGE/FORK* ";
    }else if( nParent>1 ){
      zPrefix = "*MERGE* ";
    }else if( nChild>1 ){
      zPrefix = "*FORK* ";
    }

    @     <item>
    @       <title>%s(zPrefix)%s(zCom)</title>
    @       <link>%s(g.zBaseURL)/vinfo/%s(zId)</link>
    @       <description>%s(zPrefix)%s(zCom)</description>
    @       <pubDate>%s(zDate)</pubDate>
    @       <author>%s(zAuthor)</author>
    @       <guid>%s(g.zBaseURL)/vinfo/%s(zId)</guid>
    @     </item>
    nLine++;
  }

  db_finalize(&q);
  @   </channel>
  @ </rss>

  if( zFreeProjectName != 0 ){
    free( zFreeProjectName );
  }
}
Changes to src/schema.c.
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
@ -- in the form of name-value pairs.
@ --
@ CREATE TABLE config(
@   name TEXT PRIMARY KEY NOT NULL,  -- Primary name of the entry
@   value CLOB,                      -- Content of the named parameter
@   CHECK( typeof(name)='text' AND length(name)>=1 )
@ );


















;

const char zRepositorySchema2[] =
@ -- Filenames
@ --
@ CREATE TABLE filename(
@   fnid INTEGER PRIMARY KEY,    -- Filename ID
@   name TEXT UNIQUE             -- Name of file page
@ );
@
@ -- Linkages between manifests, files created by that manifest, and
@ -- the names of those files.
@ --



@ CREATE TABLE mlink(
@   mid INTEGER REFERENCES blob,        -- Manifest ID where change occurs
@   pid INTEGER REFERENCES blob,        -- File ID in parent manifest
@   fid INTEGER REFERENCES blob,        -- Changed file ID in this manifest
@   fnid INTEGER REFERENCES filename    -- Name of the file
@ );
@ CREATE INDEX mlink_i1 ON mlink(mid);







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

>











>
>
>







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
@ -- in the form of name-value pairs.
@ --
@ CREATE TABLE config(
@   name TEXT PRIMARY KEY NOT NULL,  -- Primary name of the entry
@   value CLOB,                      -- Content of the named parameter
@   CHECK( typeof(name)='text' AND length(name)>=1 )
@ );
@
@ -- Artifacts that should not be processed are identified in the
@ -- "shun" table.  Artifacts that are control-file forgeries or
@ -- spam can be shunned in order to prevent them from contaminating
@ -- the repository.
@ --
@ CREATE TABLE shun(uuid UNIQUE);
@
@ -- An entry in this table describes a database query that generates a
@ -- table of tickets.
@ --
@ CREATE TABLE reportfmt(
@    rn integer primary key,  -- Report number
@    owner text,              -- Owner of this report format (not used)
@    title text,              -- Title of this report
@    cols text,               -- A color-key specification
@    sqlcode text             -- An SQL SELECT statement for this report
@ );
;

const char zRepositorySchema2[] =
@ -- Filenames
@ --
@ CREATE TABLE filename(
@   fnid INTEGER PRIMARY KEY,    -- Filename ID
@   name TEXT UNIQUE             -- Name of file page
@ );
@
@ -- Linkages between manifests, files created by that manifest, and
@ -- the names of those files.
@ --
@ -- pid==0 if the file is added by check-in mid.
@ -- fid==0 if the file is removed by check-in mid.
@ --
@ CREATE TABLE mlink(
@   mid INTEGER REFERENCES blob,        -- Manifest ID where change occurs
@   pid INTEGER REFERENCES blob,        -- File ID in parent manifest
@   fid INTEGER REFERENCES blob,        -- Changed file ID in this manifest
@   fnid INTEGER REFERENCES filename    -- Name of the file
@ );
@ CREATE INDEX mlink_i1 ON mlink(mid);
150
151
152
153
154
155
156
157
158
159
160



161

162
163
164
165
166
167
168
169
170
171
172
@   UNIQUE(pid, cid)
@ );
@ CREATE INDEX plink_i2 ON plink(cid);
@
@ -- Events used to generate a timeline
@ --
@ CREATE TABLE event(
@   type TEXT,
@   mtime DATETIME,
@   objid INTEGER,
@   uid INTEGER REFERENCES user,



@   user TEXT,

@   comment TEXT
@ );
@ CREATE INDEX event_i1 ON event(mtime);
@ CREATE INDEX event_i2 ON event(objid);
@
@ -- A record of phantoms.  A phantom is a record for which we know the
@ -- UUID but we do not (yet) know the file content.
@ --
@ CREATE TABLE phantom(
@   rid INTEGER PRIMARY KEY         -- Record ID of the phantom
@ );







|
|
|
|
>
>
>
|
>
|


<







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
@   UNIQUE(pid, cid)
@ );
@ CREATE INDEX plink_i2 ON plink(cid);
@
@ -- Events used to generate a timeline
@ --
@ CREATE TABLE event(
@   type TEXT,                      -- Type of event
@   mtime DATETIME,                 -- Date and time when the event occurs
@   objid INTEGER PRIMARY KEY,      -- Associated record ID
@   uid INTEGER REFERENCES user,    -- User who caused the event
@   bgcolor TEXT,                   -- Color set by 'bgcolor' property
@   brbgcolor TEXT,                 -- Color set by 'br-bgcolor' property
@   euser TEXT,                     -- User set by 'user' property
@   user TEXT,                      -- Name of the user
@   ecomment TEXT,                  -- Comment set by 'comment' property
@   comment TEXT                    -- Comment describing the event
@ );
@ CREATE INDEX event_i1 ON event(mtime);

@
@ -- A record of phantoms.  A phantom is a record for which we know the
@ -- UUID but we do not (yet) know the file content.
@ --
@ CREATE TABLE phantom(
@   rid INTEGER PRIMARY KEY         -- Record ID of the phantom
@ );
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
@ -- used to reduce push operations to a single HTTP request in the
@ -- common case when one repository only talks to a single server.
@ --
@ CREATE TABLE unsent(
@   rid INTEGER PRIMARY KEY         -- Record ID of the phantom
@ );
@

@ -- Aggregated ticket information
@ --
@ CREATE TABLE tkt(
@   tktid INTEGER PRIMARY KEY,           -- Internal ticket ID
@   fnid INTEGER REFERENCES filename,    -- Name of the ticket file
@   rid INTEGER REFERENCES blob,         -- version of ticket file scanned
@   title TEXT,                          -- title of the ticket
@   remarks TEXT                         -- text of the ticket
@ );
@ CREATE TABLE tkttag(
@   tagid INTEGER PRIMARY KEY,           -- Numeric tag ID
@   name TEXT UNIQUE                     -- Human-readable name of tag
@ );










@ CREATE TABLE tktmap(
@   tktid INTEGER REFERENCES tkt,        -- This ticket

@   tagid INTEGER REFERENCES tkttag,     --    ....holds this tag



@   UNIQUE(tktid, tagid)
@ );
@ CREATE INDEX tktmap_i2 ON tktmap(tagid);
;











/*
** The schema for the locate FOSSIL database file found at the root
** of very check-out.  This database contains the complete state of
** the checkout.
*/
const char zLocalSchema[] =







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

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

|

>
>
>
>
>
>
>
>
>
>







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
@ -- used to reduce push operations to a single HTTP request in the
@ -- common case when one repository only talks to a single server.
@ --
@ CREATE TABLE unsent(
@   rid INTEGER PRIMARY KEY         -- Record ID of the phantom
@ );
@
@ -- Each baseline or manifest can have one or more tags.  A tag
@ -- is defined by a row in the next table.
@ -- 

@ -- Wiki pages are tagged with "wiki-NAME" where NAME is the name of
@ -- the wiki page.  Tickets changes are tagged with "ticket-UUID" where 

@ -- UUID is the indentifier of the ticket.

@ --
@ CREATE TABLE tag(
@   tagid INTEGER PRIMARY KEY,       -- Numeric tag ID
@   tagname TEXT UNIQUE              -- Tag name.
@ );
@ INSERT INTO tag VALUES(1, 'bgcolor');         -- TAG_BGCOLOR
@ INSERT INTO tag VALUES(2, 'comment');         -- TAG_COMMENT
@ INSERT INTO tag VALUES(3, 'user');            -- TAG_USER
@ INSERT INTO tag VALUES(4, 'hidden');          -- TAG_HIDDEN
@
@ -- Assignments of tags to baselines.  Note that we allow tags to
@ -- have values assigned to them.  So we are not really dealing with
@ -- tags here.  These are really properties.  But we are going to
@ -- keep calling them tags because in many cases the value is ignored.
@ --
@ CREATE TABLE tagxref(
@   tagid INTEGER REFERENCES tag,   -- The tag that added or removed
@   tagtype INTEGER,                -- 0:cancel  1:single  2:branch
@   srcid INTEGER REFERENCES blob,  -- Origin of the tag. 0 for propagated tags
@   value TEXT,                     -- Value of the tag.  Might be NULL.
@   mtime TIMESTAMP,                -- Time of addition or removal
@   rid INTEGER REFERENCE blob,     -- Baseline that tag added/removed from
@   UNIQUE(rid, tagid)
@ );
@ CREATE INDEX tagxref_i1 ON tagxref(tagid, mtime);
;

/*
** Predefined tagid values
*/
#if INTERFACE
# define TAG_BGCOLOR    1
# define TAG_COMMENT    2
# define TAG_USER       3
# define TAG_HIDDEN     4
#endif

/*
** The schema for the locate FOSSIL database file found at the root
** of very check-out.  This database contains the complete state of
** the checkout.
*/
const char zLocalSchema[] =
Changes to src/setup.c.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
** Copyright (c) 2007 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 as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** 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












|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
** Copyright (c) 2007 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 as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** 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
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

/*
** Output a single entry for a menu generated using an HTML table.
** If zLink is not NULL or an empty string, then it is the page that
** the menu entry will hyperlink to.  If zLink is NULL or "", then
** the menu entry has no hyperlink - it is disabled.
*/
static void menu_entry(
  const char *zTitle,
  const char *zLink,
  const char *zDesc
){
  @ <dt>
  if( zLink && zLink[0] ){
    @ <a href="%s(zLink)">%h(zTitle)</a>
  }else{
    @ %h(zTitle)
  }
  @ </dt>
  @ <dd>%h(zDesc)</dd>
}

/*
** WEBPAGE: /setup
*/
void setup_page(void){
  login_check_credentials();
  if( !g.okSetup ){
    login_needed();
  }

  style_header("Setup");
  @ <dl id="setup">
  menu_entry("Users", "setup_ulist",
    "Grant privileges to individual users.");
  menu_entry("Access", "setup_access",
    "Control access settings.");
  menu_entry("Configuration", "setup_config",
    "Configure the WWW components of the repository");










  @ </dl>

  style_footer();
}

/*
** WEBPAGE: setup_ulist
**
** Show a list of users.  Clicking on any user jumps to the edit
** screen for that user.
*/
void setup_ulist(void){
  Stmt s;
  
  style_footer();
  login_check_credentials();
  if( !g.okSetup ){
    login_needed();
    return;
  }

  style_submenu_element("Add", "Add User", "setup_uedit");







|




|





<
|












|
|

|

|

>
>
>
>
>
>
>
>
>
>
|












|
<







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

/*
** Output a single entry for a menu generated using an HTML table.
** If zLink is not NULL or an empty string, then it is the page that
** the menu entry will hyperlink to.  If zLink is NULL or "", then
** the menu entry has no hyperlink - it is disabled.
*/
void setup_menu_entry(
  const char *zTitle,
  const char *zLink,
  const char *zDesc
){
  @ <tr><td valign="top" align="right">
  if( zLink && zLink[0] ){
    @ <a href="%s(zLink)">%h(zTitle)</a>
  }else{
    @ %h(zTitle)
  }

  @ </td><td valign="top">%h(zDesc)</td></tr>
}

/*
** WEBPAGE: /setup
*/
void setup_page(void){
  login_check_credentials();
  if( !g.okSetup ){
    login_needed();
  }

  style_header("Setup");
  @ <table border="0" cellspacing="20">
  setup_menu_entry("Users", "setup_ulist",
    "Grant privileges to individual users.");
  setup_menu_entry("Access", "setup_access",
    "Control access settings.");
  setup_menu_entry("Configuration", "setup_config",
    "Configure the WWW components of the repository");
  setup_menu_entry("Timeline", "setup_timeline",
    "Timeline display preferences");
  setup_menu_entry("Tickets", "setup_ticket",
    "Configure the trouble-ticketing system for this repository");
  setup_menu_entry("CSS", "setup_editcss",
    "Edit the Cascading Style Sheet used by all pages of this repository");
  setup_menu_entry("Header", "setup_header",
    "Edit HTML text inserted at the top of every page");
  setup_menu_entry("Footer", "setup_footer",
    "Edit HTML text inserted at the bottom of every page");
  @ </table>

  style_footer();
}

/*
** WEBPAGE: setup_ulist
**
** Show a list of users.  Clicking on any user jumps to the edit
** screen for that user.
*/
void setup_ulist(void){
  Stmt s;


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

  style_submenu_element("Add", "Add User", "setup_uedit");
121
122
123
124
125
126
127

128
129
130
131
132
133
134
  @ <b>Notes:</b>
  @ <ol>
  @ <li><p>The permission flags are as follows:</p>
  @ <ol type="a">
  @ <li value="1"><b>Admin</b>: Create and delete users</li>
  @ <li value="3"><b>Append-Tkt</b>: Append to tickets</li>
  @ <li value="4"><b>Delete</b>: Delete wiki and tickets</li>

  @ <li value="6"><b>New-Wiki</b>: Create new wiki pages</li>
  @ <li value="7"><b>Clone</b>: Clone the repository</li>
  @ <li value="8"><b>History</b>: View detail repository history</li>
  @ <li value="9"><b>Check-In</b>: Commit new versions in the repository</li>
  @ <li value="10"><b>Read-Wiki</b>: View wiki pages</li>
  @ <li value="11"><b>Write-Wiki</b>: Edit wiki pages</li>
  @ <li value="13"><b>Append-Wiki</b>: Append to wiki pages</li>







>







129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
  @ <b>Notes:</b>
  @ <ol>
  @ <li><p>The permission flags are as follows:</p>
  @ <ol type="a">
  @ <li value="1"><b>Admin</b>: Create and delete users</li>
  @ <li value="3"><b>Append-Tkt</b>: Append to tickets</li>
  @ <li value="4"><b>Delete</b>: Delete wiki and tickets</li>
  @ <li value="5"><b>Email</b>: View EMail addresses on tickets</li>
  @ <li value="6"><b>New-Wiki</b>: Create new wiki pages</li>
  @ <li value="7"><b>Clone</b>: Clone the repository</li>
  @ <li value="8"><b>History</b>: View detail repository history</li>
  @ <li value="9"><b>Check-In</b>: Commit new versions in the repository</li>
  @ <li value="10"><b>Read-Wiki</b>: View wiki pages</li>
  @ <li value="11"><b>Write-Wiki</b>: Edit wiki pages</li>
  @ <li value="13"><b>Append-Wiki</b>: Append to wiki pages</li>
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
}

/*
** WEBPAGE: /setup_uedit
*/
void user_edit(void){
  const char *zId, *zLogin, *zInfo, *zCap;
  char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap ;
  char *oak, *oad, *oaq, *oac, *oaf, *oam, *oah, *oag;
  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
  */







|
|







164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
}

/*
** WEBPAGE: /setup_uedit
*/
void user_edit(void){
  const char *zId, *zLogin, *zInfo, *zCap;
  char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap;
  char *oak, *oad, *oaq, *oac, *oaf, *oam, *oah, *oag, *oae;
  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
  */
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
  if( doWrite ){
    const char *zPw;
    const char *zLogin;
    char zCap[30];
    int i = 0;
    int aa = P("aa")!=0;
    int ad = P("ad")!=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 ap = P("ap")!=0;
    int aq = P("aq")!=0;
    int ar = P("ar")!=0;
    int as = g.okSetup && P("as")!=0;
    int aw = P("aw")!=0;
    int ac = P("ac")!=0;
    int af = P("af")!=0;
    int am = P("am")!=0;
    int ah = P("ah")!=0;
    int ag = P("ag")!=0;
    if( aa ){ zCap[i++] = 'a'; }
    if( ac ){ zCap[i++] = 'c'; }
    if( ad ){ zCap[i++] = 'd'; }

    if( af ){ zCap[i++] = 'f'; }
    if( ah ){ zCap[i++] = 'h'; }
    if( ag ){ zCap[i++] = 'g'; }
    if( ai ){ zCap[i++] = 'i'; }
    if( aj ){ zCap[i++] = 'j'; }
    if( ak ){ zCap[i++] = 'k'; }
    if( am ){ zCap[i++] = 'm'; }







>


















>







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
  if( doWrite ){
    const char *zPw;
    const char *zLogin;
    char zCap[30];
    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 ap = P("ap")!=0;
    int aq = P("aq")!=0;
    int ar = P("ar")!=0;
    int as = g.okSetup && P("as")!=0;
    int aw = P("aw")!=0;
    int ac = P("ac")!=0;
    int af = P("af")!=0;
    int am = P("am")!=0;
    int ah = P("ah")!=0;
    int ag = P("ag")!=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'; }
    if( aj ){ zCap[i++] = 'j'; }
    if( ak ){ zCap[i++] = 'k'; }
    if( am ){ zCap[i++] = 'm'; }
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248

    zCap[i] = 0;
    zPw = P("pw");
    if( zPw==0 || zPw[0]==0 ){
      zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid);
    }
    zLogin = P("login");
    if( uid>0 && 
        db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d", zLogin, uid)
    ){
      style_header("User Creation Error");
      @ <font color="red">Login "%h(zLogin)" is already used by a different
      @ user.</font>
      @
      @ <p><a href="setup_uedit?id=%d(uid))>[Bummer]</a></p>







|







245
246
247
248
249
250
251
252
253
254
255
256
257
258
259

    zCap[i] = 0;
    zPw = P("pw");
    if( zPw==0 || zPw[0]==0 ){
      zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid);
    }
    zLogin = P("login");
    if( uid>0 &&
        db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d", zLogin, uid)
    ){
      style_header("User Creation Error");
      @ <font color="red">Login "%h(zLogin)" is already used by a different
      @ user.</font>
      @
      @ <p><a href="setup_uedit?id=%d(uid))>[Bummer]</a></p>
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274

275
276
277
278
279
280
281
  }

  /* Load the existing information about the user, if any
  */
  zLogin = "";
  zInfo = "";
  zCap = "";
  oaa = oac = oad = oaf = oag = oah = oai = oaj = oak = oam =
        oan = oao = oap = oaq = oar = oas = oaw = "";
  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);
    if( strchr(zCap, 'a') ) oaa = " checked";
    if( strchr(zCap, 'c') ) oac = " checked";
    if( strchr(zCap, 'd') ) oad = " 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";
    if( strchr(zCap, 'j') ) oaj = " checked";
    if( strchr(zCap, 'k') ) oak = " checked";
    if( strchr(zCap, 'm') ) oam = " checked";







|








>







270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
  }

  /* Load the existing information about the user, if any
  */
  zLogin = "";
  zInfo = "";
  zCap = "";
  oaa = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam =
        oan = oao = oap = oaq = oar = oas = oaw = "";
  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);
    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";
    if( strchr(zCap, 'j') ) oaj = " checked";
    if( strchr(zCap, 'k') ) oak = " checked";
    if( strchr(zCap, 'm') ) oam = " checked";
319
320
321
322
323
324
325

326
327
328
329
330
331
332
  @   <td align="right" valign="top">Capabilities:</td>
  @   <td>
  if( g.okSetup ){
    @     <input type="checkbox" name="as"%s(oas)>Setup</input><br>
  }
  @     <input type="checkbox" name="aa"%s(oaa)>Admin</input><br>
  @     <input type="checkbox" name="ad"%s(oad)>Delete</input><br>

  @     <input type="checkbox" name="ap"%s(oap)>Password</input><br>
  @     <input type="checkbox" name="aq"%s(oaq)>Query</input><br>
  @     <input type="checkbox" name="ai"%s(oai)>Check-In</input><br>
  @     <input type="checkbox" name="ao"%s(oao)>Check-Out</input><br>
  @     <input type="checkbox" name="ah"%s(oah)>History</input><br>
  @     <input type="checkbox" name="ag"%s(oag)>Clone</input><br>
  @     <input type="checkbox" name="aj"%s(oaj)>Read Wiki</input><br>







>







331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
  @   <td align="right" valign="top">Capabilities:</td>
  @   <td>
  if( g.okSetup ){
    @     <input type="checkbox" name="as"%s(oas)>Setup</input><br>
  }
  @     <input type="checkbox" name="aa"%s(oaa)>Admin</input><br>
  @     <input type="checkbox" name="ad"%s(oad)>Delete</input><br>
  @     <input type="checkbox" name="ae"%s(oad)>Email</input><br>
  @     <input type="checkbox" name="ap"%s(oap)>Password</input><br>
  @     <input type="checkbox" name="aq"%s(oaq)>Query</input><br>
  @     <input type="checkbox" name="ai"%s(oai)>Check-In</input><br>
  @     <input type="checkbox" name="ao"%s(oao)>Check-Out</input><br>
  @     <input type="checkbox" name="ah"%s(oah)>History</input><br>
  @     <input type="checkbox" name="ag"%s(oag)>Clone</input><br>
  @     <input type="checkbox" name="aj"%s(oaj)>Read Wiki</input><br>
359
360
361
362
363
364
365
366


367
368
369
370
371
372
373
374
375
376
377
378
    @ </p></li>
    @
  }
  @
  @ <li><p>
  @ The <b>Delete</b> privilege give the user the ability to erase
  @ wiki, tickets, and atttachments that have been added by anonymous
  @ users.  This capability is intended for deletion of spam.


  @ </p></li>
  @
  @ <li><p>
  @ The <b>Query</b> privilege allows the user to create or edit
  @ report formats by specifying appropriate SQL.  Users can run 
  @ existing reports without the Query privilege.
  @ </p></li>
  @
  @ <li><p>
  @ An <b>Admin</b> user can add other users, create new ticket report
  @ formats, and change system defaults.  But only the <b>Setup</b> user
  @ is able to change the repository to







|
>
>




|







372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
    @ </p></li>
    @
  }
  @
  @ <li><p>
  @ The <b>Delete</b> privilege give the user the ability to erase
  @ wiki, tickets, and atttachments that have been added by anonymous
  @ users.  This capability is intended for deletion of spam.  The
  @ delete capability is only in effect for 24 hours after the item
  @ is first posted.  The Setup user can delete anything at any time.
  @ </p></li>
  @
  @ <li><p>
  @ The <b>Query</b> privilege allows the user to create or edit
  @ report formats by specifying appropriate SQL.  Users can run
  @ existing reports without the Query privilege.
  @ </p></li>
  @
  @ <li><p>
  @ An <b>Admin</b> user can add other users, create new ticket report
  @ formats, and change system defaults.  But only the <b>Setup</b> user
  @ is able to change the repository to
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











































































































































  }
  if( zQ==0 && P("submit") ){
    zQ = "off";
  }
  if( zQ ){
    int iQ = strcmp(zQ,"on")==0 || atoi(zQ);
    if( iQ!=iVal ){
      db_set(zVar, iQ ? "1" : "0");
      iVal = iQ;
    }
  }
  if( iVal ){
    @ <input type="checkbox" name="%s(zQParm)" checked>%s(zLabel)</input>
  }else{
    @ <input type="checkbox" name="%s(zQParm)">%s(zLabel)</input>
  }
}

/*
** Generate an entry box for an attribute.
*/
static void entry_attribute(
  const char *zLabel,   /* The text label on the entry box */
  int width,            /* Width of the entry box */
  const char *zVar,     /* The corresponding row in the VAR table */
  const char *zQParm,   /* The query parameter */
  const char *zDflt     /* Default value if VAR table entry does not exist */
){
  const char *zVal = db_get(zVar, zDflt);
  const char *zQ = P(zQParm);
  if( zQ && strcmp(zQ,zVal)!=0 ){
    db_set(zVar, zQ);
    zVal = zQ;
  }
  @ <input type="text" name="%s(zQParm)" value="%h(zVal)" size="%d(width)">
  @ %s(zLabel)
}

























/*
** WEBPAGE: setup_access
*/
void setup_access(void){
  login_check_credentials();
  if( !g.okSetup ){
    login_needed();
  }

  style_header("Access Control Settings");
  db_begin_transaction();
  @ <form action="%s(g.zBaseURL)/setup_access" method="POST">

  @ <hr>
  onoff_attribute("Require password for local access",
     "authenticate-localhost", "localauth", 1);
  @ <p>When enabled, the password sign-in is required for
  @ web access coming from 127.0.0.1.  When disabled, web access
  @ from 127.0.0.1 is allows without any login - the user id is selected
  @ from the ~/.fossil database. Password login is always required
  @ for incoming web connections on internet addresses other than
  @ 127.0.0.1.</p></li>

  @ <hr>
  entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766");
  @ <p>The number of hours for which a login is valid.  This must be a
  @ positive number.  The default is 8760 hours which is approximately equal
  @ to a year.</p>
   
  @ <hr>





  onoff_attribute("Allow anonymous signup", "anon-signup", "asu", 0);








  @ <p>Allow users to create their own accounts</p>



   












  @ <hr>
  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>
  @ </form>
  db_end_transaction(0);
  style_footer();
}

/*
** WEBPAGE: setup_config
*/
void setup_config(void){
  login_check_credentials();
  if( !g.okSetup ){
    login_needed();
  }

  style_header("WWW Configuration");
  db_begin_transaction();
  @ <form action="%s(g.zBaseURL)/setup_config" method="POST">

  @ <hr>
  entry_attribute("Home page", 60, "homepage", "hp", "");
  @ <p>The name of a wiki file that is the homepage for the website.

  @ The home page is the page that is displayed by the "Home" link
  @ at the top of this screen.  Omit the path and the ".wiki"
  @ suffix.  </p>

  entry_attribute("Ticket subdirectory", 60, "ticket-subdir", "tsd", "");
  @ <p>A subdirectory in the file hierarchy that contains all trouble
  @ tickets.  Leave this blank to disable ticketing.  Tickets text
  @ files within this subdirectory containing a particular format
  @ (documented separately) and with the ".tkt" suffix.</p>

  entry_attribute("Wiki subdirectory", 60, "wiki-subdir", "wsd", "");
  @ <p>A subdirectory in the file hierarchy that contains wiki pages.
  @ Leave this blank to disable wiki.  Wiki pages are
  @ files within this subdirectory whose name is he wiki page title
  @ and with the suffix ".wiki".</p>

   
  @ <hr>
  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>
  @ </form>
  db_end_transaction(0);
  style_footer();
}


















































































































































|


















|




|






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

















|












|

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



















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





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
556
557
558
559
560
561
562
563
564
565
566
567
568
569
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
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
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
  }
  if( zQ==0 && P("submit") ){
    zQ = "off";
  }
  if( zQ ){
    int iQ = strcmp(zQ,"on")==0 || atoi(zQ);
    if( iQ!=iVal ){
      db_set(zVar, iQ ? "1" : "0", 0);
      iVal = iQ;
    }
  }
  if( iVal ){
    @ <input type="checkbox" name="%s(zQParm)" checked>%s(zLabel)</input>
  }else{
    @ <input type="checkbox" name="%s(zQParm)">%s(zLabel)</input>
  }
}

/*
** Generate an entry box for an attribute.
*/
static void entry_attribute(
  const char *zLabel,   /* The text label on the entry box */
  int width,            /* Width of the entry box */
  const char *zVar,     /* The corresponding row in the VAR table */
  const char *zQParm,   /* The query parameter */
  char *zDflt     /* Default value if VAR table entry does not exist */
){
  const char *zVal = db_get(zVar, zDflt);
  const char *zQ = P(zQParm);
  if( zQ && strcmp(zQ,zVal)!=0 ){
    db_set(zVar, zQ, 0);
    zVal = zQ;
  }
  @ <input type="text" name="%s(zQParm)" value="%h(zVal)" size="%d(width)">
  @ %s(zLabel)
}

/*
** Generate a text box for an attribute.
*/
static void textarea_attribute(
  const char *zLabel,   /* The text label on the textarea */
  int rows,             /* Rows in the textarea */
  int cols,             /* Columns in the textarea */
  const char *zVar,     /* The corresponding row in the VAR table */
  const char *zQP,      /* The query parameter */
  const char *zDflt     /* Default value if VAR table entry does not exist */
){
  const char *z = db_get(zVar, (char*)zDflt);
  const char *zQ = P(zQP);
  if( zQ && strcmp(zQ,z)!=0 ){
    db_set(zVar, zQ, 0);
    z = zQ;
  }
  if( rows>0 && cols>0 ){
    @ <textarea name="%s(zQP)" rows="%d(rows)" cols="%d(cols)">%h(z)</textarea>
    @ %s(zLabel)
  }
}


/*
** WEBPAGE: setup_access
*/
void setup_access(void){
  login_check_credentials();
  if( !g.okSetup ){
    login_needed();
  }

  style_header("Access Control Settings");
  db_begin_transaction();
  @ <form action="%s(g.zBaseURL)/setup_access" method="POST">

  @ <hr>
  onoff_attribute("Require password for local access",
     "localauth", "localauth", 1);
  @ <p>When enabled, the password sign-in is required for
  @ web access coming from 127.0.0.1.  When disabled, web access
  @ from 127.0.0.1 is allows without any login - the user id is selected
  @ from the ~/.fossil database. Password login is always required
  @ for incoming web connections on internet addresses other than
  @ 127.0.0.1.</p></li>

  @ <hr>
  entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766");
  @ <p>The number of hours for which a login is valid.  This must be a
  @ positive number.  The default is 8760 hours which is approximately equal
  @ to a year.</p>

  @ <hr>
  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>
  @ </form>
  db_end_transaction(0);
  style_footer();
}

/*
** WEBPAGE: setup_timeline
*/
void setup_timeline(void){
  login_check_credentials();
  if( !g.okSetup ){
    login_needed();
  }

  style_header("Timeline Display Preferences");
  db_begin_transaction();
  @ <form action="%s(g.zBaseURL)/setup_timeline" method="POST">

  @ <hr>
  onoff_attribute("Block markup in timeline",
                  "timeline-block-markup", "tbm", 0);
  @ <p>In timeline displays, check-in comments can be displayed with or
  @ without block markup (paragraphs, tables, etc.)</p>

  @ <hr>
  entry_attribute("Max timeline comment length", 6, 
                  "timeline-max-comment", "tmc", "0");
  @ <p>The maximum length of a comment to be displayed in a timeline.
  @ "0" there is no length limit.</p>

  @ <hr>
  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>
  @ </form>
  db_end_transaction(0);
  style_footer();
}

/*
** WEBPAGE: setup_config
*/
void setup_config(void){
  login_check_credentials();
  if( !g.okSetup ){
    login_needed();
  }

  style_header("WWW Configuration");
  db_begin_transaction();
  @ <form action="%s(g.zBaseURL)/setup_config" method="POST">

  @ <hr />
  entry_attribute("Project Name", 60, "project-name", "pn", "");

  @ <p>Give your project a name so visitors know what this site is about.
  @ The project name will also be used as the RSS feed title.</p>


  @ <hr />
  textarea_attribute("Project Description", 5, 60,




                     "project-description", "pd", "");





  @ <p>Describe your project. This will be used in page headers for search
  @ engines as well as a short RSS description.</p>
  @ <hr />
  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>
  @ </form>
  db_end_transaction(0);
  style_footer();
}

/*
** WEBPAGE: setup_editcss
*/
void setup_editcss(void){
  login_check_credentials();
  if( !g.okSetup ){
    login_needed();
  }
  style_header("Edit CSS");
  @ <form action="%s(g.zBaseURL)/setup_editcss" method="POST">
  @ Edit the CSS:<br />
  textarea_attribute("", 40, 80, "css", "css", zDefaultCSS);
  @ <br />
  @ <input type="submit" name="submit" value="Apply Changes">
  @ </form>
  @ <hr>
  @ Here is the default CSS:
  @ <blockquote><pre>
  @ %h(zDefaultCSS)
  @ </pre></blockquote>
  style_footer();
}

/*
** WEBPAGE: setup_header
*/
void setup_header(void){
  login_check_credentials();
  if( !g.okSetup ){
    login_needed();
  }
  db_begin_transaction();
  if( P("clear")!=0 ){
    db_multi_exec("DELETE FROM config WHERE name='header'");
    cgi_replace_parameter("header", zDefaultHeader);
  }else{
    textarea_attribute(0, 0, 0, "header", "header", zDefaultHeader);
  }
  style_header("Edit Page Header");
  @ <form action="%s(g.zBaseURL)/setup_header" method="POST">
  @ <p>Edit HTML text with embedded subscript that will be used to
  @ generate the beginning of every page through start of the main
  @ menu.</p>
  textarea_attribute("", 40, 80, "header", "header", zDefaultHeader);
  @ <br />
  @ <input type="submit" name="submit" value="Apply Changes">
  @ <input type="submit" name="clear" value="Revert To Default">
  @ </form>
  @ <hr>
  @ Here is the default page header:
  @ <blockquote><pre>
  @ %h(zDefaultHeader)
  @ </pre></blockquote>
  db_end_transaction(0);
}

/*
** WEBPAGE: setup_footer
*/
void setup_footer(void){
  login_check_credentials();
  if( !g.okSetup ){
    login_needed();
  }
  db_begin_transaction();
  if( P("clear")!=0 ){
    db_multi_exec("DELETE FROM config WHERE name='footer'");
    cgi_replace_parameter("footer", zDefaultFooter);
  }else{
    textarea_attribute(0, 0, 0, "footer", "footer", zDefaultFooter);
  }
  style_header("Edit Page Footer");
  @ <form action="%s(g.zBaseURL)/setup_footer" method="POST">
  @ <p>Edit HTML text with embedded subscript that will be used to
  @ generate the end of every page.</p>
  textarea_attribute("", 20, 80, "footer", "footer", zDefaultFooter);
  @ <br />
  @ <input type="submit" name="submit" value="Apply Changes">
  @ <input type="submit" name="clear" value="Revert To Default">
  @ </form>
  @ <hr>
  @ Here is the default page footer:
  @ <blockquote><pre>
  @ %h(zDefaultFooter)
  @ </pre></blockquote>
  db_end_transaction(0);
}

/*
** WEBPAGE: setup_ticket
*/
void setup_ticket(void){
  const char *zConfig;
  int isSubmit;
  
  login_check_credentials();
  if( !g.okSetup ){
    login_needed();
  }
  isSubmit = P("submit")!=0;
  db_begin_transaction();
  zConfig = P("cfg");
  if( zConfig==0 ){
    zConfig = db_text((char*)zDefaultTicketConfig,
               "SELECT value FROM config WHERE name='ticket-configuration'");
  }
  style_header("Edit Ticket Configuration");
  if( P("clear")!=0 ){
    db_multi_exec("DELETE FROM config WHERE name='ticket-configuration'");
    zConfig = zDefaultTicketConfig;
  }else if( isSubmit ){
    char *zErr = ticket_config_check(zConfig);
    if( zErr==0 ){
      db_multi_exec(
         "REPLACE INTO config(name,value) VALUES('ticket-configuration',"
         "%Q)", zConfig
      );
    }else{
      @ <p><font color="red"><b>
      @ SCRIPT ERROR: %h(zErr)
      @ </b></font></p>
    }
  }
  @ <form action="%s(g.zBaseURL)/setup_ticket" method="POST">
  @ <p>Edit the "subscript" script that defines the ticketing
  @ system setup for this server.</p>
  @ <textarea name="cfg" rows="40" cols="80">%h(zConfig)</textarea>
  @ <br />
  @ <input type="submit" name="submit" value="Apply Changes">
  @ <input type="submit" name="clear" value="Revert To Default">
  @ </form>
  @ <hr>
  @ Here is the default page header:
  @ <blockquote><pre>
  @ %h(zDefaultTicketConfig)
  @ </pre></blockquote>
  db_end_transaction(0);
}
Changes to src/sha1.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
/*
** This implementation of SHA1 is adapted from the example implementation
** contained in RFC-3174.
*/
#include <stdint.h>

#include "config.h"
#include "sha1.h"

/*
 * If you do not have the ISO standard stdint.h header file, then you
 * must typdef the following:
 *    name              meaning
 *  uint32_t         unsigned 32 bit integer
 *  uint8_t          unsigned 8 bit integer (i.e., unsigned char)
 *  int_least16_t    integer of >= 16 bits
 *
 */
#define SHA1HashSize 20
#define shaSuccess 0
#define shaInputTooLong 1
#define shaStateError 2

/*
 *  This structure will hold context information for the SHA-1
 *  hashing operation
 */
typedef struct SHA1Context SHA1Context;
struct SHA1Context {
    uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest  */

    uint32_t Length_Low;            /* Message length in bits      */
    uint32_t Length_High;           /* Message length in bits      */

                               /* Index into message block array   */
    int_least16_t Message_Block_Index;
    uint8_t Message_Block[64];      /* 512-bit message blocks      */

    int Computed;               /* Is the digest computed?         */
    int Corrupted;             /* Is the message digest corrupted? */
};

/*





>









<


















|
<







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
/*
** This implementation of SHA1 is adapted from the example implementation
** contained in RFC-3174.
*/
#include <stdint.h>
#include <sys/types.h>
#include "config.h"
#include "sha1.h"

/*
 * If you do not have the ISO standard stdint.h header file, then you
 * must typdef the following:
 *    name              meaning
 *  uint32_t         unsigned 32 bit integer
 *  uint8_t          unsigned 8 bit integer (i.e., unsigned char)

 *
 */
#define SHA1HashSize 20
#define shaSuccess 0
#define shaInputTooLong 1
#define shaStateError 2

/*
 *  This structure will hold context information for the SHA-1
 *  hashing operation
 */
typedef struct SHA1Context SHA1Context;
struct SHA1Context {
    uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest  */

    uint32_t Length_Low;            /* Message length in bits      */
    uint32_t Length_High;           /* Message length in bits      */

    int Message_Block_Index;   /* Index into message block array   */

    uint8_t Message_Block[64];      /* 512-bit message blocks      */

    int Computed;               /* Is the digest computed?         */
    int Corrupted;             /* Is the message digest corrupted? */
};

/*
Changes to src/sqlite3.c.

more than 10,000 changes

Changes to src/style.c.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
** Copyright (c) 2006 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

|









|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
** Copyright (c) 2006,2007 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
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

/*
** Add a new element to the submenu
*/
void style_submenu_element(
  const char *zLabel,
  const char *zTitle,
  const char *zLink

){

  assert( nSubmenu < sizeof(aSubmenu)/sizeof(aSubmenu[0]) );
  aSubmenu[nSubmenu].zLabel = zLabel;
  aSubmenu[nSubmenu].zTitle = zTitle;

  aSubmenu[nSubmenu].zLink = zLink;

  nSubmenu++;
}

/*
** Compare two submenu items for sorting purposes
*/
static int submenuCompare(const void *a, const void *b){
  const struct Submenu *A = (const struct Submenu*)a;
  const struct Submenu *B = (const struct Submenu*)B;
  return strcmp(A->zLabel, B->zLabel);
}

/*





** Draw the header.
*/
void style_header(const char *zTitle){
  const char *zLogInOut = "Logout";

  login_check_credentials();
  @ <html>

  @ <body bgcolor="white">
  @ <hr size="1">
  @ <table border="0" cellpadding="0" cellspacing="0" width="100%%">
  @ <tr><td valign="top" align="left">




  @ <big><big><b>%s(zTitle)</b></big></big><br>



  if( g.zLogin==0 ){
    @ <small>not logged in</small>

    zLogInOut = "Login";
  }else{
    @ <small>logged in as %h(g.zLogin)</small>
  }

  @ </td><td valign="top" align="right">


  @ <a href="%s(g.zBaseURL)/index">Home</a>
  if( g.okRead ){
    @ | <a href="%s(g.zBaseURL)/leaves">Leaves</a>
    @ | <a href="%s(g.zBaseURL)/timeline">Timeline</a>
  }
  if( g.okRdWiki ){
    @ | <a href="%s(g.zBaseURL)/wiki">Wiki</a>
  }
#if 0
  @ | <font color="#888888">Search</font>
  @ | <font color="#888888">Ticket</font>
  @ | <font color="#888888">Reports</font>
#endif
  if( g.okSetup ){
    @ | <a href="%s(g.zBaseURL)/setup">Setup</a>
  }
  if( !g.noPswd ){
    @ | <a href="%s(g.zBaseURL)/login">%s(zLogInOut)</a>
  }

  if( nSubmenu>0 ){
    int i;
    @ <br>
    qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare);
    for(i=0; i<nSubmenu; i++){
      struct Submenu *p = &aSubmenu[i];
      char *zTail = i<nSubmenu-1 ? " | " : "";
      if( p->zLink==0 ){
        @ <font color="#888888">%h(p->zLabel)</font> %s(zTail)
      }else{
        @ <a href="%T(p->zLink)">%h(p->zLabel)</a> %s(zTail)
      }
    }

  }
  @ </td></tr></table>
  @ <hr size="1">
  g.cgiPanic = 1;
}

/*
** Draw the footer at the bottom of the page.
*/
void style_footer(void){

}










































































































































































/*
** WEBPAGE: index
** WEBPAGE: home
** WEBPAGE: not_found
*/
void page_index(void){
  char *zHome = db_get("homepage", 0);
  if( zHome ){
    g.zExtra = zHome;
    g.okRdWiki = 1;
    wiki_page();
  }else{
    style_header("Main Title Page");
    @ No homepage configured for this server
    style_footer();
  }



}

/*
** WEBPAGE: test_env
*/
void page_test_env(void){
  style_header("Environment Test");


  cgi_print_all();
  style_footer();
}







|
>

>



>
|
>








|




>
>
>
>
>



|
>

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

>
|
>
>
|

|
|


|


|
|
|


|


|

>


|



<

|

|


>

<
|







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

|
<
<

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







>
>



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

/*
** Add a new element to the submenu
*/
void style_submenu_element(
  const char *zLabel,
  const char *zTitle,
  const char *zLink,
  ...
){
  va_list ap;
  assert( nSubmenu < sizeof(aSubmenu)/sizeof(aSubmenu[0]) );
  aSubmenu[nSubmenu].zLabel = zLabel;
  aSubmenu[nSubmenu].zTitle = zTitle;
  va_start(ap, zLink);
  aSubmenu[nSubmenu].zLink = vmprintf(zLink, ap);
  va_end(ap);
  nSubmenu++;
}

/*
** Compare two submenu items for sorting purposes
*/
static int submenuCompare(const void *a, const void *b){
  const struct Submenu *A = (const struct Submenu*)a;
  const struct Submenu *B = (const struct Submenu*)b;
  return strcmp(A->zLabel, B->zLabel);
}

/*
** The Subscript interpreter used to render header and footer.
*/
static struct Subscript *pInterp;

/*
** Draw the header.
*/
void style_header(const char *zTitle){
  const char *zLogInOut = "Login";
  const char *zHeader = db_get("header", (char*)zDefaultHeader);  
  login_check_credentials();
  
  if( pInterp ) return;




  /* Generate the header up through the main menu */
  pInterp = SbS_Create();
  SbS_Store(pInterp, "project_name",
                     db_get("project-name","Unnamed Fossil Project"), 0);
  SbS_Store(pInterp, "title", zTitle, 0);
  SbS_Store(pInterp, "baseurl", g.zBaseURL, 0);
  SbS_Store(pInterp, "manifest_version", MANIFEST_VERSION, 0);
  SbS_Store(pInterp, "manifest_date", MANIFEST_DATE, 0);
  if( g.zLogin ){

    SbS_Store(pInterp, "login", g.zLogin, 0);
    zLogInOut = "Logout";


  }
  SbS_Render(pInterp, zHeader);

  /* Generate the main menu and the submenu (if any) */
  @ <div class="mainmenu">
  @ <a href="%s(g.zBaseURL)/home">Home</a>
  if( g.okRead ){
    @ <a href="%s(g.zBaseURL)/leaves">Leaves</a>
    @ <a href="%s(g.zBaseURL)/timeline">Timeline</a>
  }
  if( g.okRdWiki ){
    @ <a href="%s(g.zBaseURL)/wiki">Wiki</a>
  }
#if 0
  @ <font color="#888888">Search</font>
  @ <font color="#888888">Ticket</font>
  @ <font color="#888888">Reports</font>
#endif
  if( g.okSetup ){
    @ <a href="%s(g.zBaseURL)/setup">Setup</a>
  }
  if( !g.noPswd ){
    @ <a href="%s(g.zBaseURL)/login">%s(zLogInOut)</a>
  }
  @ </div>
  if( nSubmenu>0 ){
    int i;
    @ <div class="submenu">
    qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare);
    for(i=0; i<nSubmenu; i++){
      struct Submenu *p = &aSubmenu[i];

      if( p->zLink==0 ){
        @ <span class="label">%h(p->zLabel)</span>
      }else{
        @ <a class="label" href="%s(p->zLink)">%h(p->zLabel)</a>
      }
    }
    @ </div>
  }

  @ <div class="content">
  g.cgiPanic = 1;
}

/*
** Draw the footer at the bottom of the page.
*/
void style_footer(void){
  const char *zFooter;
  
  if( pInterp==0 ) return;
  zFooter = db_get("footer", (char*)zDefaultFooter);
  @ </div>
  SbS_Render(pInterp, zFooter);
  SbS_Destroy(pInterp);
  pInterp = 0;
}

/* @-comment: // */
/*
** The default page header.
*/
const char zDefaultHeader[] = 
@ <html>
@ <head>
@ <title>[project_name html]: [title html]</title>
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
@       href="[baseurl puts]/timeline.rss">
@ <link rel="stylesheet" href="[baseurl puts]/style.css" type="text/css"
@       media="screen">
@ </head>
@ <body>
@ <div class="header">
@   <div class="logo">
@     <!-- <img src="logo.gif" alt="logo"><br></br> -->
@     <nobr>[project_name html]</nobr>
@   </div>
@   <div class="title">[title html]</div>
@   <div class="status"><nobr>
@     [/login exists enable_output]     Logged in as [0 /login get html]
@     [/login exists not enable_output] Not logged in
@     [1 enable_output]
@   </nobr></div>
@ </div>
;

/*
** The default page footer
*/
const char zDefaultFooter[] = 
@ <div class="footer">
@ Fossil version [manifest_version puts] [manifest_date puts]
@ </div>
@ </body></html>
;

/*
** The default Cascading Style Sheet.
*/
const char zDefaultCSS[] = 
@ /* General settings for the entire page */
@ body {
@   margin: 0ex 1ex;
@   padding: 0px;
@   background-color: white;
@   font-family: "sans serif";
@ }
@
@ /* The project logo in the upper left-hand corner of each page */
@ div.logo {
@   display: table-cell;
@   text-align: center;
@   vertical-align: bottom;
@   font-weight: bold;
@   color: #558195;
@ }
@
@ /* The page title centered at the top of each page */
@ div.title {
@   display: table-cell;
@   font-size: 2em;
@   font-weight: bold;
@   text-align: center;
@   color: #558195;
@   vertical-align: bottom;
@   width: 100%;
@ }
@
@ /* The login status message in the top right-hand corner */
@ div.status {
@   display: table-cell;
@   text-align: right;
@   vertical-align: bottom;
@   color: #558195;
@   font-size: 0.8em;
@   font-weight: bold;
@ }
@
@ /* The header across the top of the page */
@ div.header {
@   display: table;
@   width: 100%;
@ }
@
@ /* The main menu bar that appears at the top of the page beneath
@ ** the header */
@ div.mainmenu {
@   padding: 5px 10px 5px 10px;
@   font-size: 0.9em;
@   font-weight: bold;
@   text-align: center;
@   letter-spacing: 1px;
@   background-color: #558195;
@   color: white;
@ }
@
@ /* The submenu bar that *sometimes* appears below the main menu */
@ div.submenu {
@   padding: 3px 10px 3px 0px;
@   font-size: 0.9em;
@   text-align: center;
@   background-color: #456878;
@   color: white;
@ }
@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited {
@   padding: 3px 10px 3px 10px;
@   color: white;
@   text-decoration: none;
@ }
@ div.mainmenu a:hover, div.submenu a:hover {
@   color: #558195;
@   background-color: white;
@ }
@
@ /* All page content from the bottom of the menu or submenu down to
@ ** the footer */
@ div.content {
@   padding: 0ex 1ex 0ex 2ex;
@ }
@
@ /* Some pages have section dividers */
@ div.section {
@   margin-bottom: 0px;
@   margin-top: 1em;
@   padding: 1px 1px 1px 1px;
@   font-size: 1.2em;
@   font-weight: bold;
@   background-color: #558195;
@   color: white;
@ }
@
@ /* The "Date" that occurs on the left hand side of timelines */
@ div.divider {
@   background: #a1c4d4;
@   border: 2px #558195 solid;
@   font-size: 1em; font-weight: normal;
@   padding: .25em;
@   margin: .2em 0 .2em 0;
@   float: left;
@   clear: left;
@ }
@
@ /* The footer at the very bottom of the page */
@ div.footer {
@   font-size: 0.8em;
@   margin-top: 12px;
@   padding: 5px 10px 5px 10px;
@   text-align: right;
@   background-color: #558195;
@   color: white;
@ }
@
@ /* The label/value pairs on (for example) the vinfo page */
@ table.label-value th {
@   vertical-align: top;
@   text-align: right;
@   padding: 0.2ex 2ex;
@ }
;

/*
** WEBPAGE: style.css


*/
void page_style_css(void){
  char *zCSS = 0;









  cgi_set_content_type("text/css");
  zCSS = db_get("css",(char*)zDefaultCSS);
  cgi_append_content(zCSS, -1);
}

/*
** WEBPAGE: test_env
*/
void page_test_env(void){
  style_header("Environment Test");
  @ g.zBaseURL = %h(g.zBaseURL)<br>
  @ g.zTop = %h(g.zTop)<br>
  cgi_print_all();
  style_footer();
}
Added src/subscript.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
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
556
557
558
559
560
561
562
563
564
565
566
567
568
569
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
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
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
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
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
/*
** Copyright (c) 2007 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 an implementation of the "subscript" interpreter.
**
** Subscript attempts to be an extremely light-weight scripting
** language.  It contains the barest of bare essentials.  It is
** stack-based and forth-like.  Everything is in a single global
** namespace.  There is only a single datatype of zero-terminated
** string.  The stack is of fixed, limited depth.  The symbal table
** is of a limited and fixed size.
**
** TOKENS:
**
**      *  All tokens are separated from each other by whitespace.
**      *  Leading and trailing whitespace is ignored.
**      *  Text within nested {...} is a single string token.  The outermost
**         curly braces are not part of the token.
**      *  An identifier with a leading "/" is a string token.
**      *  A token that looks like a number is a string token.
**      *  An identifier token is called a "verb".
**
** PROCESSING:
**
**      *  The input is divided into tokens.  Whitespace is discarded.
**         String and verb tokens are passed into the engine.
**      *  String tokens are pushed onto the stack.
**      *  If a verb token corresponds to a procedure, that procedure is
**         run.  The procedure might use, pop, or pull elements from 
**         the stack.
**      *  If a verb token corresponds to a variable, the value of that
**         variable is pushed onto the stack.
**
** This module attempts to be completely self-contained so that it can
** be portable to other projects.
*/
#include "config.h"
#include "subscript.h"
#include <assert.h>

#if INTERFACE
typedef struct Subscript Subscript;
#define SBS_OK      0
#define SBS_ERROR   1
#define SBS_RETURN  2
#define SBS_BREAK   3
#endif

/*
** Configuration constants
*/
#define SBSCONFIG_NHASH    41         /* Size of the hash table */
#define SBSCONFIG_NSTACK   20         /* Maximum stack depth */
#define SBSCONFIG_ERRSIZE  100        /* Maximum size of an error message */

/*
** Available token types:
*/
#define SBSTT_WHITESPACE  1    /* ex:   \040   */
#define SBSTT_NAME        2    /* ex:   /abcde  */
#define SBSTT_VERB        3    /* ex:   abcde   */
#define SBSTT_STRING      4    /* ex:   {...}   */
#define SBSTT_QUOTED      5    /* ex:   "...\n..." */
#define SBSTT_INTEGER     6    /* Integer including option sign */
#define SBSTT_INCOMPLETE  7    /* Unterminated string token */
#define SBSTT_UNKNOWN     8    /* Unknown token */
#define SBSTT_EOF         9    /* End of input */

/*
** Values are stored in the hash table as instances of the following
** structure.
*/
typedef struct SbSValue SbSValue;
struct SbSValue {
  int flags;        /* Bitmask of SBSVAL_* values */
  union {
    struct {
      int size;        /* Number of bytes in string, not counting final zero */
      char *z;         /* Pointer to string content */
    } str;          /* Value if SBSVAL_STR */
    struct {
      int (*xVerb)(Subscript*, void*);     /* Function to do the work */
      void *pArg;                          /* 2nd parameter to xVerb */
    } verb;         /* Value if SBSVAL_VERB */
  } u;              
};
#define SBSVAL_VERB    0x0001      /* Value stored in u.verb */
#define SBSVAL_STR     0x0002      /* Value stored in u.str */ 
#define SBSVAL_DYN     0x0004      /* u.str.z is dynamically allocated */
#define SBSVAL_EXEC    0x0008      /* u.str.z is a script */

/*
** An entry in the hash table is an instance of this structure.
*/
typedef struct SbsHashEntry SbsHashEntry;
struct SbsHashEntry {
  SbsHashEntry *pNext;     /* Next entry with the same hash on zKey */
  SbSValue val;            /* The payload */
  int nKey;               /* Length of the key */
  char zKey[1];           /* The key */
};

/*
** A hash table is an instance of the following structure.
*/
typedef struct SbsHashTab SbsHashTab;
struct SbsHashTab {
  SbsHashEntry *aHash[SBSCONFIG_NHASH];  /* The hash table */
};

/*
** An instance of the Subscript interpreter
*/
struct Subscript {
  int nStack;                        /* Number of entries on stack */
  SbsHashTab symTab;                 /* The symbol table */
  char zErrMsg[SBSCONFIG_ERRSIZE];   /* Space to write an error message */
  SbSValue aStack[SBSCONFIG_NSTACK]; /* The stack */
};


/*
** Given an input string z of length n, identify the token that
** starts at z[0].  Write the token type into *pTokenType and
** return the length of the token.
*/
static int sbs_next_token(const char *z, int n, int *pTokenType){
  int c;
  if( n<=0 || z[0]==0 ){
    *pTokenType = SBSTT_EOF;
    return 0;
  }
  c = z[0];
  if( isspace(c) ){
    int i;
    *pTokenType = SBSTT_WHITESPACE;
    for(i=1; i<n && isspace(z[i]); i++){}
    return i;
  }
  if( c=='#' ){
    int i;
    for(i=1; i<n && z[i] && z[i-1]!='\n'; i++){}
    *pTokenType = SBSTT_WHITESPACE;
    return i;
  }
  if( c=='"' ){
    int i;
    for(i=1; i<n && z[i] && z[i]!='"'; i++){
       if( z[i]=='\\' && i<n-1 ){ i++; }
    }
    if( z[i]=='"' ){
      *pTokenType = SBSTT_QUOTED;
      return i+1;
    }else{
      *pTokenType = SBSTT_INCOMPLETE;
      return i;
    }
  }
  if( c=='{' ){
    int depth = 1;
    int i;
    for(i=1; i<n && z[i]; i++){
      if( z[i]=='{' ){
        depth++;
      }else if( z[i]=='}' ){
        depth--;
        if( depth==0 ){
          i++;
          break;
        }
      }
    }
    if( depth ){
      *pTokenType = SBSTT_INCOMPLETE;
    }else{
      *pTokenType = SBSTT_STRING;
    }
    return i;
  }
  if( c=='/' && n>=2 && isalpha(z[1]) ){
    int i;
    for(i=2; i<n && (isalnum(z[i]) || z[i]=='_'); i++){}
    *pTokenType = SBSTT_NAME;
    return i;
  }
  if( isalpha(c) ){
    int i;
    for(i=1; i<n && (isalnum(z[i]) || z[i]=='_'); i++){}
    *pTokenType = SBSTT_VERB;
    return i;
  }
  if( isdigit(c) || ((c=='-' || c=='+') && n>=2 && isdigit(z[1])) ){
    int i;
    for(i=1; i<n && isdigit(z[i]); i++){}
    *pTokenType = SBSTT_INTEGER;
    return i;
  }
  *pTokenType = SBSTT_UNKNOWN;
  return 1;
}


/*
** Release any memory allocated by a value.
*/
static void sbs_value_reset(SbSValue *p){
  if( p->flags & SBSVAL_DYN ){
    free(p->u.str.z);
    p->flags = SBSVAL_STR;
    p->u.str.z = "";
    p->u.str.size = 0;
  }
}

/*
** Compute a hash on a string.
*/
static int sbs_hash(const char *z, int n){
  int h = 0;
  int i;
  for(i=0; i<n; i++){
    h ^= (h<<1) | z[i];
  }
  h &= 0x7ffffff;
  return h % SBSCONFIG_NHASH;
}

/*
** Look up a value in the hash table.  Return a pointer to the value.
** Return NULL if not found.
*/
static const SbSValue *sbs_fetch(
  SbsHashTab *pHash, 
  const char *zKey, 
  int nKey
){
  int h;
  SbsHashEntry *p;

  if( nKey<0 ) nKey = strlen(zKey);
  h = sbs_hash(zKey, nKey);
  for(p = pHash->aHash[h]; p; p=p->pNext){
    if( p->nKey==nKey && memcmp(p->zKey,zKey,nKey)==0 ){
      return &p->val;
    }
  }
  return 0;
}

/*
** Store a value in the hash table.  Overwrite any prior value stored
** under the same name.
**
** If the value in the 4th argument needs to be reset or freed,
** the hash table will take over responsibiliity for doing so.
*/
static int sbs_store(
  SbsHashTab *pHash,       /* Insert into this hash table */
  const char *zKey,        /* The key */
  int nKey,                /* Size of the key */
  const SbSValue *pValue   /* The value to be stored */
){
  int h;
  SbsHashEntry *p, *pNew;

  if( nKey<0 ) nKey = strlen(zKey);
  h = sbs_hash(zKey, nKey);
  for(p = pHash->aHash[h]; p; p=p->pNext){
    if( p->nKey==nKey && memcmp(p->zKey,zKey,nKey)==0 ){
      sbs_value_reset(&p->val);
      memcpy(&p->val, pValue, sizeof(p->val));
      return SBS_OK;
    }
  }
  pNew = malloc( sizeof(*pNew) + nKey );
  if( pNew ){
    pNew->nKey = nKey;
    memcpy(pNew->zKey, zKey, nKey+1);
    memcpy(&pNew->val, pValue, sizeof(pNew->val));
    pNew->pNext = pHash->aHash[h];
    pHash->aHash[h] = pNew;
    return SBS_OK;
  }
  return SBS_ERROR;
}

/*
** Reset a hash table.
*/
static void sbs_hash_reset(SbsHashTab *pHash){
  int i;
  SbsHashEntry *p, *pNext;
  for(i=0; i<SBSCONFIG_NHASH; i++){
    for(p=pHash->aHash[i]; p; p=pNext){
      pNext = p->pNext;
      sbs_value_reset(&p->val);
      free(p);
    }
  }
  memset(pHash, 0, sizeof(*pHash));
}

/*
** Push a value onto the stack of an interpreter
*/
static int sbs_push(Subscript *p, SbSValue *pVal){
  if( p->nStack>=SBSCONFIG_NSTACK ){
    sqlite3_snprintf(SBSCONFIG_ERRSIZE, p->zErrMsg, "stack overflow");
    return SBS_ERROR;
  }
  p->aStack[p->nStack++] = *pVal;
  return SBS_OK;
}

/*
** Create a new subscript interpreter.  Return a pointer to the
** new interpreter, or return NULL if malloc fails.
*/
struct Subscript *SbS_Create(void){
  Subscript *p;
  p = malloc( sizeof(*p) );
  if( p ){
    memset(p, 0, sizeof(*p));
  }
  return p;
}

/*
** Destroy an subscript interpreter
*/
void SbS_Destroy(struct Subscript *p){
  int i;
  sbs_hash_reset(&p->symTab);
  for(i=0; i<p->nStack; i++){
    sbs_value_reset(&p->aStack[i]);
  }
  free(p);
}

/*
** Set the error message for an interpreter.  Verb implementations
** use this routine when they encounter an error.
*/
void SbS_SetErrorMessage(struct Subscript *p, const char *zErr, ...){
  int nErr;
  char *zMsg;
  va_list ap;

  va_start(ap, zErr);
  zMsg = vmprintf(zErr, ap);
  va_end(ap);
  nErr = strlen(zMsg);
  if( nErr>sizeof(p->zErrMsg)-1 ){
    nErr = sizeof(p->zErrMsg)-1;
  }
  memcpy(p->zErrMsg, zMsg, nErr);
  p->zErrMsg[nErr] = 0;
  free(zMsg);
}

/*
** Return a pointer to the current error message for the
** interpreter.
*/
const char *SbS_GetErrorMessage(struct Subscript *p){
  return p->zErrMsg;
}

/*
** Add a new verb the given interpreter
*/
int SbS_AddVerb(
  struct Subscript *p,
  const char *zVerb,
  int (*xVerb)(struct Subscript*,void*),
  void *pArg
){
  SbSValue v;
  v.flags = SBSVAL_VERB;
  v.u.verb.xVerb = xVerb;
  v.u.verb.pArg = pArg;
  return sbs_store(&p->symTab, zVerb, -1, &v);
}

/*
** Store a value in an interpreter variable.
*/
int SbS_Store(
  struct Subscript *p,   /* Store into this interpreter */
  const char *zName,     /* Name of the variable */
  const char *zValue,    /* Value of the variable */
  int persistence        /* 0: static.  1: ephemeral.  2: dynamic */
){
  SbSValue v;
  v.flags = SBSVAL_STR;
  v.u.str.size = strlen(zValue);
  if( persistence==1 ){
    v.u.str.z = mprintf("%s", zValue);
  }else{
    v.u.str.z = (char*)zValue;
  }
  if( persistence>0 ){
    v.flags |= SBSVAL_DYN;
  }
  return sbs_store(&p->symTab, zName, -1, &v);
}

/*
** Push a string value onto the stack.
**
** If the 4th parameter is 0, then the string is static.
** If the 4th parameter is non-zero then the string was obtained
** from malloc and Subscript will take responsibility for freeing
** it.
**
** Return 0 on success and non-zero if there is an error.
*/
int SbS_Push(
  struct Subscript *p,  /* Push onto this interpreter */
  char *z,              /* String value to push */
  int n,                /* Length of the string, or -1 */
  int dyn               /* If true, z was obtained from malloc */
){
  SbSValue v;
  v.flags = SBSVAL_STR;
  if( dyn ){
    v.flags |= SBSVAL_DYN;
  }
  if( n<0 ) n = strlen(z);
  v.u.str.size = n;
  v.u.str.z = z;
  return sbs_push(p, &v);
}

/*
** Push an integer value onto the stack.
**
** This routine really just converts the integer into a string
** then calls SbS_Push.
*/
int SbS_PushInt(struct Subscript *p, int iVal){
  if( iVal==0 ){
    return SbS_Push(p, "0", 1, 0);
  }else if( iVal==1 ){
    return SbS_Push(p, "1", 1, 0);
  }else{
    char *z;
    int n;
    char zVal[50];
    sprintf(zVal, "%d", iVal);
    n = strlen(zVal);
    z = malloc( n+1 );
    if( z ){
      strcpy(z, zVal);
      return SbS_Push(p, z, n, 1);
    }else{
      return SBS_ERROR;
    }
  }
}

/*
** Pop and destroy zero or more values from the stack.
** Return the number of values remaining on the stack after
** the pops occur.
*/
int SbS_Pop(struct Subscript *p, int N){
  while( N>0 && p->nStack>0 ){
    p->nStack--;
    sbs_value_reset(&p->aStack[p->nStack]);
    N--;
  }
  return p->nStack;
}

/*
** Return the N-th element of the stack.  0 is the top of the stack.
** 1 is the first element down.  2 is the second element.  And so forth.
** Return NULL if there is no N-th element.
**
** The pointer returned is only valid until the value is popped
** from the stack.
*/
const char *SbS_StackValue(struct Subscript *p, int N, int *pSize){
  SbSValue *pVal;
  if( N<0 || N>=p->nStack ){
    return 0;
  }
  pVal = &p->aStack[p->nStack-N-1];
  if( (pVal->flags & SBSVAL_STR)==0 ){
    return 0;
  }
  *pSize = pVal->u.str.size;
  return pVal->u.str.z;
}

/*
** A convenience routine for extracting an integer value from the
** stack.
*/
int SbS_StackValueInt(struct Subscript *p, int N){
  int n, v;
  int isNeg = 0;
  const char *z = SbS_StackValue(p, N, &n);
  v = 0;
  if( n==0 ) return 0;
  if( z[0]=='-' ){
    isNeg = 1;
    z++;
    n--;
  }else if( z[0]=='+' ){
    z++;
    n--;
  }
  while( n>0 && isdigit(z[0]) ){
    v = v*10 + z[0] - '0';
    z++;
    n--;
  }
  if( isNeg ){
    v = -v;
  }
  return v;
}

/*
** Retrieve the value of a variable from the interpreter.  Return
** NULL if no such variable is defined.  
**
** The returned string is not necessarily (probably not) zero-terminated.
** The string may be deallocated the next time anything is done to
** the interpreter.  Make a copy if you need it to persist.
*/
const char *SbS_Fetch(
  struct Subscript *p,   /* The interpreter we are interrogating */
  const char *zKey,        /* Name of the variable.  Case sensitive */
  int nKey,                /* Length of the key */
  int *pLength             /* Write the length here */
){
  const SbSValue *pVal;

  pVal = sbs_fetch(&p->symTab, zKey, nKey);
  if( pVal==0 || (pVal->flags & SBSVAL_STR)==0 ){
    *pLength = 0;
    return 0;
  }else{
    *pLength = pVal->u.str.size;
    return pVal->u.str.z;
  }
}

/*
** Generate an error and return non-zero if the stack has
** fewer than N elements.  This is utility routine used in
** the implementation of verbs.
*/
int SbS_RequireStack(struct Subscript *p, int N, const char *zCmd){
  if( p->nStack>=N ) return 0;
  sqlite3_snprintf(sizeof(p->zErrMsg), p->zErrMsg,
     "\"%s\" requires at least %d stack elements - only found %d",
     zCmd, N, p->nStack);
  return 1;
}

/*
** Subscript command:       STRING NAME set
**
** Write the value of STRING into variable called NAME.
*/
static int setCmd(Subscript *p, void *pNotUsed){
  SbSValue *pTos;
  SbSValue *pNos;
  if( SbS_RequireStack(p, 2, "set") ) return SBS_ERROR;
  pTos = &p->aStack[--p->nStack];
  pNos = &p->aStack[--p->nStack];
  sbs_store(&p->symTab, pTos->u.str.z, pTos->u.str.size, pNos);
  sbs_value_reset(pTos);
  return 0;
}

/*
** Subscript command:      INTEGER not INTEGER
*/
static int notCmd(struct Subscript *p, void *pNotUsed){
  int n;
  if( SbS_RequireStack(p, 1, "not") ) return 1;
  n = SbS_StackValueInt(p, 0);
  SbS_Pop(p, 1);
  SbS_PushInt(p, !n);
  return 0;
}

#define SBSOP_ADD   1
#define SBSOP_SUB   2
#define SBSOP_MUL   3
#define SBSOP_DIV   4
#define SBSOP_AND   5
#define SBSOP_OR    6
#define SBSOP_MIN   7
#define SBSOP_MAX   8
#define SBSOP_EQ    9
#define SBSOP_NE   10
#define SBSOP_LT   11
#define SBSOP_GT   12
#define SBSOP_LE   13
#define SBSOP_GE   14

/*
** Subscript command:      INTEGER INTEGER <binary-op> INTEGER
*/
static int bopCmd(struct Subscript *p, void *pOp){
  int a, b, c;
  if( SbS_RequireStack(p, 2, "BINARY-OP") ) return 1;
  a = SbS_StackValueInt(p, 1);
  b = SbS_StackValueInt(p, 0);
  switch( (int)pOp ){
    case SBSOP_EQ:   c = a==b;           break;
    case SBSOP_NE:   c = a!=b;           break;
    case SBSOP_LT:   c = a<b;            break;
    case SBSOP_LE:   c = a<=b;           break;
    case SBSOP_GT:   c = a>b;            break;
    case SBSOP_GE:   c = a>=b;           break;
    case SBSOP_ADD:  c = a+b;            break;
    case SBSOP_SUB:  c = a-b;            break;
    case SBSOP_MUL:  c = a*b;            break;
    case SBSOP_DIV:  c = b!=0 ? a/b : 0; break;
    case SBSOP_AND:  c = a && b;         break;
    case SBSOP_OR:   c = a || b;         break;
    case SBSOP_MIN:  c = a<b ? a : b;    break;
    case SBSOP_MAX:  c = a<b ? b : a;    break;
  }
  SbS_Pop(p, 2);
  SbS_PushInt(p, c);
  return 0;
}

/*
** Subscript command:      STRING STRING streq INTEGER
*/
static int streqCmd(struct Subscript *p, void *pOp){
  int c, nA, nB;
  const char *A, *B;
  
  if( SbS_RequireStack(p, 2, "BINARY-OP") ) return 1;
  A = SbS_StackValue(p, 1, &nA);
  B = SbS_StackValue(p, 0, &nB);
  c = nA==nB && memcmp(A,B,nA)==0;
  SbS_Pop(p, 2);
  SbS_PushInt(p, c);
  return 0;
}

/*
** Subscript command:     STRING hascap INTEGER
**
** Return true if the user has all of the capabilities listed.
*/
static int hascapCmd(struct Subscript *p, void *pNotUsed){
  const char *z;
  int n, a;
  if( SbS_RequireStack(p, 1, "hascap") ) return 1;
  z = SbS_StackValue(p, 0, &n);
  a = login_has_capability(z, n);
  SbS_Pop(p, 1);
  SbS_PushInt(p, a);
  return 0;
}

/*
** Subscript command:     STRING linecount INTEGER
**
** Return one more than the number of \n characters in STRING.
*/
static int linecntCmd(struct Subscript *p, void *pNotUsed){
  const char *z;
  int size, n, i;
  if( SbS_RequireStack(p, 1, "linecount") ) return 1;
  z = SbS_StackValue(p, 0, &size);
  for(n=1, i=0; i<size; i++){
    if( z[i]=='\n' ) n++;
  }
  SbS_Pop(p, 1);
  SbS_PushInt(p, n);
  return 0;
}

/*
** Subscript command:     STRING length INTEGER
**
** Return one more than the number characters in STRING.
*/
static int lengthCmd(struct Subscript *p, void *pNotUsed){
  int size;
  if( SbS_RequireStack(p, 1, "length") ) return 1;
  SbS_StackValue(p, 0, &size);
  SbS_Pop(p, 1);
  SbS_PushInt(p, size);
  return 0;
}

/*
** Subscript command:     NAME exists INTEGER
**
** Return TRUE if the variable NAME exists.
*/
static int existsCmd(struct Subscript *p, void *pNotUsed){
  const char *z;
  int size, x;
  if( SbS_RequireStack(p, 1, "exists") ) return 1;
  z = SbS_StackValue(p, 0, &size);
  x = sbs_fetch(&p->symTab, (char*)z, size)!=0;
  SbS_Pop(p, 1);
  SbS_PushInt(p, x);
  return 0;
}

/*
** Subscript command:       VALUE NAME get VALUE
**
** Push the value of varible NAME onto the stack if it exists.
** If NAME does not exist, puts VALUE onto the stack instead.
*/
static int getCmd(Subscript *p, void *pNotUsed){
  const char *zName;
  int nName;
  const SbSValue *pVal;
  int rc = 0;
  
  if( SbS_RequireStack(p, 2, "get") ) return SBS_ERROR;
  zName = SbS_StackValue(p, 0, &nName);
  pVal = sbs_fetch(&p->symTab, (char*)zName, nName);
  if( pVal!=0 && (pVal->flags & SBSVAL_STR)!=0 ){
    SbS_Pop(p, 2);
    rc = SbS_Push(p, pVal->u.str.z, pVal->u.str.size, 0);
  }else{
    SbS_Pop(p, 1);
  }
  return rc;
}



/*
** True if output is enabled.  False if disabled.
*/
static int enableOutput = 1;

/*
** Subscript command:      BOOLEAN enable_output
**
** Enable or disable the puts and hputs commands.
*/
static int enableOutputCmd(struct Subscript *p, void *pNotUsed){
  if( SbS_RequireStack(p, 1, "enable_output") ) return 1;
  enableOutput = SbS_StackValueInt(p, 0)!=0;
  SbS_Pop(p, 1);
  return 0;
}

/*
** Send text to the appropriate output:  Either to the console
** or to the CGI reply buffer.
*/
static void sendText(const char *z, int n){
  if( enableOutput && n ){
    if( n<0 ) n = strlen(z);
    if( g.cgiPanic ){
      cgi_append_content(z, n);
    }else{
      fwrite(z, 1, n, stdout);
    }
  }
}

/*
** Subscript command:      STRING puts
** Subscript command:      STRING html
**
** Output STRING as HTML (html) or unchanged (puts).  
** Pop it from the stack.
*/
static int putsCmd(struct Subscript *p, void *pConvert){
  int size;
  const char *z;
  char *zOut;
  if( SbS_RequireStack(p, 1, "puts") ) return 1;
  if( enableOutput ){
    z = SbS_StackValue(p, 0, &size);
    if( pConvert ){    
      zOut = htmlize(z, size);
      size = strlen(zOut);
    }else{
      zOut = (char*)z;
    }
    sendText(zOut, size);
    if( pConvert ){
      free(zOut);
    }
  }
  SbS_Pop(p, 1);
  return 0;
}

/*
** Subscript command:      STRING wiki
**
** Render the input string as wiki.
*/
static int wikiCmd(struct Subscript *p, void *pConvert){
  int size;
  const char *z;
  if( SbS_RequireStack(p, 1, "wiki") ) return 1;
  if( enableOutput ){
    Blob src;
    z = SbS_StackValue(p, 0, &size);
    blob_init(&src, z, size);
    wiki_convert(&src, 0, WIKI_INLINE);
    blob_reset(&src);
  }
  SbS_Pop(p, 1);
  return 0;
}

/*
** Subscript command:   NAME TEXT-LIST NUMLINES combobox
**
** Generate an HTML combobox.  NAME is both the name of the
** CGI parameter and the name of a variable that contains the
** currently selected value.  TEXT-LIST is a list of possible
** values for the combobox.  NUMLINES is 1 for a true combobox.
** If NUMLINES is greater than one then the display is a listbox
** with the number of lines given.
*/
static int comboboxCmd(struct Subscript *p, void *NotUsed){
  if( SbS_RequireStack(p, 3, "combobox") ) return 1;
  if( enableOutput ){
    int height;
    Blob list, elem;
    char *zName, *zList;
    int nName, nList, nValue;
    const char *zValue;
    char *z, *zH;

    height = SbS_StackValueInt(p, 0);
    zList = (char*)SbS_StackValue(p, 1, &nList);
    blob_init(&list, zList, nList);
    zName = (char*)SbS_StackValue(p, 2, &nName);
    zValue = SbS_Fetch(p, zName, nName, &nValue);
    z = mprintf("<select name=\"%z\" size=\"%d\">", 
                 htmlize(zName, nName), height);
    sendText(z, -1);
    free(z);
    while( blob_token(&list, &elem) ){
      zH = htmlize(blob_buffer(&elem), blob_size(&elem));
      if( zValue && blob_size(&elem)==nValue 
             && memcmp(zValue, blob_buffer(&elem), nValue)==0 ){
        z = mprintf("<option value=\"%s\" selected>%s</option>", zH, zH);
      }else{
        z = mprintf("<option value=\"%s\">%s</option>", zH, zH);
      }
      free(zH);
      sendText(z, -1);
      free(z);
    }
    sendText("</select>", -1);
    blob_reset(&list);
  }
  SbS_Pop(p, 3);
  return 0;
}

/*
** Subscript command:     STRING BOOLEAN if
**
** Evaluate STRING as a script if BOOLEAN is true.
*/
static int ifCmd(struct Subscript *p, void *pNotUsed){
  int cond;
  int rc = SBS_OK;

  if( SbS_RequireStack(p, 2, "if") ) return 1;
  cond = SbS_StackValueInt(p, 0);
  if( cond ){
    SbSValue script = p->aStack[p->nStack-2];
    p->aStack[p->nStack-2].flags = 0;
    SbS_Pop(p, 2);
    rc = SbS_Eval(p, script.u.str.z, script.u.str.size);
    sbs_value_reset(&script);
  }else{
    SbS_Pop(p, 2);
  }
  return rc;
}

/*
** Subscript command:     ... STRING COUNT concat STRING
**
** Concatenate COUNT strings into a single string and leave
** the concatenation on the stack. 
*/
static int concatCmd(struct Subscript *p, void *pNotUsed){
  int count;
  int nByte;
  char *z;
  int i, j;

  if( SbS_RequireStack(p, 1, "concat") ) return SBS_ERROR;
  count = SbS_StackValueInt(p, 0);
  if( SbS_RequireStack(p, count+1, "concat") ) return SBS_ERROR;
  SbS_Pop(p, 1);
  nByte = 1;
  for(i=p->nStack-count; i<p->nStack; i++){
    nByte += p->aStack[i].u.str.size;
  }
  z = malloc(nByte);
  if( z==0 ){ fossil_panic("out of memory"); }
  for(j=0, i=p->nStack-count; i<p->nStack; i++){
    nByte = p->aStack[i].u.str.size;
    memcpy(&z[j], p->aStack[i].u.str.z, nByte);
    j += nByte;
  }
  z[j] = 0;
  SbS_Pop(p, count);
  SbS_Push(p, z, j, 1);
  return SBS_OK;
}


/*
** A table of built-in commands
*/
static const struct {
  const char *zCmd;
  int (*xCmd)(Subscript*,void*);
  void *pArg;
} aBuiltin[] = {
  { "add",             bopCmd,               (void*)SBSOP_AND    },
  { "and",             bopCmd,               (void*)SBSOP_AND    },
  { "combobox",        comboboxCmd,          0,                  },
  { "concat",          concatCmd,            0,                  },
  { "div",             bopCmd,               (void*)SBSOP_DIV    },
  { "enable_output",   enableOutputCmd,      0                   },
  { "eq",              bopCmd,               (void*)SBSOP_EQ     },
  { "exists",          existsCmd,            0,                  },
  { "get",             getCmd,               0,                  },
  { "hascap",          hascapCmd,            0                   },
  { "html",            putsCmd,              (void*)1            },
  { "if",              ifCmd,                0,                  },
  { "le",              bopCmd,               (void*)SBSOP_LE     },
  { "length",          lengthCmd,            0                   },
  { "linecount",       linecntCmd,           0                   },
  { "lt",              bopCmd,               (void*)SBSOP_LT     },
  { "max",             bopCmd,               (void*)SBSOP_MAX    },
  { "min",             bopCmd,               (void*)SBSOP_MIN    },
  { "mul",             bopCmd,               (void*)SBSOP_MUL    },
  { "not",             notCmd,               0                   },
  { "or",              bopCmd,               (void*)SBSOP_OR     },
  { "puts",            putsCmd,              0                   },
  { "set",             setCmd,               0                   },
  { "streq",           streqCmd,             0                   },
  { "sub",             bopCmd,               (void*)SBSOP_SUB    },
  { "wiki",            wikiCmd,              0,                  },
};
  

/*
** Compare a zero-terminated string zPattern against
** an unterminated string zStr of length nStr.
**
** Return less than, equal to, or greater than zero if
** zPattern is less than, equal to, or greater than zStr.
*/
static int compare_cmd(const char *zPattern, const char *zStr, int nStr){
  int c = strncmp(zPattern, zStr, nStr);
  if( c==0 && zPattern[nStr]!=0 ){
    c = 1;
  }
  return c;
}

/*
** Evaluate the script given by the first nScript bytes of zScript[].
** Return 0 on success and non-zero for an error.
*/
int SbS_Eval(struct Subscript *p, const char *zScript, int nScript){
  int rc = SBS_OK;
  if( nScript<0 ) nScript = strlen(zScript);
  while( nScript>0 && rc==SBS_OK ){
    int n;
    int ttype;
    n = sbs_next_token(zScript, nScript, &ttype);

#if 0
    {
      int i, nElem;
      const char *zElem;
      if( p->nStack>0 ){
        printf("STACK:");
        for(i=0; i<p->nStack; i++){
          zElem = SbS_StackValue(p, i, &nElem);
          printf(" [%.*s]", nElem, zElem);
        }
        printf("\n");
      }
      printf("TOKEN(%d): [%.*s]\n", ttype, n, zScript);
    }
#endif

    switch( ttype ){
      case SBSTT_WHITESPACE: {
        break;
      }
      case SBSTT_EOF: {
        nScript = 0;
        break;
      }
      case SBSTT_INCOMPLETE:
      case SBSTT_UNKNOWN: {
        rc = SBS_ERROR;
        nScript = n;
        break;
      }
      case SBSTT_INTEGER: {
        rc = SbS_Push(p, (char*)zScript, n, 0);
        break;
      }
      case SBSTT_NAME: {
        rc = SbS_Push(p, (char*)&zScript[1], n-1, 0);
        break;
      }
      case SBSTT_STRING: {
        rc = SbS_Push(p, (char*)&zScript[1], n-2, 0);
        break;
      }
      case SBSTT_QUOTED: {
        char *z = mprintf("%.*s", n-2, &zScript[1]);
        int i, j;
        for(i=j=0; z[i]; i++, j++){
          int c = z[i];
          if( c=='\\' && z[i+1] ){
            c = z[++i];
            if( c=='n' ){
              c = '\n';
            }else if( c>='0' && c<='7' ){
              int k;
              c -= '0';
              for(k=1; k<3 && z[i+k]>='0' && z[i+k]<='7'; k++){
                c = c*8 + z[i+k] - '0';
              }
              i += k-1;
            }
          }
          z[j] = c;
        }
        z[j] = 0;
        rc = SbS_Push(p, z, j, 1);
        break;
      }
      case SBSTT_VERB: {
        /* First look up the verb in the hash table */
        const SbSValue *pVal = sbs_fetch(&p->symTab, (char*)zScript, n);
        if( pVal==0 ){
          /* If the verb is not in the hash table, look for a 
          ** built-in command */
          int upr = sizeof(aBuiltin)/sizeof(aBuiltin[0]) - 1;
          int lwr = 0;
          rc = SBS_ERROR;
          while( upr>=lwr ){
            int i = (upr+lwr)/2;
            int c = compare_cmd(aBuiltin[i].zCmd, zScript, n);
            if( c==0 ){
              rc = aBuiltin[i].xCmd(p, aBuiltin[i].pArg);
              break;
            }else if( c>0 ){
              upr = i-1;
            }else{
              lwr = i+1;
            }
          }
          if( upr<lwr ){
            SbS_SetErrorMessage(p, "unknown verb: %.*s", n, zScript);
          }
        }else if( pVal->flags & SBSVAL_VERB ){
          rc = pVal->u.verb.xVerb(p, pVal->u.verb.pArg);
        }else if( pVal->flags & SBSVAL_EXEC ){
          rc = SbS_Eval(p, pVal->u.str.z, pVal->u.str.size);
        }else{
          rc = SbS_Push(p, pVal->u.str.z, pVal->u.str.size, 0);
        }
        break;
      }
    }
    zScript += n;
    nScript -= n;
  }
  return rc;
}

/*
** The z[] input contains text mixed with subscript scripts.
** The subscript scripts are contained within [...].  This routine
** processes the template and writes the results on either
** stdout or into CGI.
*/
int SbS_Render(struct Subscript *p, const char *z){
  int i = 0;
  int rc = SBS_OK;
  while( z[i] ){
    if( z[i]=='[' ){
      sendText(z, i);
      z += i+1;
      for(i=0; z[i] && z[i]!=']'; i++){}
      rc = SbS_Eval(p, z, i);
      if( rc!=SBS_OK ) break;
      if( z[i] ) i++;
      z += i;
      i = 0;
    }else{
      i++;
    }
  }
  if( rc==SBS_ERROR ){
    sendText("<hr><p><font color=\"red\"><b>ERROR: ", -1);
    sendText(SbS_GetErrorMessage(p), -1);
    sendText("</b></font></p>", -1);
  }else{
    sendText(z, i);
  }
  return rc;
}

/*
** COMMAND: test-subscript
*/
void test_subscript(void){
  Subscript *p;
  Blob in;
  if( g.argc<3 ){
    usage("FILE");
  }
  blob_zero(&in);
  blob_read_from_file(&in, g.argv[2]);
  p = SbS_Create();
  SbS_Render(p, blob_str(&in));
}
Changes to src/sync.c.
22
23
24
25
26
27
28




























29
30
31
32
33
34
35
*******************************************************************************
**
** This file contains code used to push, pull, and sync a repository
*/
#include "config.h"
#include "sync.h"
#include <assert.h>





























/*
** This routine processes the command-line argument for push, pull,
** and sync.  If a command-line argument is given, that is the URL
** of a server to sync against.  If no argument is given, use the
** most recently synced URL.  Remember the current URL for next time.
*/







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







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
*******************************************************************************
**
** This file contains code used to push, pull, and sync a repository
*/
#include "config.h"
#include "sync.h"
#include <assert.h>

/*
** If the respository is configured for autosyncing, then do an
** autosync.  This will be a pull if the argument is true or a push
** if the argument is false.  Return true if the autosync is done
** and false if autosync is not requested for the current repository.
*/
int autosync(int pullFlag){
  const char *zUrl;
  if( db_get_boolean("autosync", 0)==0 ){
    return 0;
  }
  zUrl = db_get("last-sync-url", 0);
  if( zUrl==0 ){
    return 0;  /* No default server */
  }
  url_parse(zUrl);
  if( g.urlIsFile ){
    return 0;  /* Network sync only */
  }
  if( g.urlPort!=80 ){
    printf("Autosync:  http://%s:%d%s\n", g.urlName, g.urlPort, g.urlPath);
  }else{
    printf("Autosync:  http://%s%s\n", g.urlName, g.urlPath);
  }
  client_sync(!pullFlag, pullFlag, 0);
  return 1;
}

/*
** This routine processes the command-line argument for push, pull,
** and sync.  If a command-line argument is given, that is the URL
** of a server to sync against.  If no argument is given, use the
** most recently synced URL.  Remember the current URL for next time.
*/
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
  if( zUrl==0 ){
    usage("URL");
  }
  url_parse(zUrl);
  if( g.urlIsFile ){
    fossil_fatal("network sync only");
  }
  db_set("last-sync-url", zUrl);
  user_select();
  if( g.argc==2 ){
    if( g.urlPort!=80 ){
      printf("Server:    http://%s:%d%s\n", g.urlName, g.urlPort, g.urlPath);
    }else{
      printf("Server:    http://%s%s\n", g.urlName, g.urlPath);
    }







|







72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
  if( zUrl==0 ){
    usage("URL");
  }
  url_parse(zUrl);
  if( g.urlIsFile ){
    fossil_fatal("network sync only");
  }
  db_set("last-sync-url", zUrl, 0);
  user_select();
  if( g.argc==2 ){
    if( g.urlPort!=80 ){
      printf("Server:    http://%s:%d%s\n", g.urlName, g.urlPort, g.urlPath);
    }else{
      printf("Server:    http://%s%s\n", g.urlName, g.urlPath);
    }
Added src/tag.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
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
/*
** Copyright (c) 2007 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 used to manage tags
*/
#include "config.h"
#include "tag.h"
#include <assert.h>

/*
** Propagate the tag given by tagid to the children of pid.
**
** This routine assumes that tagid is a tag that should be
** propagated and that the tag is already present in pid.
**
** If tagtype is 2 then the tag is being propagated from an
** ancestor node.  If tagtype is 0 it means a branch tag is
** being cancelled.
*/
void tag_propagate(
  int pid,             /* Propagate the tag to children of this node */
  int tagid,           /* Tag to propagate */
  int tagType,         /* 2 for a propagating tag.  0 for an antitag */
  const char *zValue,  /* Value of the tag.  Might be NULL */
  double mtime         /* Timestamp on the tag */
){
  PQueue queue;
  Stmt s, ins, eventupdate;

  assert( tagType==0 || tagType==2 );
  pqueue_init(&queue);
  pqueue_insert(&queue, pid, 0.0);
  db_prepare(&s, 
     "SELECT cid, plink.mtime,"
     "       coalesce(srcid=0 AND tagxref.mtime<:mtime, %d) AS doit"
     "  FROM plink LEFT JOIN tagxref ON cid=rid AND tagid=%d"
     " WHERE pid=:pid AND isprim",
     tagType!=0, tagid
  );
  db_bind_double(&s, ":mtime", mtime);
  if( tagType==2 ){
    db_prepare(&ins,
       "REPLACE INTO tagxref(tagid, tagtype, srcid, value, mtime, rid)"
       "VALUES(%d,2,0,%Q,:mtime,:rid)",
       tagid, zValue
    );
    db_bind_double(&ins, ":mtime", mtime);
  }else{
    zValue = 0;
    db_prepare(&ins,
       "DELETE FROM tagxref WHERE tagid=%d AND rid=:rid", tagid
    );
  }
  if( tagid==TAG_BGCOLOR ){
    db_prepare(&eventupdate,
      "UPDATE event SET brbgcolor=%Q WHERE objid=:rid", zValue
    );
  }
  while( (pid = pqueue_extract(&queue))!=0 ){
    db_bind_int(&s, ":pid", pid);
    while( db_step(&s)==SQLITE_ROW ){
      int doit = db_column_int(&s, 2);
      if( doit ){
        int cid = db_column_int(&s, 0);
        double mtime = db_column_double(&s, 1);
        pqueue_insert(&queue, cid, mtime);
        db_bind_int(&ins, ":rid", cid);
        db_step(&ins);
        db_reset(&ins);
        if( tagid==TAG_BGCOLOR ){
          db_bind_int(&eventupdate, ":rid", cid);
          db_step(&eventupdate);
          db_reset(&eventupdate);
        }
      }
    }
    db_reset(&s);
  }
  pqueue_clear(&queue);
  db_finalize(&ins);
  db_finalize(&s);
  if( tagid==TAG_BGCOLOR ){
    db_finalize(&eventupdate);
  }
}

/*
** Propagate all propagatable tags in pid to its children.
*/
void tag_propagate_all(int pid){
  Stmt q;
  db_prepare(&q,
     "SELECT tagid, tagtype, mtime, value FROM tagxref"
     " WHERE rid=%d"
     "   AND (tagtype=0 OR tagtype=2)",
     pid
  );
  while( db_step(&q)==SQLITE_ROW ){
    int tagid = db_column_int(&q, 0);
    int tagtype = db_column_int(&q, 1);
    double mtime = db_column_double(&q, 2);
    const char *zValue = db_column_text(&q, 3);
    tag_propagate(pid, tagid, tagtype, zValue, mtime);
  }
  db_finalize(&q);
}

/*
** Get a tagid for the given TAG.  Create a new tag if necessary
** if createFlag is 1.
*/
int tag_findid(const char *zTag, int createFlag){
  int id;
  id = db_int(0, "SELECT tagid FROM tag WHERE tagname=%Q", zTag);
  if( id==0 && createFlag ){
    db_multi_exec("INSERT INTO tag(tagname) VALUES(%Q)", zTag);
    id = db_last_insert_rowid();
  }
  return id;
}

/*
** Insert a tag into the database.
*/
void tag_insert(
  const char *zTag,        /* Name of the tag (w/o the "+" or "-" prefix */
  int tagtype,             /* 0:cancel  1:singleton  2:propagated */
  const char *zValue,      /* Value if the tag is really a property */
  int srcId,               /* Artifact that contains this tag */
  double mtime,            /* Timestamp.  Use default if <=0.0 */
  int rid                  /* Artifact to which the tag is to attached */
){
  Stmt s;
  const char *zCol;
  int tagid = tag_findid(zTag, 1);
  int rc;

  if( mtime<=0.0 ){
    mtime = db_double(0.0, "SELECT julianday('now')");
  }
  db_prepare(&s,
    "SELECT 1 FROM tagxref"
    " WHERE tagid=%d"
    "   AND rid=%d"
    "   AND mtime>=:mtime",
    tagid, rid
  );
  db_bind_double(&s, ":mtime", mtime);
  rc = db_step(&s);
  db_finalize(&s);
  if( rc==SQLITE_ROW ){
    /* Another entry this is more recent already exists.  Do nothing */
    return;
  }
  db_prepare(&s, 
    "REPLACE INTO tagxref(tagid,tagtype,srcId,value,mtime,rid)"
    " VALUES(%d,%d,%d,%Q,:mtime,%d)",
    tagid, tagtype, srcId, zValue, rid
  );
  db_bind_double(&s, ":mtime", mtime);
  db_step(&s);
  db_finalize(&s);
  if( tagtype==0 ){
    zValue = 0;
  }
  zCol = 0;
  switch( tagid ){
    case TAG_BGCOLOR: {
      if( tagtype==1 ){
        zCol = "bgcolor";
      }else{
        zCol = "brbgcolor";
      }
      break;
    }
    case TAG_COMMENT: {
      zCol = "ecomment";
      break;
    }
    case TAG_USER: {
      zCol = "euser";
      break;
    }
  }
  if( zCol ){
    db_multi_exec("UPDATE event SET %s=%Q WHERE objid=%d", zCol, zValue, rid);
  }
  if( tagtype==0 || tagtype==2 ){
    tag_propagate(rid, tagid, tagtype, zValue, mtime);
  }
}


/*
** COMMAND: test-tag
** %fossil test-tag (+|*|-)TAGNAME UUID ?VALUE?
**
** Add a tag or anti-tag to the rebuildable tables of the local repository.
** No tag artifact is created so the new tag is erased the next
** time the repository is rebuilt.  This routine is for testing
** use only.
*/
void testtag_cmd(void){
  const char *zTag;
  const char *zValue;
  int rid;
  int tagtype;
  db_must_be_within_tree();
  if( g.argc!=4 && g.argc!=5 ){
    usage("TAGNAME UUID ?VALUE?");
  }
  zTag = g.argv[2];
  switch( zTag[0] ){
    case '+':  tagtype = 1;  break;
    case '*':  tagtype = 2;  break;
    case '-':  tagtype = 0;  break;
    default:   
      fossil_fatal("tag should begin with '+', '*', or '-'");
      return;
  }
  rid = name_to_rid(g.argv[3]);
  if( rid==0 ){
    fossil_fatal("no such object: %s", g.argv[3]);
  }
  zValue = g.argc==5 ? g.argv[4] : 0;
  db_begin_transaction();
  tag_insert(zTag, tagtype, zValue, -1, 0.0, rid);
  db_end_transaction(0); 
}

/*
** Add a control record to the repository that either creates
** or cancels a tag.
*/
static void tag_add_artifact(
  const char *zTagname,       /* The tag to add or cancel */
  const char *zObjName,       /* Name of object attached to */
  const char *zValue,         /* Value for the tag.  Might be NULL */
  int tagtype                 /* 0:cancel 1:singleton 2:propagated */
){
  int rid;
  int nrid;
  char *zDate;
  Blob uuid;
  Blob ctrl;
  Blob cksum;
  static const char zTagtype[] = { '-', '+', '*' };

  assert( tagtype>=0 && tagtype<=2 );
  user_select();
  rid = name_to_rid(zObjName);
  blob_zero(&uuid);
  db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d", rid);
  blob_zero(&ctrl);
  
  if( validate16(zTagname, strlen(zTagname)) ){
    fossil_fatal("invalid tag name \"%s\" - might be confused with a UUID",
                 zTagname);
  }
  zDate = db_text(0, "SELECT datetime('now')");
  zDate[10] = 'T';
  blob_appendf(&ctrl, "D %s\n", zDate);
  blob_appendf(&ctrl, "T %c%F %b", zTagtype[tagtype], zTagname, &uuid);
  if( tagtype && zValue && zValue[0] ){
    blob_appendf(&ctrl, " %F\n", zValue);
  }else{
    blob_appendf(&ctrl, "\n");
  }
  blob_appendf(&ctrl, "U %F\n", g.zLogin);
  md5sum_blob(&ctrl, &cksum);
  blob_appendf(&ctrl, "Z %b\n", &cksum);
  db_begin_transaction();
  nrid = content_put(&ctrl, 0, 0);
  manifest_crosslink(nrid, &ctrl);
  db_end_transaction(0);
  
  /* Do an autosync push if requested */
  autosync(0);
}

/*
** COMMAND: tag
** Usage: %fossil tag SUBCOMMAND ...
**
** Run various subcommands to control tags and properties
**
**     %fossil tag add TAGNAME UUID ?VALUE?
**
**         Add a new tag or property to UUID.
**
**     %fossil tag branch TAGNAME UUID ?VALUE?
**
**         Add a new tag or property to UUID and make that
**         tag propagate to all direct children.
**
**     %fossil tag delete TAGNAME UUID
**
**         Delete the tag TAGNAME from UUID
**
**     %fossil tag find TAGNAME
**
**         List all baselines that use TAGNAME
**
**     %fossil tag list ?UUID?
**
**         List all tags, or if UUID is supplied, list
**         all tags and their values for UUID.
*/
void tag_cmd(void){
  int n;
  db_find_and_open_repository();
  if( g.argc<3 ){
    goto tag_cmd_usage;
  }
  n = strlen(g.argv[2]);
  if( n==0 ){
    goto tag_cmd_usage;
  }

  if( strncmp(g.argv[2],"add",n)==0 ){
    char *zValue;
    if( g.argc!=5 && g.argc!=6 ){
      usage("add TAGNAME UUID ?VALUE?");
    }
    zValue = g.argc==6 ? g.argv[5] : 0;
    tag_add_artifact(g.argv[3], g.argv[4], zValue, 1);
  }else

  if( strncmp(g.argv[2],"branch",n)==0 ){
    char *zValue;
    if( g.argc!=5 && g.argc!=6 ){
      usage("branch TAGNAME UUID ?VALUE?");
    }
    zValue = g.argc==6 ? g.argv[5] : 0;
    tag_add_artifact(g.argv[3], g.argv[4], zValue, 2);
  }else

  if( strncmp(g.argv[2],"delete",n)==0 ){
    if( g.argc!=5 ){
      usage("delete TAGNAME UUID");
    }
    tag_add_artifact(g.argv[3], g.argv[4], 0, 0);
  }else

  if( strncmp(g.argv[2],"find",n)==0 ){
    Stmt q;
    if( g.argc!=4 ){
      usage("find TAGNAME");
    }
    db_prepare(&q,
      "SELECT blob.uuid FROM tagxref, blob"
      " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
      "   AND blob.rid=tagxref.rid", g.argv[3]
    );
    while( db_step(&q)==SQLITE_ROW ){
      printf("%s\n", db_column_text(&q, 0));
    }
    db_finalize(&q);
  }else

  if( strncmp(g.argv[2],"list",n)==0 ){
    Stmt q;
    if( g.argc==3 ){
      db_prepare(&q, 
        "SELECT tagname"
        "  FROM tag"
        " WHERE EXISTS(SELECT 1 FROM tagxref"
        "               WHERE tagid=tag.tagid"
        "                 AND tagtype>0)"
        " ORDER BY tagname"
      );
      while( db_step(&q)==SQLITE_ROW ){
        printf("%s\n", db_column_text(&q, 0));
      }
      db_finalize(&q);
    }else if( g.argc==4 ){
      int rid = name_to_rid(g.argv[3]);
      db_prepare(&q,
        "SELECT tagname, value"
        "  FROM tagxref, tag"
        " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
        "   AND tagtype>0"
        " ORDER BY tagname",
        rid
      );
      while( db_step(&q)==SQLITE_ROW ){
        const char *zName = db_column_text(&q, 0);
        const char *zValue = db_column_text(&q, 1);
        if( zValue ){
          printf("%s=%s\n", zName, zValue);
        }else{
          printf("%s\n", zName);
        }
      }
      db_finalize(&q);
    }else{
      usage("tag list ?UUID?");
    }
  }else
  {
    goto tag_cmd_usage;
  }
  return;

tag_cmd_usage:
  usage("add|branch|delete|find|list ...");
}
Changes to src/timeline.c.
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
** Generate a hyperlink to a version.
*/
void hyperlink_to_uuid(const char *zUuid){
  char zShortUuid[UUID_SIZE+1];
  sprintf(zShortUuid, "%.10s", zUuid);
  if( g.okHistory ){
    @ <a href="%s(g.zBaseURL)/vinfo/%s(zUuid)">[%s(zShortUuid)]</a>
  }else{
    @ <b>[%s(zShortUuid)]</b>
  }
}

/*
** Generate a hyperlink that invokes javascript to highlight







|







31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
** Generate a hyperlink to a version.
*/
void hyperlink_to_uuid(const char *zUuid){
  char zShortUuid[UUID_SIZE+1];
  sprintf(zShortUuid, "%.10s", zUuid);
  if( g.okHistory ){
    @ <a href="%s(g.zBaseURL)/info/%s(zUuid)">[%s(zShortUuid)]</a>
  }else{
    @ <b>[%s(zShortUuid)]</b>
  }
}

/*
** Generate a hyperlink that invokes javascript to highlight
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
      @ <a href="%s(g.zBaseURL)/diff?v1=%s(zV1)&v2=%s(zV2)">[diff]</a>
    }
  }
}

/*
** Output a timeline in the web format given a query.  The query
** should return 4 columns:
**
**    0.  rid
**    1.  UUID
**    2.  Date/Time
**    3.  Comment string
**    4.  User
**    5.  Number of non-merge children
**    6.  Number of parents
**    7.  True if is a leaf


*/
void www_print_timeline(
  Stmt *pQuery,
  int *pFirstEvent,
  int *pLastEvent,
  int (*xCallback)(int, Blob*),
  Blob *pArg
 ){
  char zPrevDate[20];
  int cnt = 0;




  zPrevDate[0] = 0;








  db_multi_exec(
     "CREATE TEMP TABLE IF NOT EXISTS seen(rid INTEGER PRIMARY KEY);"
     "DELETE FROM seen;"
  );
  @ <table cellspacing=0 border=0 cellpadding=0>

  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);
    int isLeaf = db_column_int(pQuery, 7);

    const char *zDate = db_column_text(pQuery, 2);


    if( cnt==0 && pFirstEvent ){
      *pFirstEvent = rid;
    }

    if( pLastEvent ){
      *pLastEvent = rid;
    }
    db_multi_exec("INSERT OR IGNORE INTO seen VALUES(%d)", rid);
    if( xCallback ){
      xCallback(rid, pArg);
    }
    if( memcmp(zDate, zPrevDate, 10) ){
      sprintf(zPrevDate, "%.10s", zDate);
      @ <tr><td colspan=3>
      @ <table cellpadding=2 border=0>
      @ <tr><td bgcolor="#a0b5f4" class="border1">
      @ <table cellpadding=2 cellspacing=0 border=0><tr>
      @ <td bgcolor="#d0d9f4" class="bkgnd1">%s(zPrevDate)</td>
      @ </tr></table>
      @ </td></tr></table>
      @ </td></tr>
    }
    @ <tr>
    @ <td valign="top">%s(&zDate[11])</td>
    @ <td width="20" align="center" valign="top">
    @ <font id="m%d(rid)" size="+1" color="white">*</font></td>



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


    hyperlink_to_uuid_with_mouseover(zUuid, "xin", "xout", rid);
    if( nParent>1 ){
      @ <b>Merge</b> 
    }
    if( nPChild>1 ){
      @ <b>Fork</b>
    }
    if( isLeaf ){
      @ <b>Leaf</b>
    }



    @ %h(db_column_text(pQuery,3))











    @ (by %h(db_column_text(pQuery,4)))</td></tr>
  }
  @ </table>
}

/*
** Generate javascript code that records the parents and children
** of the version rid.







|









>
>








<

>
>
>
>

>
>
>
>
>
>
>
>





>






>

>
>



>










<
<
<
|
<
<






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







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
      @ <a href="%s(g.zBaseURL)/diff?v1=%s(zV1)&v2=%s(zV2)">[diff]</a>
    }
  }
}

/*
** Output a timeline in the web format given a query.  The query
** should return these columns:
**
**    0.  rid
**    1.  UUID
**    2.  Date/Time
**    3.  Comment string
**    4.  User
**    5.  Number of non-merge children
**    6.  Number of parents
**    7.  True if is a leaf
**    8.  background color
**    9.  type ("ci", "w")
*/
void www_print_timeline(
  Stmt *pQuery,
  int *pFirstEvent,
  int *pLastEvent,
  int (*xCallback)(int, Blob*),
  Blob *pArg
 ){

  int cnt = 0;
  int wikiFlags;
  int mxWikiLen;
  Blob comment;
  char zPrevDate[20];
  zPrevDate[0] = 0;

  mxWikiLen = db_get_int("timeline-max-comment", 0);
  if( db_get_boolean("timeline-block-markup", 0) ){
    wikiFlags = WIKI_INLINE;
  }else{
    wikiFlags = WIKI_INLINE | WIKI_NOBLOCK;
  }

  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);
    int isLeaf = db_column_int(pQuery, 7);
    const char *zBgClr = db_column_text(pQuery, 8);
    const char *zDate = db_column_text(pQuery, 2);
    const char *zType = db_column_text(pQuery, 9);
    const char *zUser = db_column_text(pQuery, 4);
    if( cnt==0 && pFirstEvent ){
      *pFirstEvent = rid;
    }
    cnt++;
    if( pLastEvent ){
      *pLastEvent = rid;
    }
    db_multi_exec("INSERT OR IGNORE INTO seen VALUES(%d)", rid);
    if( xCallback ){
      xCallback(rid, pArg);
    }
    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" align="center" valign="top">
    @ <font id="m%d(rid)" size="+1" color="white">*</font></td>
    if( zBgClr && zBgClr[0] ){
      @ <td valign="top" align="left" bgcolor="%h(zBgClr)">
    }else{
      @ <td valign="top" align="left">
    }
    if( zType[0]=='c' ){
      hyperlink_to_uuid_with_mouseover(zUuid, "xin", "xout", rid);
      if( nParent>1 ){
        @ <b>Merge</b> 
      }
      if( nPChild>1 ){
        @ <b>Fork</b>
      }
      if( isLeaf ){
        @ <b>Leaf</b>
      }
    }else{
      hyperlink_to_uuid(zUuid);
    }
    db_column_blob(pQuery, 3, &comment);
    if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
      Blob truncated;
      blob_zero(&truncated);
      blob_append(&truncated, blob_buffer(&comment), mxWikiLen);
      blob_append(&truncated, "...", 3);
      wiki_convert(&truncated, 0, wikiFlags);
      blob_reset(&truncated);
    }else{
      wiki_convert(&comment, 0, wikiFlags);
    }
    blob_reset(&comment);
    @ (by %h(zUser))</td></tr>
  }
  @ </table>
}

/*
** Generate javascript code that records the parents and children
** of the version rid.
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
    blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
    zSep = ",";
  }
  db_finalize(&q);
  blob_appendf(pOut, "];\n");
  return 0;
}
























/*
** WEBPAGE: timeline
**
** Query parameters:
**
**    d=STARTDATE    date in iso8601 notation.          dflt: newest event
**    n=INTEGER      number of events to show.          dflt: 25
**    e=INTEGER      starting event id.                 dflt: nil
**    u=NAME         show only events from user.        dflt: nil
**    a              show events after and including.   dflt: false
**    r              show only related events.          dflt: false


*/
void page_timeline(void){
  Stmt q;

  char *zSQL;
  Blob scriptInit;
  char zDate[100];
  const char *zStart = P("d");
  int nEntry = atoi(PD("n","20"));
  const char *zUser = P("u");
  int objid = atoi(PD("e","0"));
  int relatedEvents = P("r")!=0;
  int afterFlag = P("a")!=0;

  int firstEvent;
  int lastEvent;

  /* To view the timeline, must have permission to read project data.
  */
  login_check_credentials();
  if( !g.okRead ){ login_needed(); return; }

  style_header("Timeline");
  if( !g.okHistory &&
      db_exists("SELECT 1 FROM user"
                " WHERE login='anonymous'"
                "   AND cap LIKE '%%h%%'") ){
    @ <p><b>Note:</b> You will be able to access <u>much</u> more
    @ historical information if <a href="%s(g.zBaseURL)/login">login</a>.</p>
  }
  zSQL = mprintf(
    "SELECT blob.rid, uuid, datetime(event.mtime,'localtime'), comment, user,"
    "       (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim=1),"
    "       (SELECT count(*) FROM plink WHERE cid=blob.rid),"
    "       NOT EXISTS (SELECT 1 FROM plink WHERE pid=blob.rid)"
    "  FROM event, blob"

    " WHERE event.type='ci' AND blob.rid=event.objid"
  );

  if( zUser ){
    zSQL = mprintf("%z AND event.user=%Q", zSQL, zUser);
  }
  if( objid ){
    char *z = db_text(0, "SELECT datetime(event.mtime) FROM event"
                         " WHERE objid=%d", objid);
    if( z ){
      zStart = z;
    }
  }
  if( zStart ){
    while( isspace(zStart[0]) ){ zStart++; }
    if( zStart[0] ){

      zSQL = mprintf("%z AND event.mtime %s julianday(%Q, 'localtime')",
                      zSQL, afterFlag ? ">=" : "<=", zStart);
    }
  }
  if( relatedEvents && objid ){
    db_multi_exec(
       "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
    );
    if( afterFlag ){
      compute_descendents(objid, nEntry);
    }else{
      compute_ancestors(objid, nEntry);
    }
    zSQL = mprintf("%z AND event.objid IN ok", zSQL);
  }




  zSQL = mprintf("%z ORDER BY event.mtime DESC LIMIT %d", zSQL, nEntry);






  db_prepare(&q, zSQL);





  free(zSQL);

  zDate[0] = 0;
  blob_zero(&scriptInit);
  zDate[0] = 0;
  www_print_timeline(&q, &firstEvent, &lastEvent,
                     save_parentage_javascript, &scriptInit);
  db_finalize(&q);

  if( zStart==0 ){
    zStart = zDate;
  }
  @ <script>
  @ var parentof = new Object();
  @ var childof = new Object();
  cgi_append_content(blob_buffer(&scriptInit), blob_size(&scriptInit));







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












>
>



>









>














|

<
<
<
|
<
|
>
|
<
>

|


|








>
|
|











|

>
>
>
>
|
>
>
>
>
>
>

>
>
>
>
>
|
>






>







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
    blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
    zSep = ",";
  }
  db_finalize(&q);
  blob_appendf(pOut, "];\n");
  return 0;
}

/*
** Return a pointer to a constant string that forms the basis
** for a timeline query for the WWW interface.
*/
const char *timeline_query_for_www(void){
  static const char zBaseSql[] =
    @ SELECT
    @   blob.rid,
    @   uuid,
    @   datetime(event.mtime,'localtime') AS timestamp,
    @   coalesce(ecomment, comment),
    @   coalesce(euser, user),
    @   (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim=1),
    @   (SELECT count(*) FROM plink WHERE cid=blob.rid),
    @   NOT EXISTS (SELECT 1 FROM plink WHERE pid=blob.rid),
    @   coalesce(bgcolor, brbgcolor),
    @   event.type
    @  FROM event JOIN blob 
    @ WHERE blob.rid=event.objid
  ;
  return zBaseSql;
}

/*
** WEBPAGE: timeline
**
** Query parameters:
**
**    d=STARTDATE    date in iso8601 notation.          dflt: newest event
**    n=INTEGER      number of events to show.          dflt: 25
**    e=INTEGER      starting event id.                 dflt: nil
**    u=NAME         show only events from user.        dflt: nil
**    a              show events after and including.   dflt: false
**    r              show only related events.          dflt: false
**    y=TYPE         show only TYPE ('ci' or 'w')       dflt: nil
**    s              show the SQL                       dflt: nil
*/
void page_timeline(void){
  Stmt q;
  Blob sql;
  char *zSQL;
  Blob scriptInit;
  char zDate[100];
  const char *zStart = P("d");
  int nEntry = atoi(PD("n","20"));
  const char *zUser = P("u");
  int objid = atoi(PD("e","0"));
  int relatedEvents = P("r")!=0;
  int afterFlag = P("a")!=0;
  const char *zType = P("y");
  int firstEvent;
  int lastEvent;

  /* To view the timeline, must have permission to read project data.
  */
  login_check_credentials();
  if( !g.okRead ){ login_needed(); return; }

  style_header("Timeline");
  if( !g.okHistory &&
      db_exists("SELECT 1 FROM user"
                " WHERE login='anonymous'"
                "   AND cap LIKE '%%h%%'") ){
    @ <p><b>Note:</b> You will be able to access <u>much</u> more
    @ historical information if <a href="%s(g.zTop)/login">login</a>.</p>
  }



  blob_zero(&sql);

  blob_append(&sql, timeline_query_for_www(), -1);
  if( zType ){
    blob_appendf(&sql, " AND event.type=%Q", zType);

  }
  if( zUser ){
    blob_appendf(&sql, " AND event.user=%Q", zUser);
  }
  if( objid ){
    char *z = db_text(0, "SELECT datetime(event.mtime, 'localtime') FROM event"
                         " WHERE objid=%d", objid);
    if( z ){
      zStart = z;
    }
  }
  if( zStart ){
    while( isspace(zStart[0]) ){ zStart++; }
    if( zStart[0] ){
      blob_appendf(&sql, 
         " AND event.mtime %s (SELECT julianday(%Q, 'utc'))",
                          afterFlag ? ">=" : "<=", zStart);
    }
  }
  if( relatedEvents && objid ){
    db_multi_exec(
       "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
    );
    if( afterFlag ){
      compute_descendents(objid, nEntry);
    }else{
      compute_ancestors(objid, nEntry);
    }
    blob_append(&sql, " AND event.objid IN ok", -1);
  }
  if( afterFlag ){
    blob_appendf(&sql, " ORDER BY event.mtime ASC LIMIT %d",
                 nEntry);
  }else{
    blob_appendf(&sql, " ORDER BY event.mtime DESC LIMIT %d",
                 nEntry);
  }
  zSQL = blob_str(&sql);
  if( afterFlag ){
    zSQL = mprintf("SELECT * FROM (%s) ORDER BY timestamp DESC", zSQL);
  }
  db_prepare(&q, zSQL);
  if( P("s")!=0 ){
    @ <hr><p>%h(zSQL)</p><hr>
  }
  blob_zero(&sql);
  if( afterFlag ){
    free(zSQL);
  }
  zDate[0] = 0;
  blob_zero(&scriptInit);
  zDate[0] = 0;
  www_print_timeline(&q, &firstEvent, &lastEvent,
                     save_parentage_javascript, &scriptInit);
  db_finalize(&q);
  @ <p>firstEvent=%d(firstEvent) lastEvent=%d(lastEvent)</p>
  if( zStart==0 ){
    zStart = zDate;
  }
  @ <script>
  @ var parentof = new Object();
  @ var childof = new Object();
  cgi_append_content(blob_buffer(&scriptInit), blob_size(&scriptInit));
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
  @ <form method="GET" action="%s(g.zBaseURL)/timeline">
  @ Start Date:
  @ <input type="text" size="30" value="%h(zStart)" name="d">
  @ Number Of Entries:  
  @ <input type="text" size="4" value="%d(nEntry)" name="n">
  @ <br><input type="submit" value="Submit">
  @ </form>

  @ <form method="GET" action="%s(g.zBaseURL)/timeline">
  @ <input type="hidden" value="%h(zDate)" name="d">
  @ <input type="hidden" value="%d(nEntry)" name="n">
  @ <input type="submit" value="Next %d(nEntry) Rows">
  @ </form>






  style_footer();
}

/*
** The input query q selects various records.  Print a human-readable
** summary of those records.
**
** Limit the number of entries printed to nLine.









*/
void print_timeline(Stmt *q, int mxLine){
  int nLine = 0;
  char zPrevDate[20];



  zPrevDate[0] = 0;









  while( db_step(q)==SQLITE_ROW && nLine<=mxLine ){
    const char *zId = db_column_text(q, 1);
    const char *zDate = db_column_text(q, 2);
    const char *zCom = db_column_text(q, 3);
    int nChild = db_column_int(q, 4);
    int nParent = db_column_int(q, 5);
    char *zFree = 0;


    char zUuid[UUID_SIZE+1];

    sprintf(zUuid, "%.10s", zId);
    if( memcmp(zDate, zPrevDate, 10) ){
      printf("=== %.10s ===\n", zDate);
      memcpy(zPrevDate, zDate, 10);
      nLine++;
    }
    if( zCom==0 ) zCom = "";
    printf("%.8s ", &zDate[11]);
    if( nChild>1 || nParent>1 ){
      int n = 0;
      char zPrefix[50];
      if( nParent>1 ){
        sqlite3_snprintf(sizeof(zPrefix), zPrefix, "*MERGE* ");
        n = strlen(zPrefix);
      }
      if( nChild>1 ){
        sqlite3_snprintf(sizeof(zPrefix)-n, &zPrefix[n], "*FORK* ");
        n = strlen(zPrefix);
      }
      zFree = sqlite3_mprintf("[%.10s] %s%s", zUuid, zPrefix, zCom);
    }else{
      zFree = sqlite3_mprintf("[%.10s] %s", zUuid, zCom);

    }

    nLine += comment_print(zFree, 9, 79);
    sqlite3_free(zFree);
  }

}




















/*
** COMMAND: timeline
**
** Usage: %fossil timeline ?WHEN? ?UUID|DATETIME? ?-n|--count N?
**
** Print a summary of activity going backwards in date and time







>

|


|
>
>
>
>
>
>








>
>
>
>
>
>
>
>
>




>
>
>

>
>
>
>
>
>
>
>








>
>

|








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

>



>


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







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
  @ <form method="GET" action="%s(g.zBaseURL)/timeline">
  @ Start Date:
  @ <input type="text" size="30" value="%h(zStart)" name="d">
  @ Number Of Entries:  
  @ <input type="text" size="4" value="%d(nEntry)" name="n">
  @ <br><input type="submit" value="Submit">
  @ </form>
  @ <table><tr><td>
  @ <form method="GET" action="%s(g.zBaseURL)/timeline">
  @ <input type="hidden" value="%d(lastEvent)" name="e">
  @ <input type="hidden" value="%d(nEntry)" name="n">
  @ <input type="submit" value="Next %d(nEntry) Rows">
  @ </form></td><td>
  @ <form method="GET" action="%s(g.zBaseURL)/timeline">
  @ <input type="hidden" value="%d(firstEvent)" name="e">
  @ <input type="hidden" value="%d(nEntry)" name="n">
  @ <input type="hidden" value="1" name="a">
  @ <input type="submit" value="Previous %d(nEntry) Rows">
  @ </form></td></tr></table>
  style_footer();
}

/*
** The input query q selects various records.  Print a human-readable
** summary of those records.
**
** Limit the number of entries printed to nLine.
** 
** The query should return these columns:
**
**    0.  rid
**    1.  uuid
**    2.  Date/Time
**    3.  Comment string and user
**    4.  Number of non-merge children
**    5.  Number of parents
*/
void print_timeline(Stmt *q, int mxLine){
  int nLine = 0;
  char zPrevDate[20];
  const char *zCurrentUuid=0;
  Stmt currentQ;
  int rid = db_lget_int("checkout", 0);
  zPrevDate[0] = 0;

  db_prepare(&currentQ,
    "SELECT uuid"
    "  FROM blob WHERE rid=%d", rid
  );
  if( db_step(&currentQ)==SQLITE_ROW ){
    zCurrentUuid = db_column_text(&currentQ, 0);
  }

  while( db_step(q)==SQLITE_ROW && nLine<=mxLine ){
    const char *zId = db_column_text(q, 1);
    const char *zDate = db_column_text(q, 2);
    const char *zCom = db_column_text(q, 3);
    int nChild = db_column_int(q, 4);
    int nParent = db_column_int(q, 5);
    char *zFree = 0;
    int n = 0;
    char zPrefix[80];
    char zUuid[UUID_SIZE+1];
    
    sprintf(zUuid, "%.10s", zId);
    if( memcmp(zDate, zPrevDate, 10) ){
      printf("=== %.10s ===\n", zDate);
      memcpy(zPrevDate, zDate, 10);
      nLine++;
    }
    if( zCom==0 ) zCom = "";
    printf("%.8s ", &zDate[11]);


    zPrefix[0] = 0;
    if( nParent>1 ){
      sqlite3_snprintf(sizeof(zPrefix), zPrefix, "*MERGE* ");
      n = strlen(zPrefix);
    }
    if( nChild>1 ){
      sqlite3_snprintf(sizeof(zPrefix)-n, &zPrefix[n], "*FORK* ");
      n = strlen(zPrefix);
    }
    if( strcmp(zCurrentUuid,zId)==0 ){

      sqlite3_snprintf(sizeof(zPrefix)-n, &zPrefix[n], "*CURRENT* ");
      n += strlen(zPrefix);
    }
    zFree = sqlite3_mprintf("[%.10s] %s%s", zUuid, zPrefix, zCom);
    nLine += comment_print(zFree, 9, 79);
    sqlite3_free(zFree);
  }
  db_finalize(&currentQ);
}

/*
** Return a pointer to a static string that forms the basis for
** a timeline query for display on a TTY.
*/
const char *timeline_query_for_tty(void){
  static const char zBaseSql[] = 
    @ SELECT
    @   blob.rid,
    @   uuid,
    @   datetime(event.mtime,'localtime'),
    @   coalesce(ecomment,comment) || ' (by ' || coalesce(euser,user,'?') ||')',
    @   (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim),
    @   (SELECT count(*) FROM plink WHERE cid=blob.rid)
    @ FROM event, blob
    @ WHERE blob.rid=event.objid
  ;
  return zBaseSql;
}

/*
** COMMAND: timeline
**
** Usage: %fossil timeline ?WHEN? ?UUID|DATETIME? ?-n|--count N?
**
** Print a summary of activity going backwards in date and time
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
  k = strlen(zOrigin);
  blob_zero(&uuid);
  blob_append(&uuid, zOrigin, -1);
  if( strcmp(zOrigin, "now")==0 ){
    if( mode==3 || mode==4 ){
      fossil_fatal("cannot compute descendents or ancestors of a date");
    }
    zDate = mprintf("(SELECT julianday('now','utc'))");
  }else if( strncmp(zOrigin, "current", k)==0 ){
    objid = db_lget_int("checkout",0);
    zDate = mprintf("(SELECT mtime FROM plink WHERE cid=%d)", objid);
  }else if( name_to_uuid(&uuid, 0)==0 ){
    objid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &uuid);
    zDate = mprintf("(SELECT mtime FROM plink WHERE cid=%d)", objid);
  }else{
    if( mode==3 || mode==4 ){
      fossil_fatal("cannot compute descendents or ancestors of a date");
    }
    zDate = mprintf("(SELECT julianday(%Q, 'utc'))", zOrigin);
  }
  zSQL = mprintf(
    "SELECT blob.rid, uuid, datetime(event.mtime,'localtime'),"
    "       comment || ' (by ' || user || ')',"
    "       (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim),"
    "       (SELECT count(*) FROM plink WHERE cid=blob.rid)"
    "  FROM event, blob"
    " WHERE event.type='ci' AND blob.rid=event.objid"
    "   AND event.mtime %s %s",
    (mode==1 || mode==4) ? "<=" : ">=", zDate

  );
  if( mode==3 || mode==4 ){
    db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)");
    if( mode==3 ){
      compute_descendents(objid, n);
    }else{
      compute_ancestors(objid, n);







|












|
<
<
<
<
<
<
|
|
>







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
  k = strlen(zOrigin);
  blob_zero(&uuid);
  blob_append(&uuid, zOrigin, -1);
  if( strcmp(zOrigin, "now")==0 ){
    if( mode==3 || mode==4 ){
      fossil_fatal("cannot compute descendents or ancestors of a date");
    }
    zDate = mprintf("(SELECT datetime('now'))");
  }else if( strncmp(zOrigin, "current", k)==0 ){
    objid = db_lget_int("checkout",0);
    zDate = mprintf("(SELECT mtime FROM plink WHERE cid=%d)", objid);
  }else if( name_to_uuid(&uuid, 0)==0 ){
    objid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &uuid);
    zDate = mprintf("(SELECT mtime FROM plink WHERE cid=%d)", objid);
  }else{
    if( mode==3 || mode==4 ){
      fossil_fatal("cannot compute descendents or ancestors of a date");
    }
    zDate = mprintf("(SELECT julianday(%Q, 'utc'))", zOrigin);
  }
  zSQL = mprintf("%s AND event.mtime %s %s",






     timeline_query_for_tty(),
     (mode==1 || mode==4) ? "<=" : ">=",
     zDate
  );
  if( mode==3 || mode==4 ){
    db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)");
    if( mode==3 ){
      compute_descendents(objid, n);
    }else{
      compute_ancestors(objid, n);
Added src/tkt.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
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
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
/*
** Copyright (c) 2007 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 used render and control ticket entry
** and display pages.
*/
#include "config.h"
#include "tkt.h"
#include <assert.h>

/*
** The list of database user-defined fields in the TICKET table.
** The real table also contains some addition fields for internal
** used.  The internal-use fields begin with "tkt_".
*/
static int nField = 0;
static char **azField = 0;    /* Names of database fields */
static char **azValue = 0;    /* Original values */
static char **azAppend = 0;   /* Value to be appended */

/*
** A subscript interpreter used for processing Tickets.
*/
static struct Subscript *pInterp = 0;

/*
** Compare two entries in azField for sorting purposes
*/
static int nameCmpr(const void *a, const void *b){
  return strcmp(*(char**)a, *(char**)b);
}

/*
** Obtain a list of all fields of the TICKET table.  Put them 
** in sorted order in azField[].
**
** Also allocate space for azValue[] and azAppend[] and initialize
** all the values there to zero.
*/
static void getAllTicketFields(void){
  Stmt q;
  int i;
  if( nField>0 ) return;
  db_prepare(&q, "PRAGMA table_info(ticket)");
  while( db_step(&q)==SQLITE_ROW ){
    const char *zField = db_column_text(&q, 1);
    if( strncmp(zField,"tkt_",4)==0 ) continue;
    if( nField%10==0 ){
      azField = realloc(azField, sizeof(azField)*3*(nField+10) );
      if( azField==0 ){
        fossil_fatal("out of memory");
      }
    }
    azField[nField] = mprintf("%s", zField);
    nField++;
  }
  db_finalize(&q);
  qsort(azField, nField, sizeof(azField[0]), nameCmpr);
  azAppend = &azField[nField];
  memset(azAppend, 0, sizeof(azAppend[0])*nField);
  azValue = &azAppend[nField];
  for(i=0; i<nField; i++){
    azValue[i] = "";
  }
}

/*
** Return the index into azField[] of the given field name.
** Return -1 if zField is not in azField[].
*/
static int fieldId(const char *zField){
  int i;
  for(i=0; i<nField; i++){
    if( strcmp(azField[i], zField)==0 ) return i;
  }
  return -1;
}

/*
** Query the database for all TICKET fields for the specific
** ticket whose name is given by the "name" CGI parameter.
** Load the values for all fields into the interpreter.
**
** Only load those fields which do not already exist as
** variables.
*/
static void initializeVariablesFromDb(void){
  const char *zName;
  Stmt q;
  int i, n, size, j;

  zName = PD("name","");
  db_prepare(&q, "SELECT * FROM ticket WHERE tkt_uuid GLOB '%q*'", zName);
  if( db_step(&q)==SQLITE_ROW ){
    n = db_column_count(&q);
    for(i=0; i<n; i++){
      const char *zVal = db_column_text(&q, i);
      const char *zName = db_column_name(&q, i);
      if( zVal==0 ) zVal = "";
      for(j=0; j<nField; j++){
        if( strcmp(azField[j],zName)==0 ){
          azValue[j] = mprintf("%s", zVal);
          break;
        }
      }
      if( SbS_Fetch(pInterp, zName, -1, &size)==0 ){
        SbS_Store(pInterp, db_column_name(&q,i), zVal, 1);
      }
    }
  }else{
    db_finalize(&q);
    db_prepare(&q, "PRAGMA table_info(ticket)");
    while( db_step(&q)==SQLITE_ROW ){
      const char *zField = db_column_text(&q, 1);
      if( SbS_Fetch(pInterp, zField, -1, &size)==0 ){
        SbS_Store(pInterp, zField, "", 0);
      }
    }
  }
  db_finalize(&q);
}

/*
** Transfer all CGI parameters to variables in the interpreter.
*/
static void initializeVariablesFromCGI(void){
  int i;
  const char *z;

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

/*
** 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.
*/
void ticket_insert(Manifest *p, int createFlag, int checkTime){
  Blob sql;
  Stmt q;
  int i;
  const char *zSep;

  getAllTicketFields();
  if( createFlag ){  
    db_multi_exec("INSERT OR IGNORE INTO ticket(tkt_uuid, tkt_mtime) "
                  "VALUES(%Q, 0)", p->zTicketUuid);
  }
  blob_zero(&sql);
  blob_appendf(&sql, "UPDATE ticket SET tkt_mtime=:mtime");
  zSep = "SET";
  for(i=0; i<p->nField; i++){
    const char *zName = p->aField[i].zName;
    if( zName[0]=='+' ){
      zName++;
      if( fieldId(zName)<0 ) continue;
      blob_appendf(&sql,", %s=%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);
}

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

/*
** Create the subscript interpreter and load the ticket configuration.
*/
void ticket_init(void){
  char *zConfig;
  if( pInterp ) return;
  pInterp = SbS_Create();
  zConfig = db_text((char*)zDefaultTicketConfig,
             "SELECT value FROM config WHERE name='ticket-configuration'");
  SbS_Eval(pInterp, zConfig, -1);
}

/*
** Recreate the ticket table.
*/
void ticket_create_table(int separateConnection){
  char *zSql;
  int nSql;

  db_multi_exec("DROP TABLE IF EXISTS ticket;");
  ticket_init();
  zSql = (char*)SbS_Fetch(pInterp, "ticket_sql", -1, &nSql);
  if( zSql==0 ){
    fossil_panic("no ticket_sql defined by ticket configuration");
  }
  if( separateConnection ){
    zSql = mprintf("%.*s", nSql, zSql);
    db_init_database(g.zRepositoryName, zSql, 0);
    free(zSql);
  }else{
    db_multi_exec("%.*s", nSql, zSql);
  }
}

/*
** Repopulate the ticket table
*/
void ticket_rebuild(void){
  Stmt q;
  db_begin_transaction();
  db_prepare(&q,"SELECT tagname FROM tag WHERE tagname GLOB 'tkt-*'");
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    int len;
    zName += 4;
    len = strlen(zName);
    if( len<20 || !validate16(zName, len) ) continue;
    ticket_rebuild_entry(zName);
  }
  db_finalize(&q);
  db_end_transaction(0);
}

/*
** WEBPAGE: tktview
**
** View a ticket.
*/
void tktview_page(void){
  char *zScript;
  int nScript;
  login_check_credentials();
  if( !g.okRdTkt ){ login_needed(); return; }
  if( g.okWrTkt ){
    style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T",
        g.zTop, PD("name",""));
  }
  style_header("View Ticket");
  ticket_init();
  initializeVariablesFromDb();
  zScript = (char*)SbS_Fetch(pInterp, "tktview_template", -1, &nScript);
  zScript = mprintf("%.*s", nScript, zScript);
  SbS_Render(pInterp, zScript);
  style_footer();
}



/*
** Subscript command:   STRING FIELD append_field
**
** FIELD is the name of a database column to which we might want
** to append text.  STRING is the text to be appended to that
** column.  The append does not actually occur until the
** submit_ticket_change verb is run.
*/
static int appendRemarkCmd(struct Subscript *p, void *notUsed){
  int idx;
  const char *zField, *zValue;
  int nField, nValue;

  if( SbS_RequireStack(p, 2, "append_field") ) return 1;
  zField = SbS_StackValue(p, 0, &nField);
  for(idx=0; idx<nField; idx++){
    if( strncmp(azField[idx], zField, nField)==0 && azField[idx][nField]==0 ){
      break;
    }
  }
  if( idx>=nField ){
    SbS_SetErrorMessage(p, "no such TICKET column: %.*s", nField, zField);
    return SBS_ERROR;
  }
  zValue = SbS_StackValue(p, 1, &nValue);
  azAppend[idx] = mprintf("%.*s", nValue, zValue);
  SbS_Pop(p, 2);
  return SBS_OK;
}

/*
** Subscript command:   submit_ticket
**
** Construct and submit a new ticket artifact.
*/
static int submitTicketCmd(struct Subscript *p, void *pUuid){
  char *zDate;
  const char *zUuid;
  int i;
  int rid;
  Blob tktchng, cksum;

  zUuid = (const char *)pUuid;
  blob_zero(&tktchng);
  zDate = db_text(0, "SELECT datetime('now')");
  zDate[10] = 'T';
  blob_appendf(&tktchng, "D %s\n", zDate);
  free(zDate);
  for(i=0; i<nField; i++){
    const char *zValue;
    int nValue;
    if( azAppend[i] ){
      blob_appendf(&tktchng, "J +%s %z\n", azField[i],
                   fossilize(azAppend[i], -1));
    }else{
      zValue = SbS_Fetch(p, azField[i], -1, &nValue);
      if( zValue ){
        while( nValue>0 && isspace(zValue[nValue-1]) ){ nValue--; }
        if( strncmp(zValue, azValue[i], nValue)
                || strlen(azValue[i])!=nValue ){
          blob_appendf(&tktchng, "J %s %z\n",
             azField[i], fossilize(zValue,nValue));
        }
      }
    }
  }
  if( *(char**)pUuid ){
    zUuid = db_text(0, 
       "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'", P("name")
    );
  }else{
    zUuid = db_text(0, "SELECT lower(hex(randomblob(20)))");
  }
  *(const char**)pUuid = zUuid;
  blob_appendf(&tktchng, "K %s\n", zUuid);
  blob_appendf(&tktchng, "U %F\n", g.zLogin ? g.zLogin : "");
  md5sum_blob(&tktchng, &cksum);
  blob_appendf(&tktchng, "Z %b\n", &cksum);

  if( strncmp(g.zPath,"debug_",6)==0 ){
    @ <hr><pre>
    @ %h(blob_str(&tktchng))
    @ </pre><hr>
    blob_zero(&tktchng);
    SbS_Pop(p, 1);
    return SBS_OK;
  }

  rid = content_put(&tktchng, 0, 0);
  if( rid==0 ){
    fossil_panic("trouble committing ticket: %s", g.zErrMsg);
  }
  manifest_crosslink(rid, &tktchng);
  return SBS_RETURN;
}


/*
** WEBPAGE: tktnew
** WEBPAGE: debug_tktnew
**
** Enter a new ticket.  the tktnew_template script in the ticket
** configuration is used.  The /tktnew page is the official ticket
** entry page.  The /debug_tktnew page is used for debugging the
** tktnew_template in the ticket configuration.  /debug_tktnew works
** just like /tktnew except that it does not really save the new ticket
** when you press submit - it just prints the ticket artifact at the
** top of the screen.
*/
void tktnew_page(void){
  char *zScript;
  int nScript;
  char *zNewUuid = 0;

  login_check_credentials();
  if( !g.okNewTkt ){ login_needed(); return; }
  style_header("New Ticket");
  ticket_init();
  getAllTicketFields();
  initializeVariablesFromCGI();
  @ <form method="POST" action="%s(g.zBaseURL)/%s(g.zPath)">
  zScript = (char*)SbS_Fetch(pInterp, "tktnew_template", -1, &nScript);
  zScript = mprintf("%.*s", nScript, zScript);
  SbS_Store(pInterp, "login", g.zLogin, 0);
  SbS_Store(pInterp, "date", db_text(0, "SELECT datetime('now')"), 2);
  SbS_AddVerb(pInterp, "submit_ticket", submitTicketCmd, (void*)&zNewUuid);
  if( SbS_Render(pInterp, zScript)==SBS_RETURN && zNewUuid ){
    cgi_redirect(mprintf("%s/tktview/%s", g.zBaseURL, zNewUuid));
    return;
  }
  @ </form>
  style_footer();
}

/*
** WEBPAGE: tktedit
** WEBPAGE: debug_tktedit
**
** Edit a ticket.  The ticket is identified by the name CGI parameter.
** /tktedit is the official page.  The /debug_tktedit page does the same
** thing except that it does not save the ticket change record when you
** press submit - it instead prints the ticket change record at the top
** of the page.  The /debug_tktedit page is intended to be used when
** debugging ticket configurations.
*/
void tktedit_page(void){
  char *zScript;
  int nScript;
  int nName;
  const char *zName;
  int nRec;

  login_check_credentials();
  if( !g.okApndTkt && !g.okWrTkt ){ login_needed(); return; }
  style_header("Edit Ticket");
  zName = P("name");
  if( zName==0 || (nName = strlen(zName))<4 || nName>UUID_SIZE
          || !validate16(zName,nName) ){
    @ <font color="red"><b>Not a valid ticket id: \"%h(zName)\"</b></font>
    style_footer();
    return;
  }
  nRec = db_int(0, "SELECT count(*) FROM ticket WHERE tkt_uuid GLOB '%q*'",
                zName);
  if( nRec==0 ){
    @ <font color="red"><b>No such ticket: \"%h(zName)\"</b></font>
    style_footer();
    return;
  }
  if( nRec>1 ){
    @ <font color="red"><b>%d(nRec) tickets begin with: \"%h(zName)\"</b></font>
    style_footer();
    return;
  }
  ticket_init();
  getAllTicketFields();
  initializeVariablesFromCGI();
  initializeVariablesFromDb();
  @ <form method="POST" action="%s(g.zBaseURL)/%s(g.zPath)">
  @ <input type="hidden" name="name" value="%s(zName)">
  zScript = (char*)SbS_Fetch(pInterp, "tktedit_template", -1, &nScript);
  zScript = mprintf("%.*s", nScript, zScript);
  SbS_Store(pInterp, "login", g.zLogin, 0);
  SbS_Store(pInterp, "date", db_text(0, "SELECT datetime('now')"), 2);
  SbS_AddVerb(pInterp, "append_field", appendRemarkCmd, 0);
  SbS_AddVerb(pInterp, "submit_ticket", submitTicketCmd, (void*)&zName);
  if( SbS_Render(pInterp, zScript)==SBS_RETURN && zName ){
    cgi_redirect(mprintf("%s/tktview/%s", g.zBaseURL, zName));
    return;
  }
  @ </form>
  style_footer();
}

/*
** Check the ticket configuration in zConfig to see if it appears to
** be well-formed.  If everything is OK, return NULL.  If something is
** amiss, then return a pointer to a string (obtained from malloc) that
** describes the problem.
*/
char *ticket_config_check(const char *zConfig){
  struct Subscript *p;
  char *zErr = 0;
  const char *z;
  int n;
  int i;
  int rc;
  sqlite3 *db;
  static const char *azRequired[] = {
     "tktnew_template",
     "tktview_template",
     "tktedit_template",
  };
  
  p = SbS_Create();
  rc = SbS_Eval(p, zConfig, strlen(zConfig));
  if( rc!=SBS_OK ){
    zErr = mprintf("%s", SbS_GetErrorMessage(p));
    SbS_Destroy(p);
    return zErr;
  }
  for(i=0; i<sizeof(azRequired)/sizeof(azRequired[0]); i++){
    z = SbS_Fetch(p, azRequired[i], -1, &n);
    if( z==0 ){
      zErr = mprintf("missing definition: %s", azRequired[i]);
      SbS_Destroy(p);
      return zErr;
    }
  }
  z = SbS_Fetch(p, "ticket_sql", -1, &n);
  if( z==0 ){
    zErr = mprintf("missing definition: ticket_sql");
    SbS_Destroy(p);
    return zErr;
  }
  rc = sqlite3_open(":memory:", &db);
  if( rc==SQLITE_OK ){
    char *zSql = mprintf("%.*s", n, z);
    rc = sqlite3_exec(db, zSql, 0, 0, &zErr);
    if( rc!=SQLITE_OK ){
      sqlite3_close(db);
      SbS_Destroy(p);
      return zErr;
    }
    /* TODO: verify that the TICKET table exists and has required fields */
    sqlite3_close(db);
  }
  SbS_Destroy(p);
  return 0;
}
Added src/tktconfig.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
/*
** Copyright (c) 2007 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 a string constant that is the default ticket
** configuration.
*/
#include "config.h"
#include "tktconfig.h"

/*
** This is a sample "ticket configuration" file for fossil.
**
** There is considerable flexibility in how tickets are defined
** in fossil.  Each repository can define its own ticket setup
** differently.  Each repository has an instance of a file, like
** this one that defines how that repository deals with tickets.
**
** This file is in the form of a script in an exceedingly 
** minimalist scripting language called "subscript".  Here are
** the rules:
**
** SYNTAX
**
**     *  The script consists of a sequence of whitespace
**        separated tokens.  Whitespace is ignored, except
**        as its role as a token separator.
**
**     *  Lines that begin with '#' are considered to be whitespace.
**
**     *  Text within matching {...} is consider to be a single "string"
**        token, even if the text spans multiple lines and includes
**        embedded whitespace.  The outermost {...} are not part of
**        the text of the token.
**
**     *  A token that begins with "/" is a string token.
**
**     *  A token that looks like a number is a string token.
**
**     *  Tokens that do not fall under the previous three rules
**        are "verb" tokens.
**
**  PROCESSING:
**
**     *  When a script is processed, the engine reads tokens
**        one by one.
**
**     *  String tokens are pushed onto the stack.
**
**     *  Verb tokens which correspond to the names of variables
**        cause the corresponding variable to be pushed onto the
**        stack.
**
**     *  Verb tokens which correspond to procedures cause the
**        procedures to run.  The procedures might push or pull 
**        values from the stack.
**
** There are just a handful of verbs.  For the purposes of this
** configuration script, there is only a single verb: "set".  The
** "set" verb pops two arguments from the stack.  The topmost is
** the name of a variable.  The second element is the value.  The
** "set" verb sets the value of the named variable.
**
**      VALUE NAME set
**
** This configuration file just sets the values of various variables.
** Some of the variables have special meanings.  The content of some
** of the variables are additional subscript scripts.
*/

/* @-comment: ** */
const char zDefaultTicketConfig[] = 
@ ############################################################################
@ # Every ticket configuration *must* define an SQL statement that creates
@ # the TICKET table.  This table must have three columns named
@ # tkt_id, tkt_uuid, and tkt_mtime.  tkt_id must be the integer primary
@ # key and tkt_uuid and tkt_mtime must be unique.  A configuration should
@ # define addition columns as necessary.  All columns should be in all
@ # lower-case letters and should not begin with "tkt".
@ #
@ {
@    CREATE TABLE ticket(
@      -- Do not change any column that begins with tkt_
@      tkt_id INTEGER PRIMARY KEY,
@      tkt_uuid TEXT,
@      tkt_mtime DATE,
@      -- Add as many field as required below this line
@      type TEXT,
@      status TEXT,
@      subsystem TEXT,
@      priority TEXT,
@      severity TEXT,
@      foundin TEXT,
@      contact TEXT,
@      title TEXT,
@      comment TEXT,
@      -- Do not alter this UNIQUE clause:
@      UNIQUE(tkt_uuid, tkt_mtime)
@    );
@    -- Add indices as desired
@ } /ticket_sql set
@ 
@ ############################################################################
@ # You can define additional variables here.  These variables will be
@ # accessible to the page templates when they run.
@ #
@ {
@    Code_Defect
@    Build_Problem
@    Documentation
@    Feature_Request
@    Incident
@ } /type_choices set
@ {Immediate High Medium Low Zero} /priority_choices set
@ {Critical Severe Important Minor Cosmetic} /severity_choices set
@ {
@   Open
@   Fixed
@   Rejected
@   Unable_To_Reproduce
@   Works_As_Designed
@   External_Bug
@   Not_A_Bug
@   Duplicate
@   Overcome_By_Events
@   Drive_By_Patch
@ } /resolution_choices set
@ {
@   Open
@   Verified
@   In_Process
@   Deferred
@   Fixed
@   Tested
@   Closed
@ } /status_choices set
@ {one two three} /subsystem_choices set
@ 
@ ##########################################################################
@ # The "tktnew_template" variable is set to text which is a template for
@ # the HTML of the "New Ticket" page.  Within this template, text contained
@ # within [...] is subscript.  That subscript runs when the page is
@ # rendered.
@ #
@ {
@   <!-- load database field names not found in CGI with an empty string -->
@   <!-- start a form -->
@   [{
@      {Open} /status set
@       submit_ticket
@   } /submit exists if]
@   <table cellpadding="5">
@   <tr>
@   <td colspan="2">
@   Enter a one-line summary of the problem:<br>
@   <input type="text" name="title" size="60" value="[{} /title get html]">
@   </td>
@   </tr>
@   
@   <tr>
@   <td align="right">Type:
@   [/type type_choices 1 combobox]
@   </td>
@   <td>What type of ticket is this?</td>
@   </tr>
@   
@   <tr>
@   <td align="right">Version: 
@   <input type="text" name="foundin" size="20" value="[{} /foundin get html]">
@   </td>
@   <td>In what version or build number do you observer the problem?</td>
@   </tr>
@   
@   <tr>
@   <td align="right">Severity:
@   [/severity severity_choices 1 combobox]
@   </td>
@   <td>How debilitating is the problem?  How badly does the problem
@   effect the operation of the product?</td>
@   </tr>
@   
@   <tr>
@   <td align="right">EMail:
@   <input type="text" name="contact" value="[{} /contact get html]" size="30">
@   </td>
@   <td>Not publically visible. Used by developers to contact you with
@   questions.</td>
@   </tr>
@   
@   <tr>
@   <td colspan="2">
@   Enter a detailed description of the problem.
@   For code defects, be sure to provide details on exactly how
@   the problem can be reproduced.  Provide as much detail as
@   possible.
@   <br>
@   <textarea name="comment" cols="80"
@    rows="[{} /comment get linecount 50 max 10 min html]"
@    wrap="virtual" class="wikiedit">[{} /comment get html]</textarea><br>
@   <input type="submit" name="preview" value="Preview">
@   </tr>
@ 
@   [/preview exists enable_output]
@   <tr><td colspan="2">
@   Description Preview:<br><hr>
@   [{} /comment get wiki]
@   <hr>
@   </td></tr>
@   [1 enable_output]
@   
@   <tr>
@   <td align="right">
@   <input type="submit" name="submit" value="Submit">
@   </td>
@   <td>After filling in the information above, press this button to create
@   the new ticket</td>
@   </tr>
@   </table>
@   <!-- end of form -->
@ } /tktnew_template set
@ 
@ ##########################################################################
@ # The template for the "edit ticket" page
@ #
@ # Then generated text is inserted inside a form which feeds back to itself.
@ # All CGI parameters are loaded into variables.  All database files are
@ # loaded into variables if they have not previously been loaded by
@ # CGI parameters.
@ {
@   [
@     login /username get /username set
@     {
@       {
@         username login eq /samename set
@         {
@            "\n\n<hr><i>" login " added on " date ":</i><br>\n"
@            cmappnd 6 concat /comment append_field
@         } samename if
@         {
@            "\n\n<hr><i>" login " claiming to be " username " added on " date
@            "</i><br>\n" cmappnd 8 concat /comment append_field
@         } samename not if
@       } 0 {} /cmappnd get length lt if
@       submit_ticket
@     } /submit exists if
@   ]
@   <table cellpadding="5">
@   <tr><td align="right">Title:</td><td>
@   <input type="text" name="title" value="[title html]" size="60">
@   </td></tr>
@   <tr><td align="right">Status:</td><td>
@   [/status status_choices 1 combobox]
@   </td></tr>
@   <tr><td align="right">Type:</td><td>
@   [/type type_choices 1 combobox]
@   </td></tr>
@   <tr><td align="right">Severity:</td><td>
@   [/severity severity_choices 1 combobox]
@   </td></tr>
@   <tr><td align="right">Priority:</td><td>
@   [/priority priority_choices 1 combobox]
@   </td></tr>
@   <tr><td align="right">Resolution:</td><td>
@   [/resolution resolution_choices 1 combobox]
@   </td></tr>
@   <tr><td align="right">Subsystem:</td><td>
@   [/subsystem subsystem_choices 1 combobox]
@   </td></tr>
@   [/e hascap enable_output]
@     <tr><td align="right">Contact:</td><td>
@     <input type="text" name="contact" size="40" value="[contact html]">
@     </td></tr>
@   [1 enable_output]
@   <tr><td align="right">Version&nbsp;Found&nbsp;In:</td><td>
@   <input type="text" name="foundin" size="50" value="[foundin html]">
@   </td></tr>
@   <tr><td colspan="2">
@
@   [
@      0 /eall get /eall set           # eall means "edit all".  default==no
@      /aonlybtn exists not /eall set  # Edit all if no aonlybtn CGI param
@      /eallbtn exists /eall set       # Edit all if eallbtn CGI param
@      /w hascap eall and /eall set    # WrTkt permission needed to edit all
@   ]
@ 
@   [eall enable_output]
@     Description And Comments:<br>
@     <textarea name="comment" cols="80" 
@      rows="[{} /comment get linecount 15 max 10 min html]"
@      wrap="virtual" class="wikiedit">[comment html]</textarea><br>
@     <input type="hidden" name="eall" value="1">
@     <input type="submit" name="aonlybtn" value="Append Remark">
@   
@   [eall not enable_output]
@     Append Remark from 
@     <input type="text" name="username" value="[username html]" size="30">:<br>
@     <textarea name="cmappnd" cols="80" rows="15"
@      wrap="virtual" class="wikiedit">[{} /cmappnd get html]</textarea><br>
@     [/w hascap eall not and enable_output]
@     <input type="submit" name="eallbtn" value="Edit All">
@
@   [1 enable_output]
@   </td></tr>
@   <tr><td align="right"></td><td>
@   <input type="submit" name="submit" value="Submit Changes">
@   </td></tr>
@   </table>
@ } /tktedit_template set
@ 
@ ##########################################################################
@ # The template for the "view ticket" page
@ {
@   <!-- load database fields automatically loaded into variables -->
@   <table cellpadding="5">
@   <tr><td align="right">Title:</td><td>
@   [title html]
@   </td></tr>
@   <tr><td align="right">Status:</td><td>
@   [status html]
@   </td></tr>
@   <tr><td align="right">Type:</td><td>
@   [type html]
@   </td></tr>
@   <tr><td align="right">Severity:</td><td>
@   [severity html]
@   </td></tr>
@   <tr><td align="right">Priority:</td><td>
@   [priority html]
@   </td></tr>
@   <tr><td align="right">Resolution:</td><td>
@   [priority html]
@   </td></tr>
@   <tr><td align="right">Subsystem:</td><td>
@   [subsystem html]
@   </td></tr>
@   [{e} hascap enable_output]
@     <tr><td align="right">Contact:</td><td>
@     [contact html]
@     </td></tr>
@   [1 enable_output]
@   <tr><td align="right">Version&nbsp;Found&nbsp;In:</td><td>
@   [foundin html]
@   </td></tr>
@   <tr><td colspan="2">
@   Description And Comments:<br>
@   [comment wiki]
@   </td></tr>
@   </table>
@ } /tktview_template set
;
Added src/tktsetup.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
/*
** Copyright (c) 2007 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 to implement the ticket configuration
** setup screens.
*/
#include "config.h"
#include "tktsetup.h"
#include <assert.h>

/*
** Main sub-menu for configuring the ticketing system.
** WEBPAGE: /tktsetup
*/
void tktsetup_page(void){
  login_check_credentials();
  if( !g.okSetup ){
    login_needed();
  }

  style_header("Ticket Setup");
  @ <i>TBD...</i>
  style_footer();
}
Changes to src/translate.c.
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
}

/*
** Translate the input stream into the output stream
*/
static void trans(FILE *in, FILE *out){
  int i, j, k;        /* Loop counters */

  int lastWasEq = 0;  /* True if last non-whitespace character was "=" */
  char zLine[2000];   /* A single line of input */
  char zOut[4000];    /* The input line translated into appropriate output */


  while( fgets(zLine, sizeof(zLine), in) ){
    for(i=0; zLine[i] && isspace(zLine[i]); i++){}
    if( zLine[i]!='@' ){
      if( inPrint || inStr ) end_block(out);
      fprintf(out,"%s",zLine);





      i += strlen(&zLine[i]);
      while( i>0 && isspace(zLine[i-1]) ){ i--; }
      lastWasEq = i>0 && zLine[i-1]=='=';
    }else if( lastWasEq ){
      /* If the last non-whitespace character before the first @ was
      ** an "=" then generate a string literal.  But skip SQL comments
      ** consisting of all text between "--" and end of line.

      */
      int indent, omitline;
      i++;
      if( isspace(zLine[i]) ){ i++; }
      indent = i - 2;
      if( indent<0 ) indent = 0;
      omitline = 0;
      for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){

        if( zLine[i]=='-' && zLine[i+1]=='-' ){ omitline = 1; break; }

        if( zLine[i]=='"' || zLine[i]=='\\' ){ zOut[j++] = '\\'; }
        zOut[j++] = zLine[i];
      }
      while( j>0 && isspace(zOut[j-1]) ){ j--; }
      zOut[j] = 0;
      if( j<=0 && omitline ){
        fprintf(out,"\n");







>




>





>
>
>
>
>





|
|
>








>
|
>







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
}

/*
** Translate the input stream into the output stream
*/
static void trans(FILE *in, FILE *out){
  int i, j, k;        /* Loop counters */
  char c1, c2;        /* Characters used to start a comment */
  int lastWasEq = 0;  /* True if last non-whitespace character was "=" */
  char zLine[2000];   /* A single line of input */
  char zOut[4000];    /* The input line translated into appropriate output */

  c1 = c2 = '-';
  while( fgets(zLine, sizeof(zLine), in) ){
    for(i=0; zLine[i] && isspace(zLine[i]); i++){}
    if( zLine[i]!='@' ){
      if( inPrint || inStr ) end_block(out);
      fprintf(out,"%s",zLine);
                       /* 0123456789 12345 */
      if( strncmp(zLine, "/* @-comment: ", 14)==0 ){
        c1 = zLine[14];
        c2 = zLine[15];
      }
      i += strlen(&zLine[i]);
      while( i>0 && isspace(zLine[i-1]) ){ i--; }
      lastWasEq = i>0 && zLine[i-1]=='=';
    }else if( lastWasEq ){
      /* If the last non-whitespace character before the first @ was
      ** an "=" then generate a string literal.  But skip comments
      ** consisting of all text between c1 and c2 (default "--")
      ** and end of line.
      */
      int indent, omitline;
      i++;
      if( isspace(zLine[i]) ){ i++; }
      indent = i - 2;
      if( indent<0 ) indent = 0;
      omitline = 0;
      for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){
        if( zLine[i]==c1 && (c2==' ' || zLine[i+1]==c2) ){
           omitline = 1; break; 
        }
        if( zLine[i]=='"' || zLine[i]=='\\' ){ zOut[j++] = '\\'; }
        zOut[j++] = zLine[i];
      }
      while( j>0 && isspace(zOut[j-1]) ){ j--; }
      zOut[j] = 0;
      if( j<=0 && omitline ){
        fprintf(out,"\n");
Changes to src/update.c.
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
**
** This file contains code used to merge the changes in the current
** checkout into a different version and switch to that version.
*/
#include "config.h"
#include "update.h"
#include <assert.h>








/*
** COMMAND: update
**
** Usage: %fossil update ?VERSION?
**
** The optional argument is a version that should become the current
** version.  If the argument is omitted, then use the leaf of the
** tree that begins with the current version, if there is only a 
** single leaf.  

**
** This command is different from the "checkout" in that edits are
** not overwritten.  Edits are merged into the new version.
**



*/
void update_cmd(void){
  int vid;              /* Current version */
  int tid;              /* Target version - version we are changing to */
  Stmt q;





  if( g.argc!=3 && g.argc!=2 ){
    usage("?VERSION?");
  }
  db_must_be_within_tree();
  vid = db_lget_int("checkout", 0);
  if( vid==0 ){
    vid = 1;
  }
  if( db_exists("SELECT 1 FROM vmerge") ){
    fossil_fatal("cannot update an uncommitted merge");
  }




  if( g.argc==3 ){
    tid = name_to_rid(g.argv[2]);
    if( tid==0 ){
      fossil_fatal("not a version: %s", g.argv[2]);
    }
    if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", tid) ){
      fossil_fatal("not a version: %s", g.argv[2]);
    }

  }else{











    compute_leaves(vid);
    if( db_int(0, "SELECT count(*) FROM leaves")>1 ){
      db_prepare(&q, 
        "SELECT blob.rid, uuid, datetime(event.mtime,'localtime'),"
        "       comment || ' (by ' || user || ')', 1, 1"
        "  FROM event, blob"
        " WHERE event.type='ci' AND blob.rid=event.objid"

        "   AND event.objid IN leaves"
        " ORDER BY event.mtime DESC"

      );
      print_timeline(&q, 100);
      db_finalize(&q);
      fossil_fatal("Multiple descendents");
    }
    tid = db_int(0, "SELECT rid FROM leaves"); 


  }

  db_begin_transaction();
  vfile_check_signature(vid);
  undo_begin();
  load_vfile_from_rid(tid);








>
>
>
>
>
>
>




|




|
>




>
>
>



|

>
>

>
>






|




>
>
>
>





|


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

|

<
<
<
<
>

|
>





|
>
>







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
**
** This file contains code used to merge the changes in the current
** checkout into a different version and switch to that version.
*/
#include "config.h"
#include "update.h"
#include <assert.h>

/*
** Return true if artifact rid is a version
*/
int is_a_version(int rid){
  return db_exists("SELECT 1 FROM plink WHERE cid=%d", rid);
}

/*
** COMMAND: update
**
** Usage: %fossil update ?VERSION? ?--force? ?--latest?
**
** The optional argument is a version that should become the current
** version.  If the argument is omitted, then use the leaf of the
** tree that begins with the current version, if there is only a 
** single leaf.  If there are a multiple leaves, the latest is used
** if the --latest flag is present.
**
** This command is different from the "checkout" in that edits are
** not overwritten.  Edits are merged into the new version.
**
** If there are uncommitted edits and the safemerge option is
** enabled then no update will occur unless you provide the 
** --force flag.
*/
void update_cmd(void){
  int vid;              /* Current version */
  int tid=0;            /* Target version - version we are changing to */
  Stmt q;
  int latestFlag;       /* Pick the latest version if true */
  int forceFlag;        /* True force the update */

  latestFlag = find_option("latest",0, 0)!=0;
  forceFlag = find_option("force","f",0)!=0;
  if( g.argc!=3 && g.argc!=2 ){
    usage("?VERSION?");
  }
  db_must_be_within_tree();
  vid = db_lget_int("checkout", 0);
  if( vid==0 ){
    fossil_fatal("cannot find current version");
  }
  if( db_exists("SELECT 1 FROM vmerge") ){
    fossil_fatal("cannot update an uncommitted merge");
  }
  if( !forceFlag && db_get_int("safemerge", 0) && unsaved_changes() ){
    fossil_fatal("there are uncommitted changes and safemerge is enabled");
  }

  if( g.argc==3 ){
    tid = name_to_rid(g.argv[2]);
    if( tid==0 ){
      fossil_fatal("not a version: %s", g.argv[2]);
    }
    if( !is_a_version(tid) ){
      fossil_fatal("not a version: %s", g.argv[2]);
    }
  }

  if( tid==0 ){
    /* 
    ** Do an autosync pull prior to the update, if autosync is on and they
    ** did not want a specific version (i.e. another branch, a past revision).
    ** By not giving a specific version, they are asking for the latest, thus
    ** pull to get the latest, then update.
    */
    autosync(1);
  }
  
  if( tid==0 ){
    compute_leaves(vid);
    if( !latestFlag && db_int(0, "SELECT count(*) FROM leaves")>1 ){
      db_prepare(&q, 




        "%s "
        "   AND event.objid IN leaves"
        " ORDER BY event.mtime DESC",
        timeline_query_for_tty()
      );
      print_timeline(&q, 100);
      db_finalize(&q);
      fossil_fatal("Multiple descendents");
    }
    tid = db_int(0, "SELECT rid FROM leaves, event"
                    " WHERE event.objid=leaves.rid"
                    " ORDER BY event.mtime DESC"); 
  }

  db_begin_transaction();
  vfile_check_signature(vid);
  undo_begin();
  load_vfile_from_rid(tid);

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
































































































        zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
        unlink(zFullPath);
        free(zFullPath);
      }
    }else if( idt>0 && idv>0 && ridt!=ridv && chnged ){
      /* Merge the changes in the current tree into the target version */
      Blob e, r, t, v;

      char *zFullPath;
      printf("MERGE %s\n", zName);
      undo_save(zName);
      zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
      content_get(ridt, &t);
      content_get(ridv, &v);
      blob_zero(&e);
      blob_read_from_file(&e, zFullPath);
      blob_merge(&v, &e, &t, &r);

      blob_write_to_file(&r, zFullPath);






      free(zFullPath);
      blob_reset(&v);
      blob_reset(&e);
      blob_reset(&t);
      blob_reset(&r);

    }
  }
  db_finalize(&q);
  
  /*
  ** Clean up the mid and pid VFILE entries.  Then commit the changes.
  */
  db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid);
  manifest_to_disk(tid);
  db_lset_int("checkout", tid);
  db_end_transaction(0);
}







































































































>








|
>
|
>
>
>
>
>
>





>












>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
        zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
        unlink(zFullPath);
        free(zFullPath);
      }
    }else if( idt>0 && idv>0 && ridt!=ridv && chnged ){
      /* Merge the changes in the current tree into the target version */
      Blob e, r, t, v;
      int rc;
      char *zFullPath;
      printf("MERGE %s\n", zName);
      undo_save(zName);
      zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
      content_get(ridt, &t);
      content_get(ridv, &v);
      blob_zero(&e);
      blob_read_from_file(&e, zFullPath);
      rc = blob_merge(&v, &e, &t, &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);
      }
      free(zFullPath);
      blob_reset(&v);
      blob_reset(&e);
      blob_reset(&t);
      blob_reset(&r);
      
    }
  }
  db_finalize(&q);
  
  /*
  ** Clean up the mid and pid VFILE entries.  Then commit the changes.
  */
  db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid);
  manifest_to_disk(tid);
  db_lset_int("checkout", tid);
  db_end_transaction(0);
}


/*
** Get the contents of a file within a given revision.
*/
int historical_version_of_file(
  const char *revision,    /* The baseline name containing the file */
  const char *file,        /* Full treename of the file */
  Blob *content            /* Put the content here */
){
  Blob mfile;
  Manifest m;
  int i, rid=0;
  
  rid = name_to_rid(revision);
  content_get(rid, &mfile);
  
  if( manifest_parse(&m, &mfile) ){
    for(i=0; i<m.nFile; i++){
      if( strcmp(m.aFile[i].zName, file)==0 ){
        rid = uuid_to_rid(m.aFile[i].zUuid, 0);
        return content_get(rid, content);
      }
    }
    fossil_fatal("file %s does not exist in baseline: %s", file, revision);
  }else{
    fossil_panic("could not parse manifest for baseline: %s", revision);
  }
  return 0;
}


/*
** COMMAND: revert
**
** Usage: %fossil revert ?--yes? ?-r REVISION? FILE
**
** Revert to the current repository version of FILE, or to
** the version associated with baseline REVISION if the -r flag
** appears.  This command will confirm your operation unless the
** file is missing or the --yes option is used.
**/
void revert_cmd(void){
  const char *zFile;
  const char *zRevision;
  Blob fname;
  Blob record;
  Blob ans;
  int rid = 0, yesRevert;
  
  yesRevert = find_option("yes", "y", 0)!=0;
  zRevision = find_option("revision", "r", 1);
  verify_all_options();
  
  if( g.argc<3 ){
    usage("?OPTIONS FILE");
  }
  db_must_be_within_tree();
  
  zFile = mprintf("%/", g.argv[g.argc-1]);

  if( !file_tree_name(zFile, &fname) ){
    fossil_panic("unknown file: %s", zFile);
  }

  if( access(zFile, 0) ) yesRevert = 1;  
  if( yesRevert==0 ){
    char *prompt = mprintf("revert file %B? this will"
                           " destroy local changes [y/N]? ",
                           &fname);
    blob_zero(&ans);
    prompt_user(prompt, &ans);
    if( blob_str(&ans)[0]=='y' ){
      yesRevert = 1;
    }
  }

  if( yesRevert==1 && zRevision!=0 ){
    historical_version_of_file(zRevision, zFile, &record);
  }else if( yesRevert==1 ){
    rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B", &fname);
    if( rid==0 ){
      fossil_panic("no history for file: %b", &fname);
    }
    content_get(rid, &record);
  }
  
  if( yesRevert==1 ){
    blob_write_to_file(&record, zFile);
    printf("%s reverted\n", zFile);
    blob_reset(&record);
    blob_reset(&fname);
  }else{
    printf("revert canceled\n");
  }
}
Changes to src/user.c.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
** Copyright (c) 2006 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











|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
** Copyright (c) 2006 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
42
43
44
45
46
47
48








































49
50
51
52
53
54
55
       z[i] = 0;
       break;
    }
    if( z[i]<' ' ) z[i] = ' ';
  }
  blob_append(pBlob, z, -1);
}









































/*
** Do a single prompt for a passphrase.  Store the results in the blob.
*/
static void prompt_for_passphrase(const char *zPrompt, Blob *pPassphrase){
  char *z = getpass(zPrompt);
  strip_string(pPassphrase, z);







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







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
       z[i] = 0;
       break;
    }
    if( z[i]<' ' ) z[i] = ' ';
  }
  blob_append(pBlob, z, -1);
}

#ifdef __MINGW32__
/*
** getpass for Windows
*/
static char *getpass(const char *prompt){
  static char pwd[64];
  size_t i;

  fputs(prompt,stderr);
  fflush(stderr);
  for(i=0; i<sizeof(pwd)-1; ++i){
    pwd[i] = _getch();
    if(pwd[i]=='\r' || pwd[i]=='\n'){
      break;
    }
    /* BS or DEL */
    else if(i>0 && (pwd[i]==8 || pwd[i]==127)){
      i -= 2;
      continue;
    }
    /* CTRL-C */
    else if(pwd[i]==3) {
      i=0;
      break;
    }
    /* ESC */
    else if(pwd[i]==27){
      i=0;
      break;
    }
    else{
      fputc('*',stderr);
    }
  }
  pwd[i]='\0';
  fputs("\n", stderr);
  return pwd;
}
#endif

/*
** Do a single prompt for a passphrase.  Store the results in the blob.
*/
static void prompt_for_passphrase(const char *zPrompt, Blob *pPassphrase){
  char *z = getpass(zPrompt);
  strip_string(pPassphrase, z);
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
**        Query or set the default user.  The default user is the
**        user for command-line interaction.
**
**    %fossil user list
**
**        List all users known to the repository
**
**    %fossil user new
**
**        Create a new user in the repository.  Users can never be
**        deleted.  They can be denied all access but they must continue
**        to exist in the database.
**
**    %fossil user password USERNAME
**
**        Change the web access password for a user.
*/
void user_cmd(void){
  int n;
  db_find_and_open_repository();
  if( g.argc<3 ){
    usage("capabilities|default|list|new|password ...");
  }
  n = strlen(g.argv[2]);
  if( n>=2 && strncmp(g.argv[2],"new",n)==0 ){
    Blob passwd, login, contact;





    prompt_user("login: ", &login);




    prompt_user("contact-info: ", &contact);
    prompt_for_password("password: ", &passwd, 1);
    db_multi_exec(
      "INSERT INTO user(login,pw,cap,info)"
      "VALUES(%B,%B,'jnor',%B)",
      &login, &passwd, &contact
    );
  }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
    user_select();
    if( g.argc==3 ){
      printf("%s\n", g.zLogin);
    }else if( g.localOpen ){
      db_lset("default-user", g.zLogin);
    }else{
      db_set("default-user", g.zLogin);
    }
  }else if( n>=2 && strncmp(g.argv[2],"list",n)==0 ){
    Stmt q;
    db_prepare(&q, "SELECT login, info FROM user ORDER BY login");
    while( db_step(&q)==SQLITE_ROW ){
      printf("%-12s %s\n", db_column_text(&q, 0), db_column_text(&q, 1));
    }
    db_finalize(&q);
  }else if( n>=2 && strncmp(g.argv[2],"password",2)==0 ){
    char *zPrompt;
    int uid;
    Blob pw;
    if( g.argc!=4 ) usage("user password USERNAME");
    uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
    if( uid==0 ){
      fossil_fatal("no such user: %s", g.argv[3]);
    }
    zPrompt = mprintf("new passwd for %s: ", g.argv[3]);
    prompt_for_password(zPrompt, &pw, 1);
    if( blob_size(&pw)==0 ){







|



















>
>
>
>
|
>
>
>
>














|












|







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
**        Query or set the default user.  The default user is the
**        user for command-line interaction.
**
**    %fossil user list
**
**        List all users known to the repository
**
**    %fossil user new ?USERNAME?
**
**        Create a new user in the repository.  Users can never be
**        deleted.  They can be denied all access but they must continue
**        to exist in the database.
**
**    %fossil user password USERNAME
**
**        Change the web access password for a user.
*/
void user_cmd(void){
  int n;
  db_find_and_open_repository();
  if( g.argc<3 ){
    usage("capabilities|default|list|new|password ...");
  }
  n = strlen(g.argv[2]);
  if( n>=2 && strncmp(g.argv[2],"new",n)==0 ){
    Blob passwd, login, contact;

    if( g.argc>=4 ){
      blob_zero(&login);
      blob_append(&login, g.argv[3], -1);
    }else{
      prompt_user("login: ", &login);
    }
    if( db_exists("SELECT 1 FROM user WHERE login=%B", &login) ){
      fossil_fatal("user %b already exists", &login);
    }
    prompt_user("contact-info: ", &contact);
    prompt_for_password("password: ", &passwd, 1);
    db_multi_exec(
      "INSERT INTO user(login,pw,cap,info)"
      "VALUES(%B,%B,'jnor',%B)",
      &login, &passwd, &contact
    );
  }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
    user_select();
    if( g.argc==3 ){
      printf("%s\n", g.zLogin);
    }else if( g.localOpen ){
      db_lset("default-user", g.zLogin);
    }else{
      db_set("default-user", g.zLogin, 0);
    }
  }else if( n>=2 && strncmp(g.argv[2],"list",n)==0 ){
    Stmt q;
    db_prepare(&q, "SELECT login, info FROM user ORDER BY login");
    while( db_step(&q)==SQLITE_ROW ){
      printf("%-12s %s\n", db_column_text(&q, 0), db_column_text(&q, 1));
    }
    db_finalize(&q);
  }else if( n>=2 && strncmp(g.argv[2],"password",2)==0 ){
    char *zPrompt;
    int uid;
    Blob pw;
    if( g.argc!=4 ) usage("password USERNAME");
    uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
    if( uid==0 ){
      fossil_fatal("no such user: %s", g.argv[3]);
    }
    zPrompt = mprintf("new passwd for %s: ", g.argv[3]);
    prompt_for_password(zPrompt, &pw, 1);
    if( blob_size(&pw)==0 ){
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

  if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return;

  if( attempt_user(db_get("default-user", 0)) ) return;

  if( attempt_user(getenv("USER")) ) return;

  db_prepare(&s, "SELECT uid, login FROM user WHERE login<>'anonymous'");

  if( db_step(&s)==SQLITE_ROW ){
    g.userUid = db_column_int(&s, 0);
    g.zLogin = mprintf("%s", db_column_text(&s, 1));
  }
  db_finalize(&s);

  if( g.userUid==0 ){
    db_prepare(&s, "SELECT uid, login FROM user");
    if( db_step(&s)==SQLITE_ROW ){
      g.userUid = db_column_int(&s, 0);
      g.zLogin = mprintf("%s", db_column_text(&s, 1));
    }
    db_finalize(&s);
  }

  if( g.userUid==0 ){
    db_multi_exec(
      "INSERT INTO user(login, pw, cap, info)"
      "VALUES('anonymous', '', '', '')"
    );
    g.userUid = db_last_insert_rowid();
    g.zLogin = "anonymous";
  }
}







|
>


















|





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

  if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return;

  if( attempt_user(db_get("default-user", 0)) ) return;

  if( attempt_user(getenv("USER")) ) return;

  db_prepare(&s, "SELECT uid, login FROM user"
                 " WHERE login NOT IN ('anonymous','nobody')");
  if( db_step(&s)==SQLITE_ROW ){
    g.userUid = db_column_int(&s, 0);
    g.zLogin = mprintf("%s", db_column_text(&s, 1));
  }
  db_finalize(&s);

  if( g.userUid==0 ){
    db_prepare(&s, "SELECT uid, login FROM user");
    if( db_step(&s)==SQLITE_ROW ){
      g.userUid = db_column_int(&s, 0);
      g.zLogin = mprintf("%s", db_column_text(&s, 1));
    }
    db_finalize(&s);
  }

  if( g.userUid==0 ){
    db_multi_exec(
      "INSERT INTO user(login, pw, cap, info)"
      "VALUES('anonymous', '', 'cfghjkmnoqw', '')"
    );
    g.userUid = db_last_insert_rowid();
    g.zLogin = "anonymous";
  }
}
Changes to src/verify.c.
18
19
20
21
22
23
24
25






26
27
28
29
30
31
32
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to help verify the integrity of the
** the repository






*/
#include "config.h"
#include "verify.h"
#include <assert.h>

/*
** Load the record identify by rid.  Make sure we can reproduce it







|
>
>
>
>
>
>







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to help verify the integrity of the
** the repository.
**
** This file primarily implements the verify_before_commit() interface.
** Any function can call verify_before_commit() with a record id (RID)
** as an argument.  Then before the next change to the database commits,
** this routine will reach in and check that the record can be extracted
** correctly from the BLOB table.
*/
#include "config.h"
#include "verify.h"
#include <assert.h>

/*
** Load the record identify by rid.  Make sure we can reproduce it
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
    }
    blob_reset(&hash);
  }
  blob_reset(&uuid);
}

/*


**  





*/
static int verify_at_commit(void *notUsed){
  Stmt q;
  db_prepare(&q, "SELECT rid FROM toverify");

  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    verify_rid(rid);

  }
  db_finalize(&q);
  db_multi_exec("DELETE FROM toverify");
  return 0;
}

/*
** Arrange to verify a particular record prior to committing.
** 
** If the record rid is less than 1, then just initialize the
** verification system but do not record anything as needing
** verification.
*/
void verify_before_commit(int rid){
  static int isInit = 0;
  if( !isInit ){
    db_multi_exec(
       "CREATE TEMP TABLE toverify(rid INTEGER PRIMARY KEY);"
    );
    sqlite3_commit_hook(g.db, verify_at_commit, 0);
    isInit = 1;
  }

  if( rid>0 ){
    db_multi_exec(
      "INSERT OR IGNORE INTO toverify VALUES(%d)", rid
    );
  }
}

/*
** COMMAND: test-verify-all
**
** Verify all records in the repository.
*/
void verify_all_cmd(void){
  Stmt q;

  db_must_be_within_tree();
  db_prepare(&q, "SELECT rid FROM blob");
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    verify_rid(rid);


  }
  db_finalize(&q);


}







>
>
|
>
>
>
>
>

|
|
|
>
|
<

>

|
|













<
<
<
|


>

<
<
|










>




|
>
>


>
>

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
    }
    blob_reset(&hash);
  }
  blob_reset(&uuid);
}

/*
** The following bag holds the rid for every record that needs
** to be verified.
*/
static Bag toVerify;
static int inFinalVerify = 0;

/*
** This routine is called just prior to each commit operation.  
*/
static int verify_at_commit(void){
  int rid;
  inFinalVerify = 1;
  rid = bag_first(&toVerify);
  while( rid>0 ){

    verify_rid(rid);
    rid = bag_next(&toVerify, rid);
  }
  bag_clear(&toVerify);
  inFinalVerify = 0;
  return 0;
}

/*
** Arrange to verify a particular record prior to committing.
** 
** If the record rid is less than 1, then just initialize the
** verification system but do not record anything as needing
** verification.
*/
void verify_before_commit(int rid){
  static int isInit = 0;
  if( !isInit ){



    db_commit_hook(verify_at_commit, 1000);
    isInit = 1;
  }
  assert( !inFinalVerify );
  if( rid>0 ){


    bag_insert(&toVerify, rid);
  }
}

/*
** COMMAND: test-verify-all
**
** Verify all records in the repository.
*/
void verify_all_cmd(void){
  Stmt q;
  int cnt = 0;
  db_must_be_within_tree();
  db_prepare(&q, "SELECT rid FROM blob");
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    verify_before_commit(rid);
    cnt++;
    assert( bag_count(&toVerify)==cnt );
  }
  db_finalize(&q);
  verify_at_commit();
  assert( bag_count(&toVerify)==0 );
}
Changes to src/vfile.c.
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
    "INSERT INTO vfile(vid,rid,mrid,pathname) "
    " VALUES(:vid,:id,:id,:name)");
  db_bind_int(&ins, ":vid", vid);
  while( blob_line(p, &line) ){
    char *z = blob_buffer(&line);
    if( z[0]=='-' ){
      if( seenHeader ) break;
      while( blob_line(p, &line)>1 ){}
      if( blob_line(p, &line)==0 ) break;
    }
    seenHeader = 1;
    if( z[0]!='F' || z[1]!=' ' ) continue;
    blob_token(&line, &token);  /* Skip the "F" token */
    if( blob_token(&line, &name)==0 ) break;
    if( blob_token(&line, &uuid)==0 ) break;







|







96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
    "INSERT INTO vfile(vid,rid,mrid,pathname) "
    " VALUES(:vid,:id,:id,:name)");
  db_bind_int(&ins, ":vid", vid);
  while( blob_line(p, &line) ){
    char *z = blob_buffer(&line);
    if( z[0]=='-' ){
      if( seenHeader ) break;
      while( blob_line(p, &line)>2 ){}
      if( blob_line(p, &line)==0 ) break;
    }
    seenHeader = 1;
    if( z[0]!='F' || z[1]!=' ' ) continue;
    blob_token(&line, &token);  /* Skip the "F" token */
    if( blob_token(&line, &name)==0 ) break;
    if( blob_token(&line, &uuid)==0 ) break;
Changes to src/wiki.c.
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










































































































**
** This file contains code to do formatting of wiki text.
*/
#include <assert.h>
#include "config.h"
#include "wiki.h"


/*

** Create a fake replicate of the "vfile" table as a TEMP table
** using the manifest identified by manid.



*/
static void create_fake_vfile(int manid){
  static const char zVfileDef[] = 
    @ CREATE TEMP TABLE vfile(
    @   id INTEGER PRIMARY KEY,     -- ID of the checked out file
    @   vid INTEGER REFERENCES blob, -- The version 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
    @   pathname TEXT,              -- Full pathname
    @   UNIQUE(pathname,vid)

    @ );
    ;
  db_multi_exec(zVfileDef);
  load_vfile_from_rid(manid);
}

/*

** Locate the wiki page with the name zPageName and render it.
*/
static void locate_and_render_wikipage(const char *zPageName){
  Stmt q;
  int id = 0;
  int rid = 0;
  int chnged = 0;
  char *zPathname = 0;
  db_prepare(&q,

     "SELECT id, rid, chnged, pathname FROM vfile"
     " WHERE (pathname='%q.wiki' OR pathname LIKE '%%/%q.wiki')"
     "   AND NOT deleted", zPageName, zPageName



  );
  if( db_step(&q)==SQLITE_ROW ){
    id = db_column_int(&q, 0);
    rid = db_column_int(&q, 1);
    chnged = db_column_int(&q, 2);
    if( chnged || rid==0 ){
      zPathname = db_column_malloc(&q, 3);
    }

  }
  db_finalize(&q);
  if( id ){
    Blob page, src;
    char *zTitle = "wiki";
    char *z;
    blob_zero(&src);
    if( zPathname ){
      zPathname = mprintf("%s/%z", g.zLocalRoot, zPathname);
      blob_read_from_file(&src, zPathname);
      free(zPathname);
    }else{
      content_get(rid, &src);
    }

    /* The wiki page content is now in src.  Check to see if
    ** there is a <readonly/> or <appendonly/> element at the
    ** beginning of the content.


    */

    z = blob_str(&src);
    while( isspace(*z) ) z++;
    if( strncmp(z, "<readonly/>", 11)==0 ){



      z += 11;
    }else if( strncmp(z, "<appendonly/>", 13)==0 ){

      z += 13;



    }
    
    /* Check for <title>...</title> markup and remove it if present. */
    while( isspace(*z) ) z++;
    if( strncmp(z, "<title>", 7)==0 ){
      int i;
      for(i=7; z[i] && z[i]!='<'; i++){}
      if( z[i]=='<' && strncmp(&z[i], "</title>", 8)==0 ){
        zTitle = htmlize(&z[7], i-7);
        z = &z[i+8];
      }
    }
    
    /* Render the page */

    style_header(zTitle);
    blob_init(&page, z, -1);
    wiki_convert(&page, cgi_output_blob(), WIKI_HTML);
    blob_reset(&src);
  }else{
    style_header("Unknown Wiki Page");
    @ The wiki page "%h(zPageName)" does not exist.
  }
  style_footer();
}

/*
** WEBPAGE: wiki
** URL: /wiki/PAGENAME
**
** If the local database is available (which only happens if run








** as "server" instead of "cgi" or "http") then the file is taken


















** from the local checkout.  If there is no local checkout, then












































** the content is taken from the "head" baseline.



*/
void wiki_page(void){

  login_check_credentials();

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


    int headid = db_int(0,
       "SELECT cid FROM plink ORDER BY mtime DESC LIMIT 1"
    );
    create_fake_vfile(headid);


  }
  locate_and_render_wikipage(g.zExtra);
}





















































/*
** The g.zExtra value is of the form UUID/otherstuff.
** Extract the UUID and convert it to a record id.  Leave
** g.zExtra holding just otherstuff.  If UUID does not exist
** or is malformed, return 0 and leave g.zExtra unchanged.
*/

int extract_uuid_from_url(void){
  int i, rid;

  Blob uuid;
  for(i=0; g.zExtra[i] && g.zExtra[i]!='/'; i++){}



















  blob_zero(&uuid);
  blob_append(&uuid, g.zExtra, i);

  rid = name_to_uuid(&uuid, 0);

  blob_reset(&uuid);
  if( rid ){
    while( g.zExtra[i]=='/' ){ i++; }
    g.zExtra = &g.zExtra[i];
  }
  return rid;  


}
































































/*

** WEBPAGE: bwiki
** URL: /bwiki/UUID/PAGENAME




**
** UUID specifies a baseline.  Render the wiki page PAGENAME as

** it appears in that baseline.
*/


void bwiki_page(void){
  int headid;
  login_check_credentials();

  if( !g.okRdWiki || !g.okHistory ){ login_needed(); return; }
  headid = extract_uuid_from_url();






  if( headid ){
    create_fake_vfile(headid);

  }

  locate_and_render_wikipage(g.zExtra);









}

















































/*
** WEBPAGE: ambiguous

**













** This is the destination for UUID hyperlinks that are ambiguous.











** Show all possible choices for the destination with links to each.


**
** The ambiguous UUID prefix is in g.zExtra

*/
void ambiguous_page(void){
  Stmt q;


  style_header("Ambiguous UUID");
  @ <p>The link <a href="%s(g.zBaseURL)/ambiguous/%T(g.zExtra)">
  @ [%h(g.zExtra)]</a> is ambiguous.  It might mean any of the following:</p>
  @ <ul>
  db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid>=%Q AND uuid<'%qz'"

                 " ORDER BY uuid", g.zExtra, g.zExtra);

  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    int rid = db_column_int(&q, 1);
    @ <li> %s(zUuid) - %d(rid)
  }
  db_finalize(&q);
  @ </ul>
  style_footer();
}

















































































































<

>
|
|
>
>
>

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



>
|

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




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

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

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

|
>

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

<
>

|

>
>
|
<
<

|
>
|
>

|
<
|





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
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
556
557
558
559
560
561
562
563
564
565
566
567
568
569
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
**
** This file contains code to do formatting of wiki text.
*/
#include <assert.h>
#include "config.h"
#include "wiki.h"


/*
** Return true if the input string is a well-formed wiki page name.
**
** Well-formed wiki page names do not begin or end with whitespace,
** and do not contain tabs or other control characters and do not
** contain more than a single space character in a row.  Well-formed
** names must be between 3 and 100 chracters in length, inclusive.
*/

int wiki_name_is_wellformed(const char *z){


  int i;
  if( z[0]<=0x20 ){
    return 0;

  }
  for(i=1; z[i]; i++){
    if( z[i]<0x20 ) return 0;
    if( z[i]==0x20 && z[i-1]==0x20 ) return 0;
  }
  if( z[i-1]==' ' ) return 0;

  if( i<3 || i>100 ) return 0;
  return 1;
}

/*
** Check a wiki name.  If it is not well-formed, then issue an error
** and return true.  If it is well-formed, return false.
*/
static int check_name(const char *z){

  if( !wiki_name_is_wellformed(z) ){
    style_header("Wiki Page Name Error");
    @ The wiki name "<b>%h(z)</b>" is not well-formed.  Rules for
    @ wiki page names:

    @ <ul>
    @ <li> Must not begin or end with a space.
    @ <li> Must not contain any control characters, including tab or
    @      newline.
    @ <li> Must not have two or more spaces in a row internally.
    @ <li> Must be between 3 and 100 characters in length.
    @ </ul>
    style_footer();


    return 1;



  }
  return 0;
}













/*


** WEBPAGE: home
** WEBPAGE: index
** WEBPAGE: not_found
*/
void home_page(void){
  char *zPageName = db_get("project-name",0);

  if( zPageName ){
    login_check_credentials();
    g.zExtra = zPageName;
    cgi_set_parameter_nocopy("name", g.zExtra);
    g.okRdWiki = 1;

    g.okApndWiki = 0;
    g.okWrWiki = 0;
    g.okHistory = 0;
    wiki_page();
    return;
  }
  style_header("Home");
  @ <p>This is a stub home-page for the project.
  @ To fill in this page, first go to
  @ <a href="%s(g.zBaseURL)/setup_config">setup/config</a>
  @ and establish a "Project Name".  Then create a
  @ wiki page with that name.  The content of that wiki page
  @ will be displayed in place of this message.
  style_footer();

}

/*
** Return true if the given pagename is the name of the sandbox
*/
static int is_sandbox(const char *zPagename){




  return strcasecmp(zPagename,"sandbox")==0 ||
         strcasecmp(zPagename,"sand box")==0;


}

/*
** WEBPAGE: wiki
** URL: /wiki?name=PAGENAME
*/
void wiki_page(void){
  char *zTag;
  int rid;
  int isSandbox;
  Blob wiki;
  Manifest m;
  const char *zPageName;
  char *zHtmlPageName;
  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>
    @ <li> <a href="%s(g.zBaseURL)/timeline?y=w">Recent changes</a> to wiki
    @      pages. </li>
    @ <li> <a href="%s(g.zBaseURL)/wiki_rules">Formatting rules</a> for 
    @      wiki.</li>
    @ <li> Use the <a href="%s(g.zBaseURL)/wiki?name=Sandbox">Sandbox</a>
    @      to experiment.</li>
    @ <li> <a href="%s(g.zBaseURL)/wcontent">List of All Wiki Pages</a>
    @      available on this server.</li>
    @ </ul>
    style_footer();
    return;
  }
  if( check_name(zPageName) ) return;
  isSandbox = is_sandbox(zPageName);
  if( isSandbox ){
    zBody = db_get("sandbox",zBody);
  }else{
    zTag = mprintf("wiki-%s", zPageName);
    rid = db_int(0, 
      "SELECT rid FROM tagxref"
      " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
      " ORDER BY mtime DESC", zTag
    );
    free(zTag);
    memset(&m, 0, sizeof(m));
    blob_zero(&m.content);
    if( rid ){
      Blob content;
      content_get(rid, &content);
      manifest_parse(&m, &content);
      if( m.type==CFTYPE_WIKI ){
        zBody = m.zWiki;
      }
    }
  }
  if( isSandbox || (rid && g.okWrWiki) || (!rid && g.okNewWiki) ){
    style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T",
         g.zTop, zPageName);
  }
  if( isSandbox || (rid && g.okApndWiki) ){
    style_submenu_element("Append", "Add A Comment", "%s/wikiappend?name=%T",
         g.zTop, zPageName);
  }
  if( !isSandbox && g.okHistory ){
    style_submenu_element("History", "History", "%s/whistory?name=%T",
         g.zTop, zPageName);
  }
  zHtmlPageName = mprintf("%h", zPageName);
  style_header(zHtmlPageName);
  blob_init(&wiki, zBody, -1);
  wiki_convert(&wiki, 0, 0);
  blob_reset(&wiki);
  if( !isSandbox ){
    manifest_clear(&m);
  }
  style_footer();
}

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



  const char *z;
  char *zBody = (char*)P("w");



  if( zBody ){
    zBody = mprintf("%s", zBody);
  }
  login_check_credentials();
  zPageName = PD("name","");
  if( check_name(zPageName) ) return;
  isSandbox = is_sandbox(zPageName);
  if( isSandbox ){
    if( zBody==0 ){
      zBody = db_get("sandbox","");
    }
  }else{
    zTag = mprintf("wiki-%s", zPageName);
    rid = db_int(0, 
      "SELECT rid FROM tagxref"
      " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
      " ORDER BY mtime DESC", zTag
    );
    free(zTag);
    if( (rid && !g.okWrWiki) || (!rid && !g.okNewWiki) ){
      login_needed();
      return;
    }
    memset(&m, 0, sizeof(m));
    blob_zero(&m.content);
    if( rid && zBody==0 ){
      Blob content;
      content_get(rid, &content);
      manifest_parse(&m, &content);
      if( m.type==CFTYPE_WIKI ){
        zBody = m.zWiki;
      }
    }
  }
  if( P("submit")!=0 && zBody!=0 ){
    char *zDate;
    Blob cksum;
    int nrid;
    blob_zero(&wiki);
    db_begin_transaction();
    if( isSandbox ){
      db_set("sandbox",zBody,0);
    }else{
      zDate = db_text(0, "SELECT datetime('now')");
      zDate[10] = 'T';
      blob_appendf(&wiki, "D %s\n", zDate);
      free(zDate);
      blob_appendf(&wiki, "L %F\n", zPageName);
      if( rid ){
        char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
        blob_appendf(&wiki, "P %s\n", zUuid);
        free(zUuid);
      }

      if( g.zLogin ){


        blob_appendf(&wiki, "U %F\n", g.zLogin);

      }
      blob_appendf(&wiki, "W %d\n%s\n", strlen(zBody), zBody);
      md5sum_blob(&wiki, &cksum);
      blob_appendf(&wiki, "Z %b\n", &cksum);
      blob_reset(&cksum);

      nrid = content_put(&wiki, 0, 0);
      db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
      manifest_crosslink(nrid, &wiki);
      blob_reset(&wiki);
      content_deltify(rid, nrid, 0);
    }
    db_end_transaction(0);
    cgi_redirectf("wiki?name=%T", zPageName);
  }
  if( P("cancel")!=0 ){
    cgi_redirectf("wiki?name=%T", zPageName);
    return;
  }
  if( zBody==0 ){
    zBody = mprintf("<i>Empty Page</i>");
  }
  zHtmlPageName = mprintf("Edit: %h", zPageName);
  style_header(zHtmlPageName);
  if( P("preview")!=0 ){
    blob_zero(&wiki);
    blob_append(&wiki, zBody, -1);
    @ Preview:<hr>
    wiki_convert(&wiki, 0, 0);
    @ <hr>
    blob_reset(&wiki);



  }

  for(n=2, z=zBody; z[0]; z++){
    if( z[0]=='\n' ) n++;
  }
  if( n<20 ) n = 20;
  if( n>200 ) n = 200;
  @ <form method="POST" action="%s(g.zBaseURL)/wikiedit">
  @ <input type="hidden" name="name" value="%h(zPageName)">
  @ <textarea name="w" class="wikiedit" cols="80" 
  @  rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
  @ <br>
  @ <input type="submit" name="preview" value="Preview Your Changes">
  @ <input type="submit" name="submit" value="Apply These Changes">
  @ <input type="submit" name="cancel" value="Cancel">
  @ </form>
  if( !isSandbox ){
    manifest_clear(&m);
  }
  style_footer();
}

/*
** Append the wiki text for an remark to the end of the given BLOB.
*/
static void appendRemark(Blob *p){
  char *zDate;
  const char *zUser;
  const char *zRemark;

  zDate = db_text(0, "SELECT datetime('now')");
  blob_appendf(p, "\n\n<hr><i>On %s UTC %h", zDate, g.zLogin);
  free(zDate);
  zUser = PD("u",g.zLogin);
  if( zUser[0] && strcmp(zUser,g.zLogin) ){
    blob_appendf(p, " (claiming to be %h)", zUser);
  }
  zRemark = PD("r","");
  blob_appendf(p, " added:</i><br />\n%s", zRemark);
}

/*
** WEBPAGE: wikiappend
** URL: /wikiappend?name=PAGENAME
*/
void wikiappend_page(void){
  char *zTag;
  int rid;
  int isSandbox;
  const char *zPageName;
  char *zHtmlPageName;
  const char *zUser;

  login_check_credentials();
  zPageName = PD("name","");
  if( check_name(zPageName) ) return;
  isSandbox = is_sandbox(zPageName);
  if( !isSandbox ){
    zTag = mprintf("wiki-%s", zPageName);
    rid = db_int(0, 
      "SELECT rid FROM tagxref"
      " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
      " ORDER BY mtime DESC", zTag
    );
    free(zTag);
    if( !rid ){
      cgi_redirect("index");
      return;
    }

  }
  if( !g.okApndWiki ){

    login_needed();
    return;
  }
  if( P("submit")!=0 && P("r")!=0 && P("u")!=0 ){
    char *zDate;

    Blob cksum;
    int nrid;

    Blob body;
    Blob content;
    Blob wiki;
    Manifest m;

    blob_zero(&body);
    if( isSandbox ){

      blob_appendf(&body, db_get("sandbox",""));
      appendRemark(&body);
      db_set("sandbox", blob_str(&body), 0);
    }else{
      content_get(rid, &content);
      manifest_parse(&m, &content);
      if( m.type==CFTYPE_WIKI ){

        blob_appendf(&body, m.zWiki, -1);
      }
      manifest_clear(&m);
      blob_zero(&wiki);
      db_begin_transaction();
      zDate = db_text(0, "SELECT datetime('now')");
      zDate[10] = 'T';
      blob_appendf(&wiki, "D %s\n", zDate);
      blob_appendf(&wiki, "L %F\n", zPageName);
      if( rid ){
        char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
        blob_appendf(&wiki, "P %s\n", zUuid);
        free(zUuid);
      }
      if( g.zLogin ){
        blob_appendf(&wiki, "U %F\n", g.zLogin);
      }
      blob_appendf(&body, "\n<hr>\n");
      appendRemark(&body);
      blob_appendf(&wiki, "W %d\n%s\n", blob_size(&body), blob_str(&body));
      md5sum_blob(&wiki, &cksum);
      blob_appendf(&wiki, "Z %b\n", &cksum);
      blob_reset(&cksum);
      nrid = content_put(&wiki, 0, 0);
      db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
      manifest_crosslink(nrid, &wiki);
      blob_reset(&wiki);
      content_deltify(rid, nrid, 0);
      db_end_transaction(0);
    }
    cgi_redirectf("wiki?name=%T", zPageName);
  }
  if( P("cancel")!=0 ){
    cgi_redirectf("wiki?name=%T", zPageName);
    return;
  }
  zHtmlPageName = mprintf("Append Comment To: %h", zPageName);
  style_header(zHtmlPageName);
  if( P("preview")!=0 ){
    Blob preview;
    blob_zero(&preview);
    appendRemark(&preview);
    @ Preview:<hr>
    wiki_convert(&preview, 0, 0);
    @ <hr>
    blob_reset(&preview);
  }
  zUser = PD("u", g.zLogin);
  @ <form method="POST" action="%s(g.zBaseURL)/wikiappend">
  @ <input type="hidden" name="name" value="%h(zPageName)">
  @ Your Name:
  @ <input type="text" name="u" size="20" value="%h(zUser)"><br>
  @ Comment to append:<br>
  @ <textarea name="r" class="wikiedit" cols="80" 
  @  rows="10" wrap="virtual">%h(PD("r",""))</textarea>
  @ <br>
  @ <input type="submit" name="preview" value="Preview Your Comment">
  @ <input type="submit" name="submit" value="Append Your Changes">
  @ <input type="submit" name="cancel" value="Cancel">
  @ </form>
  style_footer();
}

/*
** WEBPAGE: whistory
** URL: /whistory?name=PAGENAME
**
** Show the complete change history for a single wiki page.
*/
void whistory_page(void){
  Stmt q;
  char *zTitle;
  char *zSQL;
  const char *zPageName;
  login_check_credentials();
  if( !g.okHistory ){ login_needed(); return; }
  zPageName = PD("name","");
  zTitle = mprintf("History Of %h", 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);
  www_print_timeline(&q, 0, 0, 0, 0);
  db_finalize(&q);
  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();
}

/*
** WEBPAGE: wiki_rules
*/
void wikirules_page(void){
  style_header("Wiki Formatting Rules");
  @ <h2>Formatting Rule Summary</h2>
  @ <ol>
  @ <li> Blank lines are paragraph breaks
  @ <li> Bullet list items are a "*" at the beginning of the line.
  @ <li> Enumeration list items are a number at the beginning of a line.
  @ <li> Indented pargraphs begin with a tab or two spaces.
  @ <li> Hyperlinks are contained with square brackets:  "[target]"
  @ <li> Most ordinary HTML works.
  @ <li> &lt;verbatim&gt; and &lt;nowiki&gt;.
  @ </ol>
  @ <p>We call the first five rules above "wiki" formatting rules.  The
  @ last two rules are the HTML formatting rule.</p>
  @ <h2>Formatting Rule Details</h2>
  @ <ol>
  @ <li> <p><b>Paragraphs</b>.  Any sequence of one or more blank lines forms
  @ a paragraph break.  Centered or right-justified paragraphs are not
  @ supported by wiki markup, but you can do these things if you need them
  @ using HTML.</p>
  @ <li> <p><b>Bullet Lists</b>.
  @ A bullet list item begins with a single "*" character surrounded on
  @ both sides by two or more spaces or by a tab.  Only a single level
  @ of bullet list is supported by wiki.  For tested lists, use HTML.</p>
  @ <li> <p><b>Enumeration Lists</b>.
  @ An enumeration list item begins with one or more digits optionally
  @ followed by a "." surrounded on both sides by two or more spaces or
  @ by a tab.  The number is significant and becomes the number shown
  @ in the rendered enumeration item.  Only a single level of enumeration
  @ list is supported by wiki.  For nested enumerations or for
  @ enumerations that count using letters or roman numerials, use HTML.</p>
  @ <li> <p><b>Indented Paragraphs</b>.
  @ Any paragraph that begins with two or more spaces or a tab and
  @ which is not a bullet or enumeration list item is rendered 
  @ indented.  Only a single level of indentation is supported by</p>
  @ <li> <p><b>Hyperlinks</b>.
  @ Text within square brackets ("[...]") becomes a hyperlink.  The
  @ target can be a wiki page name, the UUID of a check-in or ticket,
  @ the name of an image, or a URL.  By default, the target is displayed
  @ as the text of the hyperlink.  But you can specify alternative text
  @ after the target name separated by a "|" character.</p>
  @ <li> <p><b>HTML</b>.
  @ The following standard HTML elements may be used:
  @ &lt;a&gt;
  @ &lt;address&gt;
  @ &lt;b&gt;
  @ &lt;big&gt;
  @ &lt;blockquote&gt;
  @ &lt;br&gt;
  @ &lt;center&gt;
  @ &lt;cite&gt;
  @ &lt;code&gt;
  @ &lt;dd&gt;
  @ &lt;dfn&gt;
  @ &lt;dl&gt;
  @ &lt;dt&gt;
  @ &lt;em&gt;
  @ &lt;font&gt;
  @ &lt;h1&gt;
  @ &lt;h2&gt;
  @ &lt;h3&gt;
  @ &lt;h4&gt;
  @ &lt;h5&gt;
  @ &lt;h6&gt;
  @ &lt;hr&gt;
  @ &lt;img&gt;
  @ &lt;i&gt;
  @ &lt;kbd&gt;
  @ &lt;li&gt;
  @ &lt;nobr&gt;
  @ &lt;ol&gt;
  @ &lt;p&gt;
  @ &lt;pre&gt;
  @ &lt;s&gt;
  @ &lt;samp&gt;
  @ &lt;small&gt;
  @ &lt;strike&gt;
  @ &lt;strong&gt;
  @ &lt;sub&gt;
  @ &lt;sup&gt;
  @ &lt;table&gt;
  @ &lt;td&gt;
  @ &lt;th&gt;
  @ &lt;tr&gt;
  @ &lt;tt&gt;
  @ &lt;u&gt;
  @ &lt;ul&gt;
  @ &lt;var&gt;.
  @ In addition, there are two non-standard elements available:
  @ &lt;verbatim&gt; and &lt;nowiki&gt;.
  @ No other elements are allowed.  All attributes are checked and
  @ only a few benign attributes are allowed on each element.
  @ In particular, any attributes that specify javascript or CSS
  @ are elided.</p>
  @ <p>The &lt;verbatim&gt; tag disables all wiki and HTML markup
  @ up through the next &lt;/verbatim&gt;.  The &lt;nowiki&gt; tag
  @ disables all wiki formatting rules through the matching
  @ &lt;/nowiki&gt; element.
  @ </ol>
  style_footer();
}

Changes to src/wikiformat.c.
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

#if INTERFACE
/*
** Allowed wiki transformation operations
*/
#define WIKI_NOFOLLOW       0x001
#define WIKI_HTML           0x002


#endif


/*
** These are the only markup attributes allowed.
*/
#define ATTR_ALIGN              0x000001
#define ATTR_ALT                0x000002
#define ATTR_BGCOLOR            0x000004
#define ATTR_BORDER             0x000008
#define ATTR_CELLPADDING        0x000010
#define ATTR_CELLSPACING        0x000020
#define ATTR_CLEAR              0x000040
#define ATTR_COLOR              0x000080
#define ATTR_COLSPAN            0x000100
#define ATTR_COMPACT            0x000200
#define ATTR_FACE               0x000400
#define ATTR_HEIGHT             0x000800
#define ATTR_HREF               0x001000
#define ATTR_HSPACE             0x002000
#define ATTR_ID                 0x004000

#define ATTR_ROWSPAN            0x008000
#define ATTR_SIZE               0x010000
#define ATTR_SRC                0x020000
#define ATTR_START              0x040000
#define ATTR_TYPE               0x080000
#define ATTR_VALIGN             0x100000
#define ATTR_VALUE              0x200000
#define ATTR_VSPACE             0x400000
#define ATTR_WIDTH              0x800000

static const struct AllowedAttribute {
  const char *zName;
  unsigned int iMask;
} aAttribute[] = {

  { "align",         ATTR_ALIGN,          },
  { "alt",           ATTR_ALT,            },
  { "bgcolor",       ATTR_BGCOLOR,        },
  { "border",        ATTR_BORDER,         },
  { "cellpadding",   ATTR_CELLPADDING,    },
  { "cellspacing",   ATTR_CELLSPACING,    },
  { "clear",         ATTR_CLEAR,          },
  { "color",         ATTR_COLOR,          },
  { "colspan",       ATTR_COLSPAN,        },
  { "compact",       ATTR_COMPACT,        },
  { "face",          ATTR_FACE,           },
  { "height",        ATTR_HEIGHT,         },
  { "href",          ATTR_HREF,           },
  { "hspace",        ATTR_HSPACE,         },
  { "id",            ATTR_ID,             },

  { "rowspan",       ATTR_ROWSPAN,        },
  { "size",          ATTR_SIZE,           },
  { "src",           ATTR_SRC,            },
  { "start",         ATTR_START,          },
  { "type",          ATTR_TYPE,           },
  { "valign",        ATTR_VALIGN,         },
  { "value",         ATTR_VALUE,          },
  { "vspace",        ATTR_VSPACE,         },
  { "width",         ATTR_WIDTH,          },
};

/*
** Use binary search to locate a tag in the aAttribute[] table.
*/
static int findAttr(const char *z){
  int i, c, first, last;
  first = 0;
  last = sizeof(aAttribute)/sizeof(aAttribute[0]) - 1;
  while( first<=last ){
    i = (first+last)/2;
    c = strcmp(aAttribute[i].zName, z);
    if( c==0 ){
      return aAttribute[i].iMask;
    }else if( c<0 ){
      first = i+1;
    }else{
      last = i-1;
    }
  }
  return 0;
}



/*
** Allowed markup.
**
** Except for MARKUP_INVALID, this must all be in alphabetical order
** and in numerical sequence.  The first markup type must be zero.
** The value for MARKUP_XYZ must correspond to the <xyz> entry 
** in aAllowedMarkup[].
*/
#define MARKUP_INVALID         255
#define MARKUP_A                 0
#define MARKUP_ADDRESS           1

#define MARKUP_BIG               2
#define MARKUP_BLOCKQUOTE        3
#define MARKUP_B                 4
#define MARKUP_BR                5
#define MARKUP_CENTER            6
#define MARKUP_CITE              7
#define MARKUP_CODE              8
#define MARKUP_DD                9
#define MARKUP_DFN              10
#define MARKUP_DL               11
#define MARKUP_DT               12
#define MARKUP_EM               13
#define MARKUP_FONT             14
#define MARKUP_H1               15
#define MARKUP_H2               16
#define MARKUP_H3               17
#define MARKUP_H4               18
#define MARKUP_H5               19
#define MARKUP_H6               20
#define MARKUP_HR               21
#define MARKUP_IMG              22
#define MARKUP_I                23
#define MARKUP_KBD              24
#define MARKUP_LI               25
#define MARKUP_NOBR             26
#define MARKUP_NOWIKI           27
#define MARKUP_OL               28
#define MARKUP_P                29
#define MARKUP_PRE              30

#define MARKUP_SAMP             31
#define MARKUP_SMALL            32
#define MARKUP_S                33
#define MARKUP_STRIKE           34
#define MARKUP_STRONG           35
#define MARKUP_SUB              36
#define MARKUP_SUP              37
#define MARKUP_TABLE            38
#define MARKUP_TD               39
#define MARKUP_TH               40
#define MARKUP_TR               41
#define MARKUP_TT               42
#define MARKUP_UL               43
#define MARKUP_U                44

#define MARKUP_VAR              45
#define MARKUP_VERBATIM         46

/*
** The various markup is divided into the following types:
*/
#define MUTYPE_SINGLE      0x0001   /* <img>, <br>, or <hr> */
#define MUTYPE_BLOCK       0x0002   /* Forms a new paragraph. ex: <p>, <h2> */
#define MUTYPE_FONT        0x0004   /* Font changes. ex: <b>, <font>, <sub> */
#define MUTYPE_LINK        0x0008   /* Hyperlink: <a> */
#define MUTYPE_LIST        0x0010   /* Lists.  <ol>, <ul>, or <dl> */
#define MUTYPE_LI          0x0020   /* List items.  <li>, <dd>, <dt> */
#define MUTYPE_TABLE       0x0040   /* <table> */
#define MUTYPE_TR          0x0080   /* <tr> */
#define MUTYPE_TD          0x0100   /* <td> or <th> */
#define MUTYPE_SPECIAL     0x0200   /* <nowiki> or <verbatim> */
#define MUTYPE_HYPERLINK   0x0400   /* <a> */




#define MUTYPE_STACK  (MUTYPE_BLOCK | MUTYPE_FONT | MUTYPE_LIST | MUTYPE_TABLE)






static const struct AllowedMarkup {
  const char *zName;       /* Name of the markup */
  char iCode;              /* The MARKUP_* code */
  short int iType;         /* The MUTYPE_* code */
  int allowedAttr;         /* Allowed attributes on this markup */
} aMarkup[] = {

 { "a",             MARKUP_A,            MUTYPE_HYPERLINK,     ATTR_HREF },

 { "address",       MARKUP_ADDRESS,      MUTYPE_BLOCK,         0  },

 { "big",           MARKUP_BIG,          MUTYPE_FONT,          0  },
 { "blockquote",    MARKUP_BLOCKQUOTE,   MUTYPE_BLOCK,         0  },
 { "b",             MARKUP_B,            MUTYPE_FONT,          0  },
 { "br",            MARKUP_BR,           MUTYPE_SINGLE,        ATTR_CLEAR  },
 { "center",        MARKUP_CENTER,       MUTYPE_BLOCK,         0  },
 { "cite",          MARKUP_CITE,         MUTYPE_FONT,          0  },
 { "code",          MARKUP_CODE,         MUTYPE_FONT,          0  },
 { "dd",            MARKUP_DD,           MUTYPE_LI,            0  },
 { "dfn",           MARKUP_DFN,          MUTYPE_FONT,          0  },
 { "dl",            MARKUP_DL,           MUTYPE_LIST,          ATTR_COMPACT },







>
>






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





>















>
















|





|



















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

>
|
|







<








>
>
>


>
>
>
>
>






>
|
>

>


<







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

#if INTERFACE
/*
** Allowed wiki transformation operations
*/
#define WIKI_NOFOLLOW       0x001
#define WIKI_HTML           0x002
#define WIKI_INLINE         0x004  /* Do not surround with <p>..</p> */
#define WIKI_NOBLOCK        0x008  /* No block markup of any kind */
#endif


/*
** These are the only markup attributes allowed.
*/
#define ATTR_ALIGN              0x0000001
#define ATTR_ALT                0x0000002
#define ATTR_BGCOLOR            0x0000004
#define ATTR_BORDER             0x0000008
#define ATTR_CELLPADDING        0x0000010
#define ATTR_CELLSPACING        0x0000020
#define ATTR_CLEAR              0x0000040
#define ATTR_COLOR              0x0000080
#define ATTR_COLSPAN            0x0000100
#define ATTR_COMPACT            0x0000200
#define ATTR_FACE               0x0000400
#define ATTR_HEIGHT             0x0000800
#define ATTR_HREF               0x0001000
#define ATTR_HSPACE             0x0002000
#define ATTR_ID                 0x0004000
#define ATTR_NAME               0x0008000
#define ATTR_ROWSPAN            0x0010000
#define ATTR_SIZE               0x0020000
#define ATTR_SRC                0x0040000
#define ATTR_START              0x0080000
#define ATTR_TYPE               0x0100000
#define ATTR_VALIGN             0x0200000
#define ATTR_VALUE              0x0400000
#define ATTR_VSPACE             0x0800000
#define ATTR_WIDTH              0x1000000

static const struct AllowedAttribute {
  const char *zName;
  unsigned int iMask;
} aAttribute[] = {
  { 0, 0 },
  { "align",         ATTR_ALIGN,          },
  { "alt",           ATTR_ALT,            },
  { "bgcolor",       ATTR_BGCOLOR,        },
  { "border",        ATTR_BORDER,         },
  { "cellpadding",   ATTR_CELLPADDING,    },
  { "cellspacing",   ATTR_CELLSPACING,    },
  { "clear",         ATTR_CLEAR,          },
  { "color",         ATTR_COLOR,          },
  { "colspan",       ATTR_COLSPAN,        },
  { "compact",       ATTR_COMPACT,        },
  { "face",          ATTR_FACE,           },
  { "height",        ATTR_HEIGHT,         },
  { "href",          ATTR_HREF,           },
  { "hspace",        ATTR_HSPACE,         },
  { "id",            ATTR_ID,             },
  { "name",          ATTR_NAME,           },
  { "rowspan",       ATTR_ROWSPAN,        },
  { "size",          ATTR_SIZE,           },
  { "src",           ATTR_SRC,            },
  { "start",         ATTR_START,          },
  { "type",          ATTR_TYPE,           },
  { "valign",        ATTR_VALIGN,         },
  { "value",         ATTR_VALUE,          },
  { "vspace",        ATTR_VSPACE,         },
  { "width",         ATTR_WIDTH,          },
};

/*
** Use binary search to locate a tag in the aAttribute[] table.
*/
static int findAttr(const char *z){
  int i, c, first, last;
  first = 1;
  last = sizeof(aAttribute)/sizeof(aAttribute[0]) - 1;
  while( first<=last ){
    i = (first+last)/2;
    c = strcmp(aAttribute[i].zName, z);
    if( c==0 ){
      return i;
    }else if( c<0 ){
      first = i+1;
    }else{
      last = i-1;
    }
  }
  return 0;
}



/*
** Allowed markup.
**
** Except for MARKUP_INVALID, this must all be in alphabetical order
** and in numerical sequence.  The first markup type must be zero.
** The value for MARKUP_XYZ must correspond to the <xyz> entry 
** in aAllowedMarkup[].
*/
#define MARKUP_INVALID           0
#define MARKUP_A                 1
#define MARKUP_ADDRESS           2
#define MARKUP_B                 3
#define MARKUP_BIG               4
#define MARKUP_BLOCKQUOTE        5

#define MARKUP_BR                6
#define MARKUP_CENTER            7
#define MARKUP_CITE              8
#define MARKUP_CODE              9
#define MARKUP_DD               10
#define MARKUP_DFN              11
#define MARKUP_DL               12
#define MARKUP_DT               13
#define MARKUP_EM               14
#define MARKUP_FONT             15
#define MARKUP_H1               16
#define MARKUP_H2               17
#define MARKUP_H3               18
#define MARKUP_H4               19
#define MARKUP_H5               20
#define MARKUP_H6               21
#define MARKUP_HR               22
#define MARKUP_IMG              23
#define MARKUP_I                24
#define MARKUP_KBD              25
#define MARKUP_LI               26
#define MARKUP_NOBR             27
#define MARKUP_NOWIKI           28
#define MARKUP_OL               29
#define MARKUP_P                30
#define MARKUP_PRE              31
#define MARKUP_S                32
#define MARKUP_SAMP             33
#define MARKUP_SMALL            34

#define MARKUP_STRIKE           35
#define MARKUP_STRONG           36
#define MARKUP_SUB              37
#define MARKUP_SUP              38
#define MARKUP_TABLE            39
#define MARKUP_TD               40
#define MARKUP_TH               41
#define MARKUP_TR               42
#define MARKUP_TT               43

#define MARKUP_U                44
#define MARKUP_UL               45
#define MARKUP_VAR              46
#define MARKUP_VERBATIM         47

/*
** The various markup is divided into the following types:
*/
#define MUTYPE_SINGLE      0x0001   /* <img>, <br>, or <hr> */
#define MUTYPE_BLOCK       0x0002   /* Forms a new paragraph. ex: <p>, <h2> */
#define MUTYPE_FONT        0x0004   /* Font changes. ex: <b>, <font>, <sub> */

#define MUTYPE_LIST        0x0010   /* Lists.  <ol>, <ul>, or <dl> */
#define MUTYPE_LI          0x0020   /* List items.  <li>, <dd>, <dt> */
#define MUTYPE_TABLE       0x0040   /* <table> */
#define MUTYPE_TR          0x0080   /* <tr> */
#define MUTYPE_TD          0x0100   /* <td> or <th> */
#define MUTYPE_SPECIAL     0x0200   /* <nowiki> or <verbatim> */
#define MUTYPE_HYPERLINK   0x0400   /* <a> */

/*
** These markup types must have an end tag.
*/
#define MUTYPE_STACK  (MUTYPE_BLOCK | MUTYPE_FONT | MUTYPE_LIST | MUTYPE_TABLE)

/*
** This markup types are allowed for "inline" text.
*/
#define MUTYPE_INLINE (MUTYPE_FONT | MUTYPE_HYPERLINK)

static const struct AllowedMarkup {
  const char *zName;       /* Name of the markup */
  char iCode;              /* The MARKUP_* code */
  short int iType;         /* The MUTYPE_* code */
  int allowedAttr;         /* Allowed attributes on this markup */
} aMarkup[] = {
 { 0,               MARKUP_INVALID,      0,                    0  },
 { "a",             MARKUP_A,            MUTYPE_HYPERLINK,
                    ATTR_HREF|ATTR_NAME },
 { "address",       MARKUP_ADDRESS,      MUTYPE_BLOCK,         0  },
 { "b",             MARKUP_B,            MUTYPE_FONT,          0  },
 { "big",           MARKUP_BIG,          MUTYPE_FONT,          0  },
 { "blockquote",    MARKUP_BLOCKQUOTE,   MUTYPE_BLOCK,         0  },

 { "br",            MARKUP_BR,           MUTYPE_SINGLE,        ATTR_CLEAR  },
 { "center",        MARKUP_CENTER,       MUTYPE_BLOCK,         0  },
 { "cite",          MARKUP_CITE,         MUTYPE_FONT,          0  },
 { "code",          MARKUP_CODE,         MUTYPE_FONT,          0  },
 { "dd",            MARKUP_DD,           MUTYPE_LI,            0  },
 { "dfn",           MARKUP_DFN,          MUTYPE_FONT,          0  },
 { "dl",            MARKUP_DL,           MUTYPE_LIST,          ATTR_COMPACT },
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
                    ATTR_TYPE|ATTR_VALUE  },
 { "nobr",          MARKUP_NOBR,         MUTYPE_FONT,          0  },
 { "nowiki",        MARKUP_NOWIKI,       MUTYPE_SPECIAL,       0  },
 { "ol",            MARKUP_OL,           MUTYPE_LIST,          
                    ATTR_START|ATTR_TYPE|ATTR_COMPACT  },
 { "p",             MARKUP_P,            MUTYPE_BLOCK,         ATTR_ALIGN  },
 { "pre",           MARKUP_PRE,          MUTYPE_BLOCK,         0  },

 { "samp",          MARKUP_SAMP,         MUTYPE_FONT,          0  },
 { "small",         MARKUP_SMALL,        MUTYPE_FONT,          0  },
 { "s",             MARKUP_S,            MUTYPE_FONT,          0  },
 { "strike",        MARKUP_STRIKE,       MUTYPE_FONT,          0  },
 { "strong",        MARKUP_STRONG,       MUTYPE_FONT,          0  },
 { "sub",           MARKUP_SUB,          MUTYPE_FONT,          0  },
 { "sup",           MARKUP_SUP,          MUTYPE_FONT,          0  },
 { "table",         MARKUP_TABLE,        MUTYPE_TABLE,         
                    ATTR_ALIGN|ATTR_BGCOLOR|ATTR_BORDER|ATTR_CELLPADDING|
                    ATTR_CELLSPACING|ATTR_HSPACE|ATTR_VSPACE  },
 { "td",            MARKUP_TD,           MUTYPE_TD,            
                    ATTR_ALIGN|ATTR_BGCOLOR|ATTR_COLSPAN|
                    ATTR_ROWSPAN|ATTR_VALIGN  },
 { "th",            MARKUP_TH,           MUTYPE_TD,
                    ATTR_ALIGN|ATTR_BGCOLOR|ATTR_COLSPAN|
                    ATTR_ROWSPAN|ATTR_VALIGN  },
 { "tr",            MARKUP_TR,           MUTYPE_TR, 
                    ATTR_ALIGN|ATTR_BGCOLOR||ATTR_VALIGN  },
 { "tt",            MARKUP_TT,           MUTYPE_FONT,          0  },

 { "ul",            MARKUP_UL,           MUTYPE_LIST,          
                    ATTR_TYPE|ATTR_COMPACT  },
 { "u",             MARKUP_U,            MUTYPE_FONT,          0  },
 { "var",           MARKUP_VAR,          MUTYPE_FONT,          0  },
 { "verbatim",      MARKUP_VERBATIM,     MUTYPE_SPECIAL,       ATTR_ID },
};

/*
** Use binary search to locate a tag in the aMarkup[] table.
*/
static int findTag(const char *z){
  int i, c, first, last;
  first = 0;
  last = sizeof(aMarkup)/sizeof(aMarkup[0]) - 1;
  while( first<=last ){
    i = (first+last)/2;
    c = strcmp(aMarkup[i].zName, z);
    if( c==0 ){
      assert( aMarkup[i].iCode==i );
      return i;







>


<
















>


<









|







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
                    ATTR_TYPE|ATTR_VALUE  },
 { "nobr",          MARKUP_NOBR,         MUTYPE_FONT,          0  },
 { "nowiki",        MARKUP_NOWIKI,       MUTYPE_SPECIAL,       0  },
 { "ol",            MARKUP_OL,           MUTYPE_LIST,          
                    ATTR_START|ATTR_TYPE|ATTR_COMPACT  },
 { "p",             MARKUP_P,            MUTYPE_BLOCK,         ATTR_ALIGN  },
 { "pre",           MARKUP_PRE,          MUTYPE_BLOCK,         0  },
 { "s",             MARKUP_S,            MUTYPE_FONT,          0  },
 { "samp",          MARKUP_SAMP,         MUTYPE_FONT,          0  },
 { "small",         MARKUP_SMALL,        MUTYPE_FONT,          0  },

 { "strike",        MARKUP_STRIKE,       MUTYPE_FONT,          0  },
 { "strong",        MARKUP_STRONG,       MUTYPE_FONT,          0  },
 { "sub",           MARKUP_SUB,          MUTYPE_FONT,          0  },
 { "sup",           MARKUP_SUP,          MUTYPE_FONT,          0  },
 { "table",         MARKUP_TABLE,        MUTYPE_TABLE,         
                    ATTR_ALIGN|ATTR_BGCOLOR|ATTR_BORDER|ATTR_CELLPADDING|
                    ATTR_CELLSPACING|ATTR_HSPACE|ATTR_VSPACE  },
 { "td",            MARKUP_TD,           MUTYPE_TD,            
                    ATTR_ALIGN|ATTR_BGCOLOR|ATTR_COLSPAN|
                    ATTR_ROWSPAN|ATTR_VALIGN  },
 { "th",            MARKUP_TH,           MUTYPE_TD,
                    ATTR_ALIGN|ATTR_BGCOLOR|ATTR_COLSPAN|
                    ATTR_ROWSPAN|ATTR_VALIGN  },
 { "tr",            MARKUP_TR,           MUTYPE_TR, 
                    ATTR_ALIGN|ATTR_BGCOLOR||ATTR_VALIGN  },
 { "tt",            MARKUP_TT,           MUTYPE_FONT,          0  },
 { "u",             MARKUP_U,            MUTYPE_FONT,          0  },
 { "ul",            MARKUP_UL,           MUTYPE_LIST,          
                    ATTR_TYPE|ATTR_COMPACT  },

 { "var",           MARKUP_VAR,          MUTYPE_FONT,          0  },
 { "verbatim",      MARKUP_VERBATIM,     MUTYPE_SPECIAL,       ATTR_ID },
};

/*
** Use binary search to locate a tag in the aMarkup[] table.
*/
static int findTag(const char *z){
  int i, c, first, last;
  first = 1;
  last = sizeof(aMarkup)/sizeof(aMarkup[0]) - 1;
  while( first<=last ){
    i = (first+last)/2;
    c = strcmp(aMarkup[i].zName, z);
    if( c==0 ){
      assert( aMarkup[i].iCode==i );
      return i;
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
#define TOKEN_ENUM          7    /*  "  \(?\d+[.)]?  " */
#define TOKEN_INDENT        8    /*  "   " */
#define TOKEN_TEXT          9    /* None of the above */

/*
** State flags
*/
#define AT_NEWLINE        0x001  /* At start of a line */
#define AT_PARAGRAPH      0x002  /* At start of a paragraph */
#define ALLOW_WIKI        0x004  /* Allow wiki markup */
#define FONT_MARKUP_ONLY  0x008  /* Only allow MUTYPE_FONT markup */

#define IN_LIST           0x010  /* Within <ul> */




















/*
** 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.
*/
static int markupLength(const char *z){
  int n = 1;
  int inparen = 0;
  if( z[n]=='/' ){ n++; }
  if( !isalpha(z[n]) ) return 0;
  while( isalpha(z[n]) ){ n++; }
  if( z[n]!='>' && !isspace(z[n]) ) return 0;
  while( z[n] && (z[n]!='>' || inparen) ){
    if( z[n]=='"' ){
      inparen = !inparen;
    }
    n++;
  }







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












|







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
#define TOKEN_ENUM          7    /*  "  \(?\d+[.)]?  " */
#define TOKEN_INDENT        8    /*  "   " */
#define TOKEN_TEXT          9    /* None of the above */

/*
** State flags
*/
#define AT_NEWLINE          0x001  /* At start of a line */
#define AT_PARAGRAPH        0x002  /* At start of a paragraph */
#define ALLOW_WIKI          0x004  /* Allow wiki markup */
#define FONT_MARKUP_ONLY    0x008  /* Only allow MUTYPE_FONT markup */
#define INLINE_MARKUP_ONLY  0x010  /* Allow only "inline" markup */
#define IN_LIST             0x020  /* Within wiki <ul> or <ol> */

/*
** Current state of the rendering engine
*/
typedef struct Renderer Renderer;
struct Renderer {
  Blob *pOut;                 /* Output appended to this blob */
  int state;                  /* Flag that govern rendering */
  int wikiList;               /* Current wiki list type */
  int inVerbatim;             /* True in <verbatim> mode */
  int preVerbState;           /* Value of state prior to verbatim */
  int wantAutoParagraph;      /* True if a <p> is desired */
  int inAutoParagraph;        /* True if within an automatic paragraph */
  const char *zVerbatimId;    /* The id= attribute of <verbatim> */
  int nStack;                 /* Number of elements on the stack */
  int nAlloc;                 /* Space allocated for aStack */
  unsigned char *aStack;      /* Open markup stack */
};


/*
** 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.
*/
static int markupLength(const char *z){
  int n = 1;
  int inparen = 0;
  if( z[n]=='/' ){ n++; }
  if( !isalpha(z[n]) ) return 0;
  while( isalnum(z[n]) ){ n++; }
  if( z[n]!='>' && !isspace(z[n]) ) return 0;
  while( z[n] && (z[n]!='>' || inparen) ){
    if( z[n]=='"' ){
      inparen = !inparen;
    }
    n++;
  }
408
409
410
411
412
413
414




































415
416
417
418
419
420
421
    if( z[n]=='\t' ) i++;
    i++;
    n++;
  }
  if( i<2 || isspace(z[n]) ) return 0;
  return n;
}





































/*
** Check to see if the z[] string is the beginning of an indented
** paragraph.  If it is, return the length of the indent.  Otherwise
** return 0.
*/
static int indentLength(const char *z){







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







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
    if( z[n]=='\t' ) i++;
    i++;
    n++;
  }
  if( i<2 || isspace(z[n]) ) return 0;
  return n;
}

/*
** Check to see if the z[] string is the beginning of a enumeration value.
** If it is, return the length of the bullet text.  Otherwise return 0.
**
** Syntax:
**    *  a tab or two or more spaces
**    *  one or more digits
**    *  optional "."
**    *  another tab or two ore more spaces.
**
*/
static int enumLength(const char *z){
  int i, n;
  n = 0;
  i = 0;
  while( z[n]==' ' || z[n]=='\t' ){
    if( z[n]=='\t' ) i++;
    i++;
    n++;
  }
  if( i<2 ) return 0;
  for(i=0; isdigit(z[n]); i++, n++){}
  if( i==0 ) return 0;
  if( z[n]=='.' ){
    n++;
  }
  i = 0;
  while( z[n]==' ' || z[n]=='\t' ){
    if( z[n]=='\t' ) i++;
    i++;
    n++;
  }
  if( i<2 || isspace(z[n]) ) return 0;
  return n;
}

/*
** Check to see if the z[] string is the beginning of an indented
** paragraph.  If it is, return the length of the indent.  Otherwise
** return 0.
*/
static int indentLength(const char *z){
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
}


/*
** z points to the start of a token.  Return the number of
** characters in that token.  Write the token type into *pTokenType.
*/
static int nextToken(const char *z, int state, int *pTokenType){
  int n;
  if( z[0]=='<' ){
    n = markupLength(z);
    if( n>0 ){
      *pTokenType = TOKEN_MARKUP;
      return n;
    }else{
      *pTokenType = TOKEN_CHARACTER;
      return 1;
    }
  }
  if( z[0]=='&' && !isElement(z) ){
    *pTokenType = TOKEN_CHARACTER;
    return 1;
  }
  if( (state & ALLOW_WIKI)!=0 ){
    if( z[0]=='\n' ){
      n = paragraphBreakLength(z);
      if( n>0 ){
        *pTokenType = TOKEN_PARAGRAPH;
        return n;
      }else if( isspace(z[1]) ){
        *pTokenType = TOKEN_NEWLINE;
        return 1;
      }
    }
    if( (state & AT_NEWLINE)!=0 /* && (state & (AT_PARAGRAPH|IN_LIST))!=0 */
             && isspace(z[0]) ){
      n = bulletLength(z);
      if( n>0 ){
        *pTokenType = TOKEN_BULLET;
        return n;
      }
#if 0
      n = enumLength(z);
      if( n>0 ){
        *pTokenType = TOKEN_ENUM;
        return n;
      }
#endif
    }
    if( (state & AT_PARAGRAPH)!=0 && isspace(z[0]) ){
      n = indentLength(z);
      if( n>0 ){
        *pTokenType = TOKEN_INDENT;
        return n;
      }
    }
    if( z[0]=='[' && (n = linkLength(z))>0 ){
      *pTokenType = TOKEN_LINK;
      return n;
    }
  }
  *pTokenType = TOKEN_TEXT;
  return 1 + textLength(z+1, state & ALLOW_WIKI);
}

/*
** A single markup is parsed into an instance of the following
** structure.
*/
typedef struct ParsedMarkup ParsedMarkup;







|











|



|










<
|





<





<

|












|







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
556

557
558
559
560
561

562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
}


/*
** z points to the start of a token.  Return the number of
** characters in that token.  Write the token type into *pTokenType.
*/
static int nextToken(const char *z, Renderer *p, int *pTokenType){
  int n;
  if( z[0]=='<' ){
    n = markupLength(z);
    if( n>0 ){
      *pTokenType = TOKEN_MARKUP;
      return n;
    }else{
      *pTokenType = TOKEN_CHARACTER;
      return 1;
    }
  }
  if( z[0]=='&' && (p->inVerbatim || !isElement(z)) ){
    *pTokenType = TOKEN_CHARACTER;
    return 1;
  }
  if( (p->state & ALLOW_WIKI)!=0 ){
    if( z[0]=='\n' ){
      n = paragraphBreakLength(z);
      if( n>0 ){
        *pTokenType = TOKEN_PARAGRAPH;
        return n;
      }else if( isspace(z[1]) ){
        *pTokenType = TOKEN_NEWLINE;
        return 1;
      }
    }

    if( (p->state & AT_NEWLINE)!=0 && isspace(z[0]) ){
      n = bulletLength(z);
      if( n>0 ){
        *pTokenType = TOKEN_BULLET;
        return n;
      }

      n = enumLength(z);
      if( n>0 ){
        *pTokenType = TOKEN_ENUM;
        return n;
      }

    }
    if( (p->state & AT_PARAGRAPH)!=0 && isspace(z[0]) ){
      n = indentLength(z);
      if( n>0 ){
        *pTokenType = TOKEN_INDENT;
        return n;
      }
    }
    if( z[0]=='[' && (n = linkLength(z))>0 ){
      *pTokenType = TOKEN_LINK;
      return n;
    }
  }
  *pTokenType = TOKEN_TEXT;
  return 1 + textLength(z+1, p->state & ALLOW_WIKI);
}

/*
** A single markup is parsed into an instance of the following
** structure.
*/
typedef struct ParsedMarkup ParsedMarkup;
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
static void renderMarkup(Blob *pOut, ParsedMarkup *p){
  int i;
  if( p->endTag ){
    blob_appendf(pOut, "</%s>", aMarkup[p->iCode].zName);
  }else{
    blob_appendf(pOut, "<%s", aMarkup[p->iCode].zName);
    for(i=0; i<p->nAttr; i++){
      blob_appendf(pOut, " %s", aAttribute[p->aAttr[i].iCode]);
      if( p->aAttr[i].zValue ){
        blob_appendf(pOut, "=\"%s\"", p->aAttr[i].zValue);
      }
    }
    blob_append(pOut, ">", 1);
  }
}







|







669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
static void renderMarkup(Blob *pOut, ParsedMarkup *p){
  int i;
  if( p->endTag ){
    blob_appendf(pOut, "</%s>", aMarkup[p->iCode].zName);
  }else{
    blob_appendf(pOut, "<%s", aMarkup[p->iCode].zName);
    for(i=0; i<p->nAttr; i++){
      blob_appendf(pOut, " %s", aAttribute[p->aAttr[i].iCode].zName);
      if( p->aAttr[i].zValue ){
        blob_appendf(pOut, "=\"%s\"", p->aAttr[i].zValue);
      }
    }
    blob_append(pOut, ">", 1);
  }
}
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
    char *z = p->aAttr[i].zValue;
    if( z==0 ) continue;
    n = strlen(z);
    z[n] = p->aAttr[i].cTerm;
  }
}

/*
** Current state of the rendering engine
*/
typedef struct Renderer Renderer;
struct Renderer {
  Blob *pOut;                 /* Output appended to this blob */
  int state;                  /* Flag that govern rendering */
  int inVerbatim;             /* True in <verbatim> mode */
  int preVerbState;           /* Value of state prior to verbatim */
  const char *zVerbatimId;    /* The id= attribute of <verbatim> */
  int nStack;                 /* Number of elements on the stack */
  int nAlloc;                 /* Space allocated for aStack */
  unsigned char *aStack;      /* Open markup stack */
};

/*
** Pop a single element off of the stack.  As the element is popped,
** output its end tag.
*/
static void popStack(Renderer *p){
  if( p->nStack ){
    p->nStack--;







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







693
694
695
696
697
698
699















700
701
702
703
704
705
706
    char *z = p->aAttr[i].zValue;
    if( z==0 ) continue;
    n = strlen(z);
    z[n] = p->aAttr[i].cTerm;
  }
}
















/*
** Pop a single element off of the stack.  As the element is popped,
** output its end tag.
*/
static void popStack(Renderer *p){
  if( p->nStack ){
    p->nStack--;
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
718
719
720













721

722
723
724
725
726
727
728
    popStack(p);
  }
}

/*
** Pop the stack until the top-most element of the stack
** is an element that matches the type in iMask.  Return

** true on success.  If the stack does not have an element
** that matches iMask, then leave the stack unchanged and
** return false.
*/
static int backupToType(Renderer *p, int iMask){
  int i;
  for(i=p->nStack-1; i>=0 && (aMarkup[p->aStack[i]].iType&iMask)==0; i--){}
  if( i<0 ) return 0;
  i++;
  while( p->nStack>i ){
    popStack(p);
  }
  return 1;
}

/*
** Add missing markup in preparation for writing text.
**







** "Missing" markup are things like start tags for table rows

** or table columns or paragraphs that are omitted from input.
*/
static void addMissingMarkup(Renderer *p){






  /* TBD */








}

/*
** Resolve a hyperlink.  The argument is the content of the [...]
** in the wiki.  Append the URL to the output of the Renderer.
*/
static void resolveHyperlink(const char *zTarget, Renderer *p){













  blob_appendf(p->pOut, "http://www.fossil-scm.org/test-%T", zTarget);

}

/*
** Check to see if the given parsed markup is the correct
** </verbatim> tag.
*/
static int endVerbatim(Renderer *p, ParsedMarkup *pMarkup){







>
|

|









|



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

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







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







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
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
    popStack(p);
  }
}

/*
** Pop the stack until the top-most element of the stack
** is an element that matches the type in iMask.  Return
** code of the markup element that is on left on top of the stack.
** If the stack does not have an element
** that matches iMask, then leave the stack unchanged and
** return false (MARKUP_INVALID).
*/
static int backupToType(Renderer *p, int iMask){
  int i;
  for(i=p->nStack-1; i>=0 && (aMarkup[p->aStack[i]].iType&iMask)==0; i--){}
  if( i<0 ) return 0;
  i++;
  while( p->nStack>i ){
    popStack(p);
  }
  return p->aStack[i-1];
}

/*
** Begin a new paragraph if that something that is needed.
*/
static void startAutoParagraph(Renderer *p){
  if( p->wantAutoParagraph==0 ) return;
  blob_appendf(p->pOut, "<p>", -1);
  pushStack(p, MARKUP_P);
  p->wantAutoParagraph = 0;
  p->inAutoParagraph = 1;
}

/*
** End a paragraph if we are in one.
*/
static void endAutoParagraph(Renderer *p){
  if( p->inAutoParagraph ){
    popStackToTag(p, MARKUP_P);
    p->inAutoParagraph = 0;
  }
}

/*
** If the input string corresponds to an existing baseline,
** return true.
*/
static int is_valid_uuid(const char *z){
  int n = strlen(z);
  if( n<4 || n>UUID_SIZE ) return 0;
  if( !validate16(z, n) ) return 0;
  return 1;
}

/*
** Resolve a hyperlink.  The argument is the content of the [...]
** in the wiki.  Append the URL to the output of the Renderer.
*/
static void resolveHyperlink(const char *zTarget, Renderer *p){
  if( strncmp(zTarget, "http:", 5)==0 
   || strncmp(zTarget, "https:", 6)==0
   || strncmp(zTarget, "ftp:", 4)==0 
   || strncmp(zTarget, "mailto:", 7)==0
  ){
    blob_appendf(p->pOut, zTarget);
  }else if( zTarget[0]=='/' ){
    blob_appendf(p->pOut, "%s%h", g.zBaseURL, zTarget);
  }else if( is_valid_uuid(zTarget) ){
    blob_appendf(p->pOut, "%s/info/%s", g.zBaseURL, zTarget);
  }else if( wiki_name_is_wellformed(zTarget) ){
    blob_appendf(p->pOut, "%s/wiki?name=%T", g.zBaseURL, zTarget);
  }else{
    blob_appendf(p->pOut, "error");
  }
}

/*
** Check to see if the given parsed markup is the correct
** </verbatim> tag.
*/
static int endVerbatim(Renderer *p, ParsedMarkup *pMarkup){
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
794
795
796
797
798
799
800

801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837


838
839
840
841
842


843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860

861
862
863
864
865
866
867
**
** This routine will probably modify the content of z[].
*/
static void wiki_render(Renderer *p, char *z){
  int tokenType;
  ParsedMarkup markup;
  int n;


  while( z[0] ){
    n = nextToken(z, p->state, &tokenType);
    p->state &= ~(AT_NEWLINE|AT_PARAGRAPH);
    switch( tokenType ){
      case TOKEN_PARAGRAPH: {


        blob_append(p->pOut, "\n\n<p>", -1);









        p->state |= AT_PARAGRAPH|AT_NEWLINE;
        popStackToTag(p, MARKUP_P);
        break;
      }
      case TOKEN_NEWLINE: {
        blob_append(p->pOut, "\n", 1);
        p->state |= AT_NEWLINE;
        break;
      }
      case TOKEN_BULLET: {
        if( backupToType(p, MUTYPE_LIST)==0 ){






          pushStack(p, MARKUP_UL);
          blob_append(p->pOut, "<ul>", 4);

        }


        pushStack(p, MARKUP_LI);
        blob_append(p->pOut, "<li>", 4);






























        break;
      }
      case TOKEN_CHARACTER: {

        if( z[0]=='<' ){
          blob_append(p->pOut, "&lt;", 4);
        }else if( z[0]=='&' ){
          blob_append(p->pOut, "&amp;", 5);
        }
        break;
      }
      case TOKEN_LINK: {
        char *zTarget;
        char *zDisplay = 0;
        int i;
        int savedState;
        addMissingMarkup(p);
        zTarget = &z[1];
        for(i=1; z[i] && z[i]!=']'; i++){
          if( z[i]=='|' && zDisplay==0 ){
            zDisplay = &z[i+1];
            z[i] = 0;

          }
        }
        z[i] = 0;
        if( zDisplay==0 ){
          zDisplay = zTarget;
        }else{
          while( isspace(*zDisplay) ) zDisplay++;
        }
        blob_append(p->pOut, "<a href=\"", -1);
        resolveHyperlink(zTarget, p);
        blob_append(p->pOut, "\">", -1);
        savedState = p->state;
        p->state &= ~ALLOW_WIKI;
        p->state |= FONT_MARKUP_ONLY;
        wiki_render(p, zDisplay);
        p->state = savedState;
        blob_append(p->pOut, "</a>", 4);
        break;
      }
      case TOKEN_TEXT: {
        addMissingMarkup(p);
        blob_append(p->pOut, z, n);
        break;
      }
      case TOKEN_MARKUP: {
        parseMarkup(&markup, z);
        if( p->inVerbatim ){
          if( endVerbatim(p, &markup) ){
            p->inVerbatim = 0;
            p->state = p->preVerbState;
            blob_append(p->pOut, "</pre>", 6);
          }else{
            unparseMarkup(&markup);
            blob_append(p->pOut, "&lt;", 4);
            n = 1;
          }
        }else if( markup.iCode==MARKUP_INVALID ){


          blob_append(p->pOut, "&lt;", 4);
          n = 1;
        }else if( (markup.iType&MUTYPE_FONT)==0
                    && (p->state & FONT_MARKUP_ONLY)!=0 ){
          /* Do nothing */


        }else if( markup.iCode==MARKUP_NOWIKI ){
          if( markup.endTag ){
            p->state |= ALLOW_WIKI;
          }else{
            p->state &= ALLOW_WIKI;
          }
        }else if( markup.endTag ){
          popStackToTag(p, markup.iCode);
        }else if( markup.iCode==MARKUP_VERBATIM ){
          if( markup.nAttr==1 ){
            p->zVerbatimId = markup.aAttr[0].zValue;
          }else{
            p->zVerbatimId = 0;
          }
          p->inVerbatim = 1;
          p->preVerbState = p->state;
          p->state &= ~ALLOW_WIKI;
          blob_append(p->pOut, "<pre>", 5);

        }else if( markup.iType==MUTYPE_LI ){
          if( backupToType(p, MUTYPE_LIST)==0 ){
            pushStack(p, MARKUP_UL);
            blob_append(p->pOut, "<ul>", 4);
          }
          pushStack(p, MARKUP_LI);
          renderMarkup(p->pOut, &markup);







>


|



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

<








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



>










|

|





>




















|
















>
>





>
>




|













>







839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865

866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
**
** This routine will probably modify the content of z[].
*/
static void wiki_render(Renderer *p, char *z){
  int tokenType;
  ParsedMarkup markup;
  int n;
  int inlineOnly = (p->state & INLINE_MARKUP_ONLY)!=0;

  while( z[0] ){
    n = nextToken(z, p, &tokenType);
    p->state &= ~(AT_NEWLINE|AT_PARAGRAPH);
    switch( tokenType ){
      case TOKEN_PARAGRAPH: {
        if( inlineOnly ){
          /* blob_append(p->pOut, " &para; ", -1); */
          blob_append(p->pOut, " &nbsp;&nbsp; ", -1);
        }else{
          if( p->wikiList ){
            popStackToTag(p, p->wikiList);
            p->wikiList = 0;
          }
          endAutoParagraph(p);
          blob_appendf(p->pOut, "\n\n", 1);
          p->wantAutoParagraph = 1;
        }
        p->state |= AT_PARAGRAPH|AT_NEWLINE;

        break;
      }
      case TOKEN_NEWLINE: {
        blob_append(p->pOut, "\n", 1);
        p->state |= AT_NEWLINE;
        break;
      }
      case TOKEN_BULLET: {
        if( inlineOnly ){
          blob_append(p->pOut, " &#149; ", -1);
        }else{
          if( p->wikiList!=MARKUP_UL ){
            if( p->wikiList ){
              popStackToTag(p, p->wikiList);
            }
            pushStack(p, MARKUP_UL);
            blob_append(p->pOut, "<ul>", 4);
            p->wikiList = MARKUP_UL;
          }
          popStackToTag(p, MARKUP_LI);
          startAutoParagraph(p);
          pushStack(p, MARKUP_LI);
          blob_append(p->pOut, "<li>", 4);
        }
        break;
      }
      case TOKEN_ENUM: {
        if( inlineOnly ){
          blob_appendf(p->pOut, " (%d) ", atoi(z));
        }else{
          if( p->wikiList!=MARKUP_OL ){
            if( p->wikiList ){
              popStackToTag(p, p->wikiList);
            }
            pushStack(p, MARKUP_OL);
            blob_append(p->pOut, "<ol>", 4);
            p->wikiList = MARKUP_OL;
          }
          popStackToTag(p, MARKUP_LI);
          startAutoParagraph(p);
          pushStack(p, MARKUP_LI);
          blob_appendf(p->pOut, "<li value=\"%d\">", atoi(z));
        }
        break;
      }
      case TOKEN_INDENT: {
        if( inlineOnly ){
          assert( p->wikiList==0 );
          pushStack(p, MARKUP_BLOCKQUOTE);
          blob_append(p->pOut, "<blockquote>", -1);
          p->wantAutoParagraph = 0;
          p->wikiList = MARKUP_BLOCKQUOTE;
        }
        break;
      }
      case TOKEN_CHARACTER: {
        startAutoParagraph(p);
        if( z[0]=='<' ){
          blob_append(p->pOut, "&lt;", 4);
        }else if( z[0]=='&' ){
          blob_append(p->pOut, "&amp;", 5);
        }
        break;
      }
      case TOKEN_LINK: {
        char *zTarget;
        char *zDisplay = 0;
        int i, j;
        int savedState;
        startAutoParagraph(p);
        zTarget = &z[1];
        for(i=1; z[i] && z[i]!=']'; i++){
          if( z[i]=='|' && zDisplay==0 ){
            zDisplay = &z[i+1];
            z[i] = 0;
            for(j=i-1; j>0 && isspace(z[j]); j--){ z[j] = 0; }
          }
        }
        z[i] = 0;
        if( zDisplay==0 ){
          zDisplay = zTarget;
        }else{
          while( isspace(*zDisplay) ) zDisplay++;
        }
        blob_append(p->pOut, "<a href=\"", -1);
        resolveHyperlink(zTarget, p);
        blob_append(p->pOut, "\">", -1);
        savedState = p->state;
        p->state &= ~ALLOW_WIKI;
        p->state |= FONT_MARKUP_ONLY;
        wiki_render(p, zDisplay);
        p->state = savedState;
        blob_append(p->pOut, "</a>", 4);
        break;
      }
      case TOKEN_TEXT: {
        startAutoParagraph(p);
        blob_append(p->pOut, z, n);
        break;
      }
      case TOKEN_MARKUP: {
        parseMarkup(&markup, z);
        if( p->inVerbatim ){
          if( endVerbatim(p, &markup) ){
            p->inVerbatim = 0;
            p->state = p->preVerbState;
            blob_append(p->pOut, "</pre>", 6);
          }else{
            unparseMarkup(&markup);
            blob_append(p->pOut, "&lt;", 4);
            n = 1;
          }
        }else if( markup.iCode==MARKUP_INVALID ){
          unparseMarkup(&markup);
          startAutoParagraph(p);
          blob_append(p->pOut, "&lt;", 4);
          n = 1;
        }else if( (markup.iType&MUTYPE_FONT)==0
                    && (p->state & FONT_MARKUP_ONLY)!=0 ){
          /* Do nothing */
        }else if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){
          /* Do nothing */
        }else if( markup.iCode==MARKUP_NOWIKI ){
          if( markup.endTag ){
            p->state |= ALLOW_WIKI;
          }else{
            p->state &= ~ALLOW_WIKI;
          }
        }else if( markup.endTag ){
          popStackToTag(p, markup.iCode);
        }else if( markup.iCode==MARKUP_VERBATIM ){
          if( markup.nAttr==1 ){
            p->zVerbatimId = markup.aAttr[0].zValue;
          }else{
            p->zVerbatimId = 0;
          }
          p->inVerbatim = 1;
          p->preVerbState = p->state;
          p->state &= ~ALLOW_WIKI;
          blob_append(p->pOut, "<pre>", 5);
          p->wantAutoParagraph = 0;
        }else if( markup.iType==MUTYPE_LI ){
          if( backupToType(p, MUTYPE_LIST)==0 ){
            pushStack(p, MARKUP_UL);
            blob_append(p->pOut, "<ul>", 4);
          }
          pushStack(p, MARKUP_LI);
          renderMarkup(p->pOut, &markup);
875
876
877
878
879
880
881





882





883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921









922



923
924
925

926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
            if( stackTopType(p)==MUTYPE_TABLE ){
              pushStack(p, MARKUP_TR);
              blob_append(p->pOut, "<tr>", 4);
            }
            pushStack(p, markup.iCode);
            renderMarkup(p->pOut, &markup);
          }





        }else{





          if( (markup.iType & MUTYPE_STACK )!=0 ){
            pushStack(p, markup.iCode);
          }
          renderMarkup(p->pOut, &markup);
        }
        break;
      }
    }
    z += n;
  }
}


/*
** Transform the text in the pIn blob.  Write the results
** into the pOut blob.  The pOut blob should already be
** initialized.  The output is merely appended to pOut.
**
** The transformations carried out depend on the ops flag:
**
** WIKI_NOFOLLOW
**
**     * Add the nofollow attribute to external links
**
** WIKI_HTML
**
**     * Convert wiki into HTML
**     * Remove <nowiki> and <verbatium>
**     * Convert & into &amp;
**     * Unrecognized markup and markup within <verbatim>
**       is converted into &lt;...&gt;
**     * Unauthorized attributes on markup are removed
*/
void wiki_convert(Blob *pIn, Blob *pOut, int ops){
  char *z;
  Renderer renderer;
  
  memset(&renderer, 0, sizeof(renderer));
  renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH;









  renderer.pOut = pOut;




  z = blob_str(pIn);
  wiki_render(&renderer, z);

  while( renderer.nStack ){
    popStack(&renderer);
  }
  blob_append(pOut, "\n", 1);
  free(renderer.aStack);
}


/*
** COMMAND: test-wiki-render
*/
void test_wiki_render(void){
  Blob in, out;
  if( g.argc!=3 ) usage("FILE");
  blob_zero(&out);
  blob_read_from_file(&in, g.argv[2]);
  wiki_convert(&in, &out, WIKI_HTML);
  blob_write_to_file(&out, "-");
}







>
>
>
>
>

>
>
>
>
>

















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

|





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



>



|


<









|


1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055

1056
1057












1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087

1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
            if( stackTopType(p)==MUTYPE_TABLE ){
              pushStack(p, MARKUP_TR);
              blob_append(p->pOut, "<tr>", 4);
            }
            pushStack(p, markup.iCode);
            renderMarkup(p->pOut, &markup);
          }
        }else if( markup.iType==MUTYPE_HYPERLINK ){
          popStackToTag(p, markup.iCode);
          startAutoParagraph(p);
          renderMarkup(p->pOut, &markup);
          pushStack(p, markup.iCode);
        }else{
          if( markup.iType==MUTYPE_FONT ){
            startAutoParagraph(p);
          }else if( markup.iType==MUTYPE_BLOCK ){
            p->wantAutoParagraph = 0;
          }
          if( (markup.iType & MUTYPE_STACK )!=0 ){
            pushStack(p, markup.iCode);
          }
          renderMarkup(p->pOut, &markup);
        }
        break;
      }
    }
    z += n;
  }
}


/*
** Transform the text in the pIn blob.  Write the results
** into the pOut blob.  The pOut blob should already be
** initialized.  The output is merely appended to pOut.

** If pOut is NULL, then the output is appended to the CGI
** reply.












*/
void wiki_convert(Blob *pIn, Blob *pOut, int flags){
  char *z;
  Renderer renderer;
  
  memset(&renderer, 0, sizeof(renderer));
  renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH;
  if( flags & WIKI_NOBLOCK ){
    renderer.state |= INLINE_MARKUP_ONLY;
  }
  if( flags & WIKI_INLINE ){
    renderer.wantAutoParagraph = 0;
  }else{
    renderer.wantAutoParagraph = 1;
  }
  if( pOut ){
    renderer.pOut = pOut;
  }else{
    renderer.pOut = cgi_output_blob();
  }

  z = blob_str(pIn);
  wiki_render(&renderer, z);
  endAutoParagraph(&renderer);
  while( renderer.nStack ){
    popStack(&renderer);
  }
  blob_append(renderer.pOut, "\n", 1);
  free(renderer.aStack);
}


/*
** COMMAND: test-wiki-render
*/
void test_wiki_render(void){
  Blob in, out;
  if( g.argc!=3 ) usage("FILE");
  blob_zero(&out);
  blob_read_from_file(&in, g.argv[2]);
  wiki_convert(&in, &out, 0);
  blob_write_to_file(&out, "-");
}
Changes to src/xfer.c.
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
**
** If any error occurs, write a message into pErr which has already
** be initialized to an empty string.
*/
static void xfer_accept_file(Xfer *pXfer){
  int n;
  int rid;

  Blob content, hash;
  
  if( pXfer->nToken<3 
   || pXfer->nToken>4
   || !blob_is_uuid(&pXfer->aToken[1])
   || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &n)
   || n<0
   || (pXfer->nToken==4 && !blob_is_uuid(&pXfer->aToken[2]))
  ){
    blob_appendf(&pXfer->err, "malformed file line");
    return;
  }
  blob_zero(&content);
  blob_zero(&hash);
  blob_extract(pXfer->pIn, n, &content);




  if( pXfer->nToken==4 ){
    Blob src;
    int srcid = rid_from_uuid(&pXfer->aToken[2], 1);
    if( content_get(srcid, &src)==0 ){
      content_put(&content, blob_str(&pXfer->aToken[1]), srcid);
      blob_appendf(pXfer->pOut, "gimme %b\n", &pXfer->aToken[2]);
      pXfer->nGimmeSent++;
      pXfer->nDanglingFile++;
      return;
    }







>















>
>
>
>


|







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
**
** If any error occurs, write a message into pErr which has already
** be initialized to an empty string.
*/
static void xfer_accept_file(Xfer *pXfer){
  int n;
  int rid;
  int srcid = 0;
  Blob content, hash;
  
  if( pXfer->nToken<3 
   || pXfer->nToken>4
   || !blob_is_uuid(&pXfer->aToken[1])
   || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &n)
   || n<0
   || (pXfer->nToken==4 && !blob_is_uuid(&pXfer->aToken[2]))
  ){
    blob_appendf(&pXfer->err, "malformed file line");
    return;
  }
  blob_zero(&content);
  blob_zero(&hash);
  blob_extract(pXfer->pIn, n, &content);
  if( db_exists("SELECT 1 FROM shun WHERE uuid=%B", &pXfer->aToken[1]) ){
    /* Ignore files that have been shunned */
    return;
  }
  if( pXfer->nToken==4 ){
    Blob src;
    srcid = rid_from_uuid(&pXfer->aToken[2], 1);
    if( content_get(srcid, &src)==0 ){
      content_put(&content, blob_str(&pXfer->aToken[1]), srcid);
      blob_appendf(pXfer->pOut, "gimme %b\n", &pXfer->aToken[2]);
      pXfer->nGimmeSent++;
      pXfer->nDanglingFile++;
      return;
    }
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
  }else{
    manifest_crosslink(rid, &content);
  }
  remote_has(rid);
}

/*
** Try to send a file as a delta.  If successful, return the number
** of bytes in the delta.  If not, return zero.
**
** If srcId is specified, use it.  If not, try to figure out a
** reasonable srcId.
*/
static int send_as_delta(
  Xfer *pXfer,            /* The transfer context */
  int rid,                /* record id of the file to send */
  Blob *pContent,         /* The content of the file to send */
  Blob *pUuid,            /* The UUID of the file to send */
  int srcId               /* Send as a delta against this record */
){
  static const char *azQuery[] = {
    "SELECT pid FROM plink"
    " WHERE cid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)",



    "SELECT pid FROM mlink"
    " WHERE fid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)",


  };
  int i;
  Blob src, delta;
  int size = 0;


  for(i=0; srcId==0 && i<count(azQuery); i++){
    srcId = db_int(0, azQuery[i], rid);
  }
  if( srcId>0 && content_get(srcId, &src) ){
    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", srcId);
    blob_delta_create(&src, pContent, &delta);







|
|
<
|
|

|



|
<


|

|
>
>

|

|
>
>




>







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
  }else{
    manifest_crosslink(rid, &content);
  }
  remote_has(rid);
}

/*
** Try to send a file as a delta against its parent.
** If successful, return the number of bytes in the delta.

** If we cannot generate an appropriate delta, then send
** nothing and return zero.
*/
static int send_delta_parent(
  Xfer *pXfer,            /* The transfer context */
  int rid,                /* record id of the file to send */
  Blob *pContent,         /* The content of the file to send */
  Blob *pUuid             /* The UUID of the file to send */

){
  static const char *azQuery[] = {
    "SELECT pid FROM plink x"
    " WHERE cid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
    "   AND NOT EXISTS(SELECT 1 FROM plink y"
                      " WHERE y.pid=x.cid AND y.cid=x.pid)",

    "SELECT pid FROM mlink x"
    " WHERE fid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
    "   AND NOT EXISTS(SELECT 1 FROM mlink y"
                     "  WHERE y.pid=x.fid AND y.fid=x.pid)"
  };
  int i;
  Blob src, delta;
  int size = 0;
  int srcId = 0;

  for(i=0; srcId==0 && i<count(azQuery); i++){
    srcId = db_int(0, azQuery[i], rid);
  }
  if( srcId>0 && content_get(srcId, &src) ){
    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", srcId);
    blob_delta_create(&src, pContent, &delta);
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
    }
    blob_reset(&delta);
    free(zUuid);
    blob_reset(&src);
  }
  return size;
}



































/*
** Send the file identified by rid.
**
** The pUuid can be NULL in which case the correct UUID is computed
** from the rid.
**
** If srcId is positive, then a delta is sent against that srcId.
** If srcId is zero, then an attempt is made to find an appropriate
** file to delta against.   If srcId is negative, the file is sent
** without deltaing.
*/
static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int srcId){
  Blob content, uuid;
  int size = 0;

  if( db_exists("SELECT 1 FROM onremote WHERE rid=%d", rid) ){
     return;
  }
  blob_zero(&uuid);
  if( pUuid==0 ){
    db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d AND size>=0", rid);
    if( blob_size(&uuid)==0 ){
      return;
    }
    pUuid = &uuid;
  }
  if( pXfer->mxSend<=blob_size(pXfer->pOut) ){
    blob_appendf(pXfer->pOut, "igot %b\n", pUuid);
    pXfer->nIGotSent++;
    blob_reset(&uuid);
    return;
  }







  content_get(rid, &content);

  if( blob_size(&content)>100 ){
    size = send_as_delta(pXfer, rid, &content, pUuid, srcId);
  }
  if( size==0 ){
    int size = blob_size(&content);
    blob_appendf(pXfer->pOut, "file %b %d\n", pUuid, size);
    blob_append(pXfer->pOut, blob_buffer(&content), size);
    pXfer->nFileSent++;
  }else{
    pXfer->nDeltaSent++;

  }
  remote_has(rid);
  blob_reset(&uuid);
}

/*
** Send a gimme message for every phantom.
*/
static void request_phantoms(Xfer *pXfer){
  Stmt q;

  db_prepare(&q, "SELECT uuid FROM phantom JOIN blob USING(rid)");


  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    blob_appendf(pXfer->pOut, "gimme %s\n", zUuid);
    pXfer->nGimmeSent++;
  }
  db_finalize(&q);
}







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







<
<
|
|

|




















>
>
>
>
>
>
>
|

|
|
|
|
|
|
|
|
|
|
>










>
|
>
>







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
    }
    blob_reset(&delta);
    free(zUuid);
    blob_reset(&src);
  }
  return size;
}

/*
** Try to send a file as a native delta.  
** If successful, return the number of bytes in the delta.
** If we cannot generate an appropriate delta, then send
** nothing and return zero.
*/
static int send_delta_native(
  Xfer *pXfer,            /* The transfer context */
  int rid,                /* record id of the file to send */
  Blob *pUuid             /* The UUID of the file to send */
){
  Blob src, delta;
  int size = 0;
  int srcId;

  srcId = db_int(0, "SELECT srcid FROM delta WHERE rid=%d", rid);
  if( srcId>0 ){
    blob_zero(&delta);
    db_blob(&delta, "SELECT content FROM blob WHERE rid=%d", rid);
    blob_uncompress(&delta, &delta);
    blob_zero(&src);
    db_blob(&src, "SELECT uuid FROM blob WHERE rid=%d", srcId);
    blob_appendf(pXfer->pOut, "file %b %b %d\n",
                pUuid, &src, blob_size(&delta));
    blob_append(pXfer->pOut, blob_buffer(&delta), blob_size(&delta));
    size = blob_size(&delta);
    blob_reset(&delta);
    blob_reset(&src);
  }else{
    size = 0;
  }
  return size;
}

/*
** Send the file identified by rid.
**
** The pUuid can be NULL in which case the correct UUID is computed
** from the rid.
**


** Try to send the file as a native delta if nativeDelta is true, or
** as a parent delta if nativeDelta is false.
*/
static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int nativeDelta){
  Blob content, uuid;
  int size = 0;

  if( db_exists("SELECT 1 FROM onremote WHERE rid=%d", rid) ){
     return;
  }
  blob_zero(&uuid);
  if( pUuid==0 ){
    db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d AND size>=0", rid);
    if( blob_size(&uuid)==0 ){
      return;
    }
    pUuid = &uuid;
  }
  if( pXfer->mxSend<=blob_size(pXfer->pOut) ){
    blob_appendf(pXfer->pOut, "igot %b\n", pUuid);
    pXfer->nIGotSent++;
    blob_reset(&uuid);
    return;
  }
  if( nativeDelta ){
    size = send_delta_native(pXfer, rid, pUuid);
    if( size ){
      pXfer->nDeltaSent++;
    }
  }
  if( size==0 ){
    content_get(rid, &content);

    if( !nativeDelta && blob_size(&content)>100 ){
      size = send_delta_parent(pXfer, rid, &content, pUuid);
    }
    if( size==0 ){
      int size = blob_size(&content);
      blob_appendf(pXfer->pOut, "file %b %d\n", pUuid, size);
      blob_append(pXfer->pOut, blob_buffer(&content), size);
      pXfer->nFileSent++;
    }else{
      pXfer->nDeltaSent++;
    }
  }
  remote_has(rid);
  blob_reset(&uuid);
}

/*
** Send a gimme message for every phantom.
*/
static void request_phantoms(Xfer *pXfer){
  Stmt q;
  db_prepare(&q, 
    "SELECT uuid FROM phantom JOIN blob USING(rid)"
    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    blob_appendf(pXfer->pOut, "gimme %s\n", zUuid);
    pXfer->nGimmeSent++;
  }
  db_finalize(&q);
}
373
374
375
376
377
378
379

380


381
382
383
384
385
386
387
/*
** Send an igot message for every entry in unclustered table.
** Return the number of messages sent.
*/
static int send_unclustered(Xfer *pXfer){
  Stmt q;
  int cnt = 0;

  db_prepare(&q, "SELECT uuid FROM unclustered JOIN blob USING(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;
}







>
|
>
>







424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
/*
** Send an igot message for every entry in unclustered table.
** Return the number of messages sent.
*/
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)"
  );
  while( db_step(&q)==SQLITE_ROW ){
    blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
    cnt++;
  }
  db_finalize(&q);
  return cnt;
}
400
401
402
403
404
405
406

407
408
409
410
411
412
413
** Process this message and form an appropriate reply.
*/
void page_xfer(void){
  int isPull = 0;
  int isPush = 0;
  int nErr = 0;
  Xfer xfer;


  memset(&xfer, 0, sizeof(xfer));
  blobarray_zero(xfer.aToken, count(xfer.aToken));
  cgi_set_content_type(g.zContentType);
  blob_zero(&xfer.err);
  xfer.pIn = &g.cgiIn;
  xfer.pOut = cgi_output_blob();







>







454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
** Process this message and form an appropriate reply.
*/
void page_xfer(void){
  int isPull = 0;
  int isPush = 0;
  int nErr = 0;
  Xfer xfer;
  int deltaFlag = 0;

  memset(&xfer, 0, sizeof(xfer));
  blobarray_zero(xfer.aToken, count(xfer.aToken));
  cgi_set_content_type(g.zContentType);
  blob_zero(&xfer.err);
  xfer.pIn = &g.cgiIn;
  xfer.pOut = cgi_output_blob();
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
    if( blob_eq(&xfer.aToken[0], "gimme")
     && xfer.nToken==2
     && blob_is_uuid(&xfer.aToken[1])
    ){
      if( isPull ){
        int rid = rid_from_uuid(&xfer.aToken[1], 0);
        if( rid ){
          send_file(&xfer, rid, &xfer.aToken[1], 0);
        }
      }
    }else

    /*   igot UUID
    **
    ** Client announces that it has a particular file.







|







503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
    if( blob_eq(&xfer.aToken[0], "gimme")
     && xfer.nToken==2
     && blob_is_uuid(&xfer.aToken[1])
    ){
      if( isPull ){
        int rid = rid_from_uuid(&xfer.aToken[1], 0);
        if( rid ){
          send_file(&xfer, rid, &xfer.aToken[1], deltaFlag);
        }
      }
    }else

    /*   igot UUID
    **
    ** Client announces that it has a particular file.
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
          @ error not\sauthorized\sto\sread
          nErr++;
          break;
        }
        isPull = 1;
      }else{
        if( !g.okWrite ){

          cgi_reset_content();
          @ error not\sauthorized\sto\swrite
          nErr++;
          break;

        }

        isPush = 1;

      }
    }else

    /*    clone
    **
    ** The client knows nothing.  Tell all.
    */
    if( blob_eq(&xfer.aToken[0], "clone") ){
      login_check_credentials();
      if( !g.okClone ){
        cgi_reset_content();
        @ error not\sauthorized\sto\sclone
        nErr++;
        break;
      }
      isPull = 1;

      @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
    }else

    /*    login  USER  NONCE  SIGNATURE
    **
    ** Check for a valid login.  This has to happen before anything else.
    ** The client can send multiple logins.  Permissions are cumulative.







>
|
|
|
|
>
|
>
|
>
















>







568
569
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
          @ error not\sauthorized\sto\sread
          nErr++;
          break;
        }
        isPull = 1;
      }else{
        if( !g.okWrite ){
          if( !isPull ){
            cgi_reset_content();
            @ error not\sauthorized\sto\swrite
            nErr++;
          }else{
            @ message pull\sonly\s-\snot\sauthorized\sto\spush
          }
        }else{
          isPush = 1;
        }
      }
    }else

    /*    clone
    **
    ** The client knows nothing.  Tell all.
    */
    if( blob_eq(&xfer.aToken[0], "clone") ){
      login_check_credentials();
      if( !g.okClone ){
        cgi_reset_content();
        @ error not\sauthorized\sto\sclone
        nErr++;
        break;
      }
      isPull = 1;
      deltaFlag = 1;
      @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
    }else

    /*    login  USER  NONCE  SIGNATURE
    **
    ** Check for a valid login.  This has to happen before anything else.
    ** The client can send multiple logins.  Permissions are cumulative.
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
    if( zCookie ){
      blob_appendf(&send, "cookie %s\n", zCookie);
    }
    
    /* Generate gimme messages for phantoms and leaf messages
    ** for all leaves.
    */
    if( pullFlag ){
      request_phantoms(&xfer);
    }
    if( pushFlag ){
      send_unsent(&xfer);
      nMsg += send_unclustered(&xfer);
    }








|







756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
    if( zCookie ){
      blob_appendf(&send, "cookie %s\n", zCookie);
    }
    
    /* Generate gimme messages for phantoms and leaf messages
    ** for all leaves.
    */
    if( pullFlag || cloneFlag ){
      request_phantoms(&xfer);
    }
    if( pushFlag ){
      send_unsent(&xfer);
      nMsg += send_unclustered(&xfer);
    }

775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
      */
      if( xfer.nToken==2
       && blob_eq(&xfer.aToken[0], "igot")
       && blob_is_uuid(&xfer.aToken[1])
      ){
        int rid = 0;
        nMsg++;
        if( pullFlag ){
          if( !db_exists("SELECT 1 FROM blob WHERE uuid='%b' AND size>=0",
                &xfer.aToken[1]) ){
            rid = content_put(0, blob_str(&xfer.aToken[1]), 0);
            newPhantom = 1;
          }
        }
        if( rid==0 ){







|







835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
      */
      if( xfer.nToken==2
       && blob_eq(&xfer.aToken[0], "igot")
       && blob_is_uuid(&xfer.aToken[1])
      ){
        int rid = 0;
        nMsg++;
        if( pullFlag || cloneFlag ){
          if( !db_exists("SELECT 1 FROM blob WHERE uuid='%b' AND size>=0",
                &xfer.aToken[1]) ){
            rid = content_put(0, blob_str(&xfer.aToken[1]), 0);
            newPhantom = 1;
          }
        }
        if( rid==0 ){
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831










832
833
834
835
836
837
838
839
840
841
842
843
      ){
        if( blob_eq_str(&xfer.aToken[1], zSCode, -1) ){
          fossil_fatal("server loop");
        }
        nMsg++;
        if( zPCode==0 ){
          zPCode = mprintf("%b", &xfer.aToken[2]);
          db_set("project-code", zPCode);
        }
        cloneFlag = 0;
        pullFlag = 1;
        blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
        nMsg++;
      }else
      
      /*    cookie TEXT
      **
      ** The server might include a cookie in its reply.  The client
      ** should remember this cookie and send it back to the server
      ** in its next query.
      **
      ** Each cookie received overwrites the prior cookie from the
      ** same server.
      */
      if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
        db_set("cookie", blob_str(&xfer.aToken[1]));










      }else

      /*   error MESSAGE
      **
      ** Report an error
      */        
      if( blob_eq(&xfer.aToken[0],"error") && xfer.nToken==2 ){
        char *zMsg = blob_terminate(&xfer.aToken[1]);
        defossilize(zMsg);
        blob_appendf(&xfer.err, "server says: %s", zMsg);
      }else








|

<
<
|













|
>
>
>
>
>
>
>
>
>
>




|







866
867
868
869
870
871
872
873
874


875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
      ){
        if( blob_eq_str(&xfer.aToken[1], zSCode, -1) ){
          fossil_fatal("server loop");
        }
        nMsg++;
        if( zPCode==0 ){
          zPCode = mprintf("%b", &xfer.aToken[2]);
          db_set("project-code", zPCode, 0);
        }


        blob_appendf(&send, "clone\n");
        nMsg++;
      }else
      
      /*    cookie TEXT
      **
      ** The server might include a cookie in its reply.  The client
      ** should remember this cookie and send it back to the server
      ** in its next query.
      **
      ** Each cookie received overwrites the prior cookie from the
      ** same server.
      */
      if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
        db_set("cookie", blob_str(&xfer.aToken[1]), 0);
      }else

      /*   message MESSAGE
      **
      ** Print a message.  Similar to "error" but does not stop processing
      */        
      if( blob_eq(&xfer.aToken[0],"message") && xfer.nToken==2 ){
        char *zMsg = blob_terminate(&xfer.aToken[1]);
        defossilize(zMsg);
        printf("Server says: %s\n", zMsg);
      }else

      /*   error MESSAGE
      **
      ** Report an error and abandon the sync session
      */        
      if( blob_eq(&xfer.aToken[0],"error") && xfer.nToken==2 ){
        char *zMsg = blob_terminate(&xfer.aToken[1]);
        defossilize(zMsg);
        blob_appendf(&xfer.err, "server says: %s", zMsg);
      }else

878
879
880
881
882
883
884

885
886
    ** another round 
    */
    if( xfer.nFileSent+xfer.nDeltaSent>0 ){
      go = 1;
    }
  };
  http_close();

  db_end_transaction(0);
}







>


946
947
948
949
950
951
952
953
954
955
    ** another round 
    */
    if( xfer.nFileSent+xfer.nDeltaSent>0 ){
      go = 1;
    }
  };
  http_close();
  db_multi_exec("DROP TABLE onremote");
  db_end_transaction(0);
}
Changes to src/zip.c.
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
  rid = name_to_rid(g.argv[2]);
  zip_of_baseline(rid, &zip);
  blob_write_to_file(&zip, g.argv[3]);
}

/*
** WEBPAGE: zip

**
** Generate a ZIP archive for the baseline specified by g.zExtra
** and return that ZIP archive as the HTTP reply content.
*/
void baseline_zip_page(void){
  int rid;
  char *zName;
  int i;
  Blob zip;

  login_check_credentials();
  if( !g.okRead || !g.okHistory ){ login_needed(); return; }
  zName = mprintf("%s", g.zExtra);
  i = strlen(zName);
  for(i=strlen(zName)-1; i>5; i--){
    if( zName[i]=='.' ){
      zName[i] = 0;
      break;
    }
  }







>

|
|









|







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
  rid = name_to_rid(g.argv[2]);
  zip_of_baseline(rid, &zip);
  blob_write_to_file(&zip, g.argv[3]);
}

/*
** WEBPAGE: zip
** URL: /zip/RID.zip
**
** Generate a ZIP archive for the baseline.
** Return that ZIP archive as the HTTP reply content.
*/
void baseline_zip_page(void){
  int rid;
  char *zName;
  int i;
  Blob zip;

  login_check_credentials();
  if( !g.okRead || !g.okHistory ){ login_needed(); return; }
  zName = mprintf("%s", PD("name",""));
  i = strlen(zName);
  for(i=strlen(zName)-1; i>5; i--){
    if( zName[i]=='.' ){
      zName[i] = 0;
      break;
    }
  }
Changes to test/merge1.test.
75
76
77
78
79
80
81


82











83
84
85
86
87
88
89
90
91
92
93
94
95
96
  111 - This is line one OF the demo program - 1111
  222 - The second line program line in code - 2222
  333 - This is a test of the merging algohm - 3333
  444 - If all goes well, we will be pleased - 4444
  555 - we think it well and other stuff too - 5555
}
write_file_indented t23 {


  111 - This is line ONE OF the demo program - 1111











  222 - The second line program line in code - 2222
  333 - This is a test of the merging algohm - 3333
  444 - If all goes well, we will be pleased - 4444
  555 - we think it well and other stuff too - 5555
}
fossil test-3 t1 t3 t2 a32
test merge1-2.1 {[same_file t23 a32]}
fossil test-3 t1 t2 t3 a23
test merge1-2.2 {[same_file t23 a23]}

write_file_indented t1 {
  111 - This is line one of the demo program - 1111
  222 - The second line program line in code - 2222
  333 - This is a test of the merging algohm - 3333







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






|







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
  111 - This is line one OF the demo program - 1111
  222 - The second line program line in code - 2222
  333 - This is a test of the merging algohm - 3333
  444 - If all goes well, we will be pleased - 4444
  555 - we think it well and other stuff too - 5555
}
write_file_indented t23 {
  >>>>>>>> BEGIN MERGE CONFLICT <<<<<<<<
  111 - This is line ONE of the demo program - 1111
  111 - This is line one OF the demo program - 1111
  >>>>>>>>> END MERGE CONFLICT <<<<<<<<<
  222 - The second line program line in code - 2222
  333 - This is a test of the merging algohm - 3333
  444 - If all goes well, we will be pleased - 4444
  555 - we think it well and other stuff too - 5555
}
write_file_indented t32 {
  >>>>>>>> BEGIN MERGE CONFLICT <<<<<<<<
  111 - This is line one OF the demo program - 1111
  111 - This is line ONE of the demo program - 1111
  >>>>>>>>> END MERGE CONFLICT <<<<<<<<<
  222 - The second line program line in code - 2222
  333 - This is a test of the merging algohm - 3333
  444 - If all goes well, we will be pleased - 4444
  555 - we think it well and other stuff too - 5555
}
fossil test-3 t1 t3 t2 a32
test merge1-2.1 {[same_file t32 a32]}
fossil test-3 t1 t2 t3 a23
test merge1-2.2 {[same_file t23 a23]}

write_file_indented t1 {
  111 - This is line one of the demo program - 1111
  222 - The second line program line in code - 2222
  333 - This is a test of the merging algohm - 3333
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
  333 - This is a test of the merging algohm - 3333
  555 - we think it well and other stuff too - 5555
}
fossil test-3 t1 t3 t2 a32
test merge1-6.1 {[same_file t32 a32]}
fossil test-3 t1 t2 t3 a23
test merge1-6.2 {[same_file t32 a23]}

# 123456789 123456789 123456789 123456789 123456789 123456789 
write_file_indented t1 {
  111 - This is line one of the demo program - 1111
  222 - The second line program line in code - 2222
  333 - This is a test of the merging algohm - 3333
  444 - If all goes well, we will be pleased - 4444
  555 - we think it well and other stuff too - 5555
}
write_file_indented t2 {
  222 - The second line program line in code - 2222
  333 - This is a test of THREE rging algohm - 3333
  444 - If all goes well, we will be pleased - 4444
  111 - This is line one of the demo program - 1111
  555 - we think it well and other stuff too - 5555
}
write_file_indented t3 {
  111 - This is line ONEONE the demo program - 1111
  222 - The second line program line in code - 2222
  333 - This is a test of the merging algohm - 3333
  444 - If all goes well, we will be pleased - 4444
  555 - we think it FIVEFIVE other stuff too - 5555
}
write_file_indented t32 {
  222 - The second line program line in code - 2222
  333 - This is a test of THREE rging algohm - 3333
  444 - If all goes well, we will be pleased - 4444
  111 - This is line ONEONE the demo program - 1111
  555 - we think it FIVEFIVE other stuff too - 5555
}
fossil test-3 t1 t3 t2 a32
test merge1-6.1 {[same_file t32 a32]}







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
233
234
235
236
237
238
239
































  333 - This is a test of the merging algohm - 3333
  555 - we think it well and other stuff too - 5555
}
fossil test-3 t1 t3 t2 a32
test merge1-6.1 {[same_file t32 a32]}
fossil test-3 t1 t2 t3 a23
test merge1-6.2 {[same_file t32 a23]}
































Changes to test/tester.tcl.
85
86
87
88
89
90
91




92
93
94
95
96
97
98
99
proc write_file_indented {filename txt} {
  write_file $filename [string trim [string map [list "\n  " \n] $txt]]\n
}

# Return true if two files are the same
#
proc same_file {a b} {




  return [expr {[read_file $a]==[read_file $b]}]
}

# Perform a test
#
proc test {name expr} {
  global bad_test
  set r [uplevel 1 [list expr $expr]]







>
>
>
>
|







85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
proc write_file_indented {filename txt} {
  write_file $filename [string trim [string map [list "\n  " \n] $txt]]\n
}

# Return true if two files are the same
#
proc same_file {a b} {
  set x [read_file $a]
  regsub -all { +\n} $x \n x
  set y [read_file $b]
  regsub -all { +\n} $y \n y
  return [expr {$x==$y}]
}

# Perform a test
#
proc test {name expr} {
  global bad_test
  set r [uplevel 1 [list expr $expr]]
Changes to todo.txt.



























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



























Things to work on:

 *  Use the wiki_convert() routine to render comments and other text
    on web pages.

 *  If the server does not have write permission on the database
    file, or on the directory containing the database file (and 
    it is thus unable to update the database because it cannot create
    a rollback journal) then it currently fails silently on a push.
    It needs to return a helpful error.

 *  If the server returns an error (for example if the outbound /xfer
    message exceeds the maximum POST size of the server) the client
    does not report this error back to the user as it should.

 *  Bug: If the server closes the socket unexpectedly, the
    fwrite() in http.c:103 throws a signal and kills the child
    process.  fwrite() is not suppose to do this.  Need to figure
    out what is going wrong.

 *  Bug: pull is ending prematurely.

 *  Bug: Make sure merge and other commands (check-out) do not try
    to use a phantom.
¿
 *  The ipaddr field of the rcvfrom table is not being set.  This
    field should be the IP address from which information is received
    for the local repository.  So when somebody does a push of new
    files we record the ipaddr.  Or when we do a pull, we record
    the ipaddr.

 *  Additional information displayed for the "vinfo" page:

     +  All leaves of this version that are not included in the
        descendant list.  With date, user, comment, and hyperlink.
        Leaves in the descendant table should be marked as such.
        See the compute_leaves() function to see how to find all
        leaves.
     +  Add file diff links to the file change list.

 *  Timeline enhanced so that you can specify a range of dates.

 *  The /xfer handler (for push, pull, and clone) does not do
    delta compression.  This results in excess bandwidth usage.
    There are some pieces in xfer.c that are sketches of ideas on
    how to do delta compression, but nothing has been implemented.

 *  Enhancements to the diff and tkdiff commands in the cli.
    Allow the entire tree or a subtree to be diffed, not just a 
    single file.  Allow diffs against any two arbitrary versions,
    not just diffs against the current check-out.  Allow 
    configuration options to replace tkdiff with some other
    visual differ of the users choice. Example: eskil.

 *  Ticketing interface (expand this bullet)

     +  Create new tickets as files in the file hierarchy
     +  Append remarks to a ticket
     +  Add attachments to a ticket
     +  Change attributes of a ticket
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>















<
<
<
<
<
<
<
<
<
<




|










<
<
<
<
<
<
<



|
<
<







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
Bugs:

 *  When doing an update, if a file is not found that should be there
    then the update aborts, only after it has written several files. It
    does not update your checked out version, thus when doing a fossil
    changes, you see everything that has come over from your update
    command. Steps to reproduce:
    
    $ rm src/main.c
    $ fossil update 9b30
    UPDATE ... etc ...
    fossil: no such file: src/main.c
    $ fossil info
    (shows version you were on prior to update)
    $ fossil changes
    EDITED ... many ...
    
 *  Bug: If the server closes the socket unexpectedly, the
    fwrite() in http.c:103 throws a signal and kills the child
    process.  fwrite() is not suppose to do this.  Need to figure
    out what is going wrong.

 *  Bug: Make sure merge and other commands (check-out) do not try
    to use a phantom.

 *  Bug: When clone use incorrect http URL, local repo file is still created.

Things to work on:

 *  Use the wiki_convert() routine to render comments and other text
    on web pages.

 *  If the server does not have write permission on the database
    file, or on the directory containing the database file (and 
    it is thus unable to update the database because it cannot create
    a rollback journal) then it currently fails silently on a push.
    It needs to return a helpful error.

 *  If the server returns an error (for example if the outbound /xfer
    message exceeds the maximum POST size of the server) the client
    does not report this error back to the user as it should.











 *  The ipaddr field of the rcvfrom table is not being set.  This
    field should be the IP address from which information is received
    for the local repository.  So when somebody does a push of new
    files we record the ipaddr.  Or when we do a pull, we record
    the ipaddr.  (I think this has been fixed.  Need to test.)

 *  Additional information displayed for the "vinfo" page:

     +  All leaves of this version that are not included in the
        descendant list.  With date, user, comment, and hyperlink.
        Leaves in the descendant table should be marked as such.
        See the compute_leaves() function to see how to find all
        leaves.
     +  Add file diff links to the file change list.








 *  Enhancements to the diff and tkdiff commands in the cli.
    Allow the entire tree or a subtree to be diffed, not just a 
    single file.  Allow diffs against any two arbitrary versions,
    not just diffs against the current check-out.



 *  Ticketing interface (expand this bullet)

     +  Create new tickets as files in the file hierarchy
     +  Append remarks to a ticket
     +  Add attachments to a ticket
     +  Change attributes of a ticket
113
114
115
116
117
118
119
120
121
122
123
124
125




 *  Add the ability to override check-in comments.  It is not possible
    to change the comment in the check-in itself due to the cryptographic
    hash.  But you can add a new record to the repository that overloads
    a check-in comment with a new comment.  Comment changes should be
    GPG clearsigned at the very least.  Comment changes only apply if
    the user who made the change has the right permissions.

 *  Get a build working for win32.  Perhaps use cygwin.  Or omit
    the server component from win32 (to eliminate the need for fork()).

 *  Make the interface to fossil look pretty and be customizable so
    that other people will be attracted to it, will take over maintenance
    of it, and we can eventually move on to other things.











<
<
<



>
>
>
>
121
122
123
124
125
126
127



128
129
130
131
132
133
134
 *  Add the ability to override check-in comments.  It is not possible
    to change the comment in the check-in itself due to the cryptographic
    hash.  But you can add a new record to the repository that overloads
    a check-in comment with a new comment.  Comment changes should be
    GPG clearsigned at the very least.  Comment changes only apply if
    the user who made the change has the right permissions.




 *  Make the interface to fossil look pretty and be customizable so
    that other people will be attracted to it, will take over maintenance
    of it, and we can eventually move on to other things.
    
    This has begun, but I wonder if we need to use a templating system for
    full customization. If the CSS is done correctly, you can change 99.9%
    of everything. See: http://www.csszengarden.com/
Added tools/fossil_chat.tcl.


































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
#!/home/drh/bin/tobe
#
# Simple chat client for Tcl/Tk.
#
package require Tk

set SERVERHOST fossil-scm.hwaci.com
# set SERVERHOST 127.0.0.1
#set SERVERHOST 64.5.53.192
set SERVERPORT 8615

# set to correct values if you have to use a proxy
set PROXYHOST {}
set PROXYPORT {}

# Setup the user interface
wm title . Fossil-Chat
wm iconname . [wm title .]

menu .mb -type menubar
if {$tcl_platform(platform)=="unix" && $tcl_platform(os)!="Darwin"} {
  pack .mb -side top -fill x
} else {
  . config -menu .mb
}
.mb add cascade -label File -underline 0 -menu .mb.file
menu .mb.file -tearoff 0
.mb.file add command -label Send -command send_message
.mb.file add command -label {Remove older messages} -command cleanup_record
.mb.file add separator
.mb.file add command -label {Exit} -command exit

frame .who
pack .who -side right -anchor n -fill y
label .who.title -text {Users:      }
pack .who.title -side top -anchor nw
label .who.list -anchor w -justify left -text {}
pack .who.list -side top -anchor nw -expand 1 -padx 5
label .who.time -text {} -justify right
proc update_time {} {
  after 1000 update_time
  set now [clock seconds]
  set time [clock format $now -format %H:%M -gmt 1]
  .who.time config -text "UTC: $time"
}
update_time
pack .who.time -side bottom -anchor sw

frame .input
pack .input -side bottom -fill x
text .input.t -bd 1 -relief sunken -bg white -fg black -width 60 -height 3 \
   -wrap word -yscrollcommand [list .input.sb set] -takefocus 1
bind .input.t <Key-Return> {send_message; break}
pack .input.t -side left -fill both -expand 1
scrollbar .input.sb -orient vertical -command [list .input.t yview]
pack .input.sb -side left -fill y

frame .msg
pack .msg -side top -fill both -expand 1
text .msg.t -bd 1 -relief sunken -bg white -fg black -width 60 -height 20 \
   -wrap word -yscrollcommand [list .msg.sb set] -takefocus 0
bindtags .msg.t [list .msg.t . all]
.msg.t tag config error -foreground red
.msg.t tag config meta -foreground forestgreen
.msg.t tag config norm -foreground black
pack .msg.t -side left -fill both -expand 1
scrollbar .msg.sb -orient vertical -command [list .msg.t yview]
pack .msg.sb -side left -fill y

update

# Send periodic messages to keep the TCP/IP link up
#
proc keep_alive {} {
  global TIMER SOCKET
  catch {after cancel $TIMER}
  set TIMER [after 300000 keep_alive]
  catch {puts $SOCKET noop; flush $SOCKET}
}

# Connect to the server
proc connect {} {
  global SOCKET tcl_platform
  catch {close $SOCKET}
  if {[catch {
      if {$::PROXYHOST ne {}} {
          set SOCKET [socket $::PROXYHOST $::PROXYPORT]
          puts $SOCKET "CONNECT $::SERVERHOST:$::SERVERPORT HTTP/1.1"
          puts $SOCKET "Host:  $::SERVERHOST:$::SERVERPORT" 
          puts $SOCKET ""
      } else {
          set SOCKET [socket $::SERVERHOST $::SERVERPORT]
      }
    fconfigure $SOCKET -translation binary -blocking 0
    puts $SOCKET [list login $tcl_platform(user) fact,fuzz]
    flush $SOCKET
    fileevent $SOCKET readable handle_input
    keep_alive
  } errmsg]} {
    if {[tk_messageBox -icon error -type yesno -parent . -message \
           "Unable to connect to server.  $errmsg.\n\nTry again?"]=="yes"} {
      after 100 connect
    }
  }
}
connect

# Send the message text contained in the .input.t widget to the server.
#
proc send_message {} {
  set txt [.input.t get 1.0 end]
  .input.t delete 1.0 end
  regsub -all "\[ \t\n\f\r\]+" [string trim $txt] { } txt
  if {$txt==""} return
  global SOCKET
  puts $SOCKET [list message $txt]
  flush $SOCKET
}

.mb add cascade -label "Transfer" -underline 0 -menu .mb.files
menu .mb.files -tearoff 0
.mb.files add command -label "Send file..." -command send_file
.mb.files add command -label "Delete files" -command delete_files \
    -state disabled
.mb.files add separator

# Encode a string (possibly containing binary and \000 characters) into
# single line of text.
#
proc encode {txt} {
  return [string map [list % %25 + %2b " " + \n %0a \t %09 \000 %00] $txt]
}

# Undo the work of encode.  Convert an encoded string back into its original
# form.
#
proc decode {txt} {
  return [string map [list %00 \000 %09 \t %0a \n + " " %2b + %25 %] $txt]
}

# Delete all of the downloaded files we are currently holding.
#
proc delete_files {} {
  global FILES
  .mb.files delete 3 end
  array unset FILES
  .mb.files entryconfigure 1 -state disabled 
}

# Prompt the user to select a file from the disk.  Then send that
# file to all chat participants.
#
proc send_file {} {
  global SOCKET
  set openfile [tk_getOpenFile]
  if {$openfile==""} return
  set f [open $openfile]
  fconfigure $f -translation binary
  set data [read $f]
  close $f
  puts $SOCKET [list file [file tail $openfile] [encode $data]]
  flush $SOCKET
  set time [clock format [clock seconds] -format {%H:%M} -gmt 1]
  .msg.t insert end "\[$time\] sent file [file tail $openfile]\
        - [string length $data] bytes\n" meta
  .msg.t see end
}

# Save the named file to the disk.
#
 proc save_file {filename} {
  global FILES
  set savefile [tk_getSaveFile -initialfile $filename]
  if {$savefile==""} return
  set f [open $savefile w]
  fconfigure $f -translation binary
  puts -nonewline $f [decode $FILES($filename)]
  close $f
}

# Handle a "file" message from the chat server.
#
proc handle_file {from filename data} {
  global FILES
  foreach prior [array names FILES] {
    if {$filename==$prior} break
  }
  if {![info exists prior] || $filename!=$prior} {
    .mb.files add command -label "Save \"$filename\"" \
        -command [list save_file $filename]
  }
  set FILES($filename) $data
  .mb.files entryconfigure 1 -state active 
  set time [clock format [clock seconds] -format {%H:%M} -gmt 1]
  .msg.t insert end "\[$time $from\] " meta "File: \"$filename\"\n" norm
  .msg.t see end
}

# Handle input from the server
#
proc handle_input {} {
  global SOCKET
  if {[eof $SOCKET]} {
    disconnect
    return
  }
  set line [gets $SOCKET]
  if {$line==""} return
  set cmd [lindex $line 0]
  if {$cmd=="userlist"} {
    set ulist {}
    foreach u [lrange $line 1 end] {
      append ulist $u\n
    }
    .who.list config -text [string trim $ulist]
  } elseif {$cmd=="message"} {
    set time [clock format [clock seconds] -format {%H:%M} -gmt 1]
    set from [lindex $line 1]
    .msg.t insert end "\[$time $from\] " meta [lindex $line 2]\n norm
    .msg.t see end
    bell
    wm deiconify .
    update
    raise .
  } elseif {$cmd=="noop"} {
    # do nothing
  } elseif {$cmd=="meta"} {
    set now [clock seconds]
    set time [clock format $now -format {%H:%M} -gmt 1]
    .msg.t insert end "\[$time\] [lindex $line 1]\n" meta
    .msg.t see end
  } elseif {$cmd=="file"} {
    if {[info commands handle_file]=="handle_file"} {
      handle_file [lindex $line 1] [lindex $line 2] [lindex $line 3]
    }
  }
}    

# Handle a broken socket connection
#
proc disconnect {} {
  global SOCKET
  close $SOCKET
  set q [tk_messageBox -icon error -type yesno -parent . -message \
           "TCP/IP link lost.  Try to reconnet?"]
  if {$q=="yes"} {
    connect
  } else {
    exit
  }
}

# Remove all but the most recent 100 message from the message log
#
proc cleanup_record {} {
  .msg.t delete 1.0 {end -100 lines}
}
Added win32.txt.


















































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
Fossil on Win32
======================================================================

Setting up the build environment:
----------------------------------------------------------------------

  Install:
    MinGW 5.1.3
    MSYS  1.0.10
  
  Download/compile/install zlib (configure --prefix=/mingw)
  Download/compile/install tclsh (configure --prefix=/) (for tests)
  
  Installing zlib and tclsh was done in the MSYS shell so I could
  easily use the configure scripts. Tcl is only required to run the
  tests. You could install a binary distribution of Tcl/Tk and use
  that to run the tests, or you could opt to simply trust that 
  everything worked and not run the tests.
  
  There is a contributed zlib package on the MinGW site. 
  
  Downloading and installing a binary Tcl/Tk package and the 
  contributed zlib package would remove the requirement of MSYS.
  

Building on Windows:
----------------------------------------------------------------------

  Ensure you have read "Setting up the build environment" first.
  
  Building is as simple as one command:
  
      C:\fossil-src> make -f Makefile.w32


Outstanding Issues:
----------------------------------------------------------------------

* server is totally non-functional - #if/#end'd out of the code


Commands status:
----------------------------------------------------------------------

add               OK
cgi               Not tested
changes           OK
checkout          BAD #1
clean             OK
clone             OK
close             OK
commit            OK
config            OK
deconstruct       OK
del               OK
descendents       OK
diff              OK
extra             OK
help              OK
http              Not Tested
info              OK
leaves            OK
ls                OK
merge             OK
new               OK
open              OK
pull              OK
push              OK
rebuild           OK
redo              BAD #3
rm                OK
server            BAD #4
status            OK
sync              OK
timeline          OK
tkdiff            OK
undo              OK
update            OK
user capabilities OK
user default      OK
user list         OK
user new          OK
user password     OK

#1 Have a repo where I removed a file. I did a fossil checkout 123abc,
   which is the last version that had the file. The file does not
   appear. fossil checkout --force 123abc does things, but still the
   file does not appear.
   
   Make a new dir, fossil open ../repo.fsl && fossil checkout 123abc and
   the file appears.
   
   Is that normal operation?

#3 In test1/ I edited a file, test2/ I updated, type file.txt changes
   were there. I then did fossil undo file.txt. The changes were gone
   and fossil status said I had edited file.txt. A fossil redo did not
   print anything to the screen and the changes for file.txt are not
   in the file. fossil status still reports that the file was edited.
   There was no commit/update or any other command inbetween these
   actions.

#4 There were various difficulties in this function beyond simple socket
   problems. The major one being fork. This will probably be the last
   command to be functional in fossil on windows.
Changes to www/concepts.html.
28
29
30
31
32
33
34
35

36
37
38
39
40
41
42
<img src="concept1.gif" align="right" hspace="10">

<p>A software project normally consists of a "source tree".
A source tree is a hierarchy of files that are used to generate
the end product.  The source tree changes over time as the
software grows and expands and as features are added and bugs
are fixed.  A snapshot of the source tree at any point in time
is called a "version" or a "baseline" of the product.</p>


<p>A "repository" is a database that contains copies of all historical
versions or baselines for a project.  Baselines are normally stored in the
repository in a highly space-efficient compressed format (delta encoding).
But that is an implementation detail that you the user need not worry over.
Think of the repository as a safe place where all your old baselines are
securely stored away and available for retrieval whenever you need







|
>







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<img src="concept1.gif" align="right" hspace="10">

<p>A software project normally consists of a "source tree".
A source tree is a hierarchy of files that are used to generate
the end product.  The source tree changes over time as the
software grows and expands and as features are added and bugs
are fixed.  A snapshot of the source tree at any point in time
is called a "version" or "revision" or a "baseline" of the product.
In fossil, we use the name "baseline".</p>

<p>A "repository" is a database that contains copies of all historical
versions or baselines for a project.  Baselines are normally stored in the
repository in a highly space-efficient compressed format (delta encoding).
But that is an implementation detail that you the user need not worry over.
Think of the repository as a safe place where all your old baselines are
securely stored away and available for retrieval whenever you need
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<p>Each source tree that is controlled by fossil is associated with
a single repository on the local disk drive.  You can tie two or more
source trees to a single repository if you want (though one
tree per repository is the most common configuration.)  So a
single repository can be associated with many source trees, but
each source tree is associated with only one repository.</p>

<p>Fossil source tree may not overlap.  A fossil source tree is identified
by a file named "_FOSSIL_" in the root directory of the source tree.  Every
file that is a sibling of _FOSSIL_ and every file in every subfolder is
considered potentially a part of the source tree.  The _FOSSIL_ file
contains (among other things) the pathname of the repository with which
the source tree is associated.  On the other hand, the repository has
no record of its source trees.  So you are free to delete a source tree
or move it around without consequence.  But if you move or rename or







|







52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<p>Each source tree that is controlled by fossil is associated with
a single repository on the local disk drive.  You can tie two or more
source trees to a single repository if you want (though one
tree per repository is the most common configuration.)  So a
single repository can be associated with many source trees, but
each source tree is associated with only one repository.</p>

<p>Fossil source trees may not overlap.  A fossil source tree is identified
by a file named "_FOSSIL_" in the root directory of the source tree.  Every
file that is a sibling of _FOSSIL_ and every file in every subfolder is
considered potentially a part of the source tree.  The _FOSSIL_ file
contains (among other things) the pathname of the repository with which
the source tree is associated.  On the other hand, the repository has
no record of its source trees.  So you are free to delete a source tree
or move it around without consequence.  But if you move or rename or
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
or through a central server.  Changes can "push" from the local
repository into a remote repository.  Or changes can "pull" from a
remote repository into a local repository.  Or one can do a "sync"
which is a shortcut for doing both a push and a pull at the same time.
Fossil also has the concept of "cloning".  A "clone" is like a "pull",
except that instead of beginning with an existing local repository,
a clone begins with nothing and creates a new local repository that
is a replicate of a remote repository.</p>

<p>Communication between repositories is via HTTP.  Remote
repositories are identified by URL.  You can also point a webbrowser
at a repository and get human-readable status, history, and tracking
information about the project.</p>

<h3>2.1 Identification Of Artifacts</h3>

<p>A particular version of a particular file is called an "artifact".
Each artifact has a universally unique name which is the
<a href="http://en.wikipedia.org/wiki/SHA">SHA1</a> hash of the content
of that file expressed as 40 characters of lower-case hexadecimal.  Such
a hash is referred to as the Universally Unique Identifier or UUID
for the artifact.  The SHA1 algorithm is created with the purpose of
providing a highly forgery-resistent identifier for a file.  Given any
file it is simple to find the UUID for that file.  But given a
UUID it is computationally intractable to generate a file that will
generate that UUID.</p>


<p>UUIDs look something like this:</p>

<blockquote><b>
6089f0b563a9db0a6d90682fe47fd7161ff867c8<br>
59712614a1b3ccfd84078a37fa5b606e28434326<br>







|

















|







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
or through a central server.  Changes can "push" from the local
repository into a remote repository.  Or changes can "pull" from a
remote repository into a local repository.  Or one can do a "sync"
which is a shortcut for doing both a push and a pull at the same time.
Fossil also has the concept of "cloning".  A "clone" is like a "pull",
except that instead of beginning with an existing local repository,
a clone begins with nothing and creates a new local repository that
is a duplicate of a remote repository.</p>

<p>Communication between repositories is via HTTP.  Remote
repositories are identified by URL.  You can also point a webbrowser
at a repository and get human-readable status, history, and tracking
information about the project.</p>

<h3>2.1 Identification Of Artifacts</h3>

<p>A particular version of a particular file is called an "artifact".
Each artifact has a universally unique name which is the
<a href="http://en.wikipedia.org/wiki/SHA">SHA1</a> hash of the content
of that file expressed as 40 characters of lower-case hexadecimal.  Such
a hash is referred to as the Universally Unique Identifier or UUID
for the artifact.  The SHA1 algorithm is created with the purpose of
providing a highly forgery-resistent identifier for a file.  Given any
file it is simple to find the UUID for that file.  But given a
UUID it is computationally intractable to generate a file that will
have that UUID.</p>


<p>UUIDs look something like this:</p>

<blockquote><b>
6089f0b563a9db0a6d90682fe47fd7161ff867c8<br>
59712614a1b3ccfd84078a37fa5b606e28434326<br>
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
you look at a "timeline" of changes in fossil, the UUID associated
with each check-in or commit is really just the UUID of the
manifest for that baseline.</p>

<p>Fossil automatically generates a manifest whenever you "commit" 
a new baseline.  So this is not something that you, the developer,
need to worry with.  The format of a manifest is intentionally
designed to be simple to parse, however, so that if
you want to read and interpret a manifest, either by hand or
with a script, that is easy to do.</p>


<p>In addition to identifying all files in the baseline, a
manifest also contains a check-in comment, the date and time
when the baseline was established, who created the baseline,
and links to other baselines from which the current baseline
is derived.  There is also a couple of checksums used to verify
the integrity of the baseline.  And the whole manifest might
be PGP clearsigned.</p>

<h3>2.3 Key concepts</h3>

<ul>
<li>A <b>baseline</b> or <b>version</b> is a set of files arranged
    in a hierarchy.</li>
<li>A <b>repository</b> keeps a record of historical baselines.</li>
<li>Repositories share their changes using <b>push</b>, <b>pull</b>,
    <b>sync</b>, and <b>clone</b>.</li>
<li>A particular version of a particular file is an <b>artifact</b>
    that is identified by a <b>UUID</b>.</li>
<li>Artifacts tracked by fossil are inherently immutable.</li>







|

|
>












|







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
you look at a "timeline" of changes in fossil, the UUID associated
with each check-in or commit is really just the UUID of the
manifest for that baseline.</p>

<p>Fossil automatically generates a manifest whenever you "commit" 
a new baseline.  So this is not something that you, the developer,
need to worry with.  The format of a manifest is intentionally
designed to be simple to parse, so that if
you want to read and interpret a manifest, either by hand or
with a script, that is easy to do.  But you will probably never
need to do so.</p>

<p>In addition to identifying all files in the baseline, a
manifest also contains a check-in comment, the date and time
when the baseline was established, who created the baseline,
and links to other baselines from which the current baseline
is derived.  There is also a couple of checksums used to verify
the integrity of the baseline.  And the whole manifest might
be PGP clearsigned.</p>

<h3>2.3 Key concepts</h3>

<ul>
<li>A <b>baseline</b> is a set of files arranged
    in a hierarchy.</li>
<li>A <b>repository</b> keeps a record of historical baselines.</li>
<li>Repositories share their changes using <b>push</b>, <b>pull</b>,
    <b>sync</b>, and <b>clone</b>.</li>
<li>A particular version of a particular file is an <b>artifact</b>
    that is identified by a <b>UUID</b>.</li>
<li>Artifacts tracked by fossil are inherently immutable.</li>
182
183
184
185
186
187
188
189


190
191
192
193
194
195
196
197
install any other software in order to use fossil.  You do <u>not</u> need
CVS, gzip, diff, rsync, Python, Perl, Tcl, Java, apache, PostgreSQL, MySQL,
SQLite, patch, or any similar software on your system in order to use
fossil effectively.  You will want to have some kind of text editor
for entering check-in comments.  Fossil will use whatever text editor
is identified by your VISUAL environment variable.  Fossil will also
use GPG to clearsign your manifests if you happen to have it installed,
but fossil will skip that step if you do not have GPG so it is not


essential.</p>

<p>To uninstall fossil, simply delete the executable.</p>

<p>To upgrade an older version of fossil to a newer version, just
replace the old executable with the new one.  You might need to 
run a one-time command to restructure your repositories after
an upgrade.  Check the instructions that come with the upgrade







|
>
>
|







184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
install any other software in order to use fossil.  You do <u>not</u> need
CVS, gzip, diff, rsync, Python, Perl, Tcl, Java, apache, PostgreSQL, MySQL,
SQLite, patch, or any similar software on your system in order to use
fossil effectively.  You will want to have some kind of text editor
for entering check-in comments.  Fossil will use whatever text editor
is identified by your VISUAL environment variable.  Fossil will also
use GPG to clearsign your manifests if you happen to have it installed,
but fossil will skip that step if GPG missing from your system.
You can optionally set up fossil to use external "diff" programs, 
though a perfectly functional "diff" algorithm is built it and works
find for most people.</p>

<p>To uninstall fossil, simply delete the executable.</p>

<p>To upgrade an older version of fossil to a newer version, just
replace the old executable with the new one.  You might need to 
run a one-time command to restructure your repositories after
an upgrade.  Check the instructions that come with the upgrade
273
274
275
276
277
278
279
































280
281
282
283
284
285
286
</p></li>

<li><p>
Repeat all of the above until you have generated great software.
</p></li>
</ol>

































<h2>5.0 Setting Up A Fossil Server</h2>

<p>With other configuration management software, setting up a server is
a lot of work and normally takes time, patience, and a lot of system
knowledge.  Fossil is designed to avoid this frustration.  Setting up
a server with fossil is ridiculously easy.  You have three options:</p>








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







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
</p></li>

<li><p>
Repeat all of the above until you have generated great software.
</p></li>
</ol>

<h3>4.1 Variations</h3>

<p>The <b>settings</b> lets you view and modify various operating
properties of fossil.  Among the available settings is "autosync"
mode.  When autosync is enabled, the push and pull of content from
your local server is largely automated.  Whenever you use the <b>update</b>
command, fossil first does a <b>pull</b> to see if other users have
perhaps added new baselines to the central repository.  When you
<b>commit</b>, fossil also does a <b>pull</b> and issues a warning
if your check-in would cause a fork.  After a <b>commit</b>, fossil
automatically does a <b>push</b> to send your changes up to the
central server.</p>

<p>With autosync enabled, fossil works like
<a href="http://www.nongnu.org/cvs/">CVS</a> or 
<a href="http://subversion.tigris.org/">Subversion</a>.
When autosync disabled, fossil works more like
<a href="http://monotone.ca/">Monotone</a>, 
<a href="http://git.or.cz">GIT</a>, or
<a href="http://www.selenic.com/mercurial/wiki/">Mercurial</a>.
The fun thing about fossil is that it will work either
way, depending on your needs of the moment.  You can freely switch
between these operating modes using commands like:</p>

<blockquote>
<b>fossil setting autosync off<br />
fossil setting autosync on</b>
</blockquote>

<p>For additional information about autosync and other settings
using the <b>help</b> command.</p>

<h2>5.0 Setting Up A Fossil Server</h2>

<p>With other configuration management software, setting up a server is
a lot of work and normally takes time, patience, and a lot of system
knowledge.  Fossil is designed to avoid this frustration.  Setting up
a server with fossil is ridiculously easy.  You have three options:</p>

Changes to www/fileformat.html.
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
<html>
<head>
<title>Fossil File Format</title>
</head>
<body bgcolor="white">
<p>[ <a href="index.html">Index</a> ]</p>
<hr>
<h1 align="center">
Fossil File Formats
</h1>

<p>
The global state of a fossil repository is determined by an unordered
set of files.  Some files are used to represent wiki pages, trouble tickets,
and the special "manifest" file has a specific and well-defined format.


Other files are just data.  Files can be text or binary.
</p>

<p>
Each file in the repository is named by its SHA1 hash.
No prefixes or meta information is added to a file before
its hash is computed.  The name of a file 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 files have a particular format which qualifies them
as "manifests".  A manifest assigns filenames to a subset
of the files in the repository, in order to provide a
snapshot of the state of the project at a point in time.

Each manifest file corresponds to a version or baseline

of the project.


</p>



<h2>1.0 The Manifest File</h2>









<p>
Any file 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 line-oriented text file.  Newline characters

(ASCII 0x0a) separate lines.  Each line begins with a single
character "line type".  Zero or more arguments may follow
the line type.  All arguments are separated from each other
and from the line-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 line separator.
</p>

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

<p>
Allowed lines 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><br>
<b>P</b> <i>SHA1-hash</i>+<br>
<b>R</b> <i>repository-checksum</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-line.  The sole argument to
the C-line is a check-in comment that describes the baseline 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-line.  The sole argument to
the D-line 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-lines.  Each F-line defines a file
(other than the manifest itself) which is part of the baseline that
the manifest defines.  There are two arguments.  The first argment
is the pathname of the file in the baseline relative to the root
of the project file hierarchy.  No ".." or "." directories are allowed
within the filename.  Space characters are escaped as in C-line
comment text.  Backslash characters and newlines are not allowed
within filenames.  The directory separator character is a forward
slash (ASCII 0x2F).  The second argument to the F-line is the
full 40-character hexadecimal SHA1 hash of the file content.  
Upper-case letters ABCDEF are used for the higher digits of the
hexadecimal.
</p>

<p>
A manifest has zero or one P-lines.  Most manifests have one P-line.
The P-line 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-line must be unique to that line.
The first predecessor is the manifests direct ancestor.
Other arguments define manifests with which the first was
merged to yield the current manifest.  Most manifests have
a P-line with a single argument.  The first manifest in the
project has no ancestors and thus has no P-line.
</p>

<p>
A manifest may optionally have a single R-line.  The R-line has
a single argument which is the MD5 checksum of all files in 
the baseline 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 baseline (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>
Each manifest has a single U-line.  The argument to the U-line 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-line.
</p>

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

<h2>2.0 Trouble Tickets</h2>

<p>




Each trouble ticket is a file in the repository and appears in
















a manifest for every baseline in which the ticket exists.



Trouble tickets occur in a specific subdirectory of the file




heirarchy.  The name of the subdirectory that contains tickets









is part of the local state of each repository.  The filename
of each trouble ticket has a ".tkt" suffix.  The trouble ticket

has a particular file format defined below.








</p>




<i>To be continued...</i>





































<h2>3.0 Wiki Pages</h2>









































<p>




Each wiki is a file in the repository and appears in

a manifest for every baseline in which that wiki page exists.
Wiki pages occur in a specific subdirectory of the file
heirarchy.  The name of the subdirectory that contains wiki pages

is part of the local state of each repository.  The filename


of each wiki page has a ".wiki" suffix.  The base name of
the file is the name of the wiki page.  The wiki pages

have a particular file format defined below.
</p>








<i>To be continued...</i>













|
|
>
>
|



|
|
|




|
|
|
<
>
|
>
|
>
>
|

>
>
|

>
>
>
>
>
>
>
>

|






|
>
|
|
|
|


|



|
|
|




|













|
|











|
|









|




|


|
|
<
|



|
|



|
|


|
|



|













|


|



|
|

|




|


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


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

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

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

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

>
>
>
>
>
>
>
|
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
<html>
<head>
<title>Fossil File Format</title>
</head>
<body bgcolor="white">
<p>[ <a href="index.html">Index</a> ]</p>
<hr>
<h1 align="center">
Fossil File Formats
</h1>

<p>
The global state of a fossil repository is determined by an unordered
set of files.  A file in fossil is called an "artifact".
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.  Artifacts can be text or binary.
</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>

<h2>1.0 The Manifest</h2>

<p>A manifest defines a baseline 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 baselines, the name of the
programmer who created the baseline, the date and time when
the baseline was created, and any check-in comments associated
with the baseline.</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><br>
<b>P</b> <i>SHA1-hash</i>+<br>
<b>R</b> <i>repository-checksum</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 baseline that
the manifest defines.  There are two arguments.  The first argment
is the pathname of the file in the baseline 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
comment text.  Backslash characters and newlines are not allowed
within filenames.  The directory separator character is a forward
slash (ASCII 0x2F).  The second argument to the F-card is the
full 40-character lower-case hexadecimal SHA1 hash of the content

artifact.
</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 baseline 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 baseline (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>
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 preceeds the "Z".  The Z-card is just
a sanity check to prove that the manifest is well-formed and
consistent.
</p>

<h2>2.0 Clusters</h2>

<p>
A cluster is a artifact that declares the existance of other artifacts.
Clusters are used during repository synchronization to help 
reduce network traffic.
</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 cluter 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>uuid</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 UUID 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>


<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  uuid  ?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 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 UUID 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 decendents (but not branches) of the artifact down
to but not including the first decendent 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 baseline will override the check-in comment of that baseline
for display purposes.</p>

<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-uuid</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>

<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 value</i><br />
<b>K</b> <i>ticket-uuid</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 UUID.  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 existance.</p>

<p>
J cards specify changes to "fields" of the ticket.  Each fossil
server has a ticket configuration which specifies the fields its
understands.  This is not a limit on the fields that can appear
on the J cards, however.  If a J card specifies a field that a
particular fossil server does not recognize, 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>
Changes to www/quickstart.html.
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232

</blockquote><h2>More Hints</h2><blockquote>

    <p>Try these commands:</p>

    <blockquote><b>
    fossil help<br>
    fossil commands<br>
    fossil test-commands
    </b></blockquote>

    <p>Explore and have fun!</p>


</blockquote></body></html>







<







218
219
220
221
222
223
224

225
226
227
228
229
230
231

</blockquote><h2>More Hints</h2><blockquote>

    <p>Try these commands:</p>

    <blockquote><b>
    fossil help<br>

    fossil test-commands
    </b></blockquote>

    <p>Explore and have fun!</p>


</blockquote></body></html>
Changes to www/selfcheck.html.
13
14
15
16
17
18
19






20
21
22
23
24
25
26
Even though fossil is a relatively new project and still contains
many bugs, it is designed with features to give it a high level
of integrity so that you can have confidence that you will not
lose your files.  This note describes the defensive measures that
fossil uses to help prevent file loss due to bugs.
</p>







<h2>Atomic Check-ins With Rollback</h2>

<p>
The fossil repository is an
<a href="http://www.sqlite.org/">SQLite version 3</a> database file.  
SQLite is very mature and stable and has been in wide-spread use for many
years, so we have little worries that it might cause repository







>
>
>
>
>
>







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Even though fossil is a relatively new project and still contains
many bugs, it is designed with features to give it a high level
of integrity so that you can have confidence that you will not
lose your files.  This note describes the defensive measures that
fossil uses to help prevent file loss due to bugs.
</p>

<p><i>Follow-up as of 2007-11-24:</i>
Fossil has been hosting itself and several other projects for
months now.  Many bugs have been encountered.  But, thanks in large
part to the defensive measures described here, no data has been
lost.  The integrity checks are doing their job well.</p>

<h2>Atomic Check-ins With Rollback</h2>

<p>
The fossil repository is an
<a href="http://www.sqlite.org/">SQLite version 3</a> database file.  
SQLite is very mature and stable and has been in wide-spread use for many
years, so we have little worries that it might cause repository
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

<h2>Verification Of Delta Encodings Prior To Transaction Commit</h2>

<p>
The content files that comprise the global state of a fossil respository
are stored in the repository as a tree.  The leaves of the tree are
stored as zlib-compressed BLOBs.  Interior nodes are deltas from their
decendents.  There is a lot of encoding going on here.  There is
zlib-compression which is relatively well-tested but still might
cause corruption if used improperly.  And there is the relatively
new delta-encoding mechanism designed expressly for fossil.  We want
to make sure that bugs in these encoding mechanisms do not lead to
loss of data.
</p>

<p>
To increase our confidence that everything in the repository is
recoverable, fossil makes sure it can extract an exact replicate
of every content file that it changes just prior to transaction
commit.  So during the course of check-in, many different files

in the repository might be modified.  Some files are simply
compressed.  Other files are delta encoded and then compressed.
While all this is going on, fossil makes a record of every file
that is encoded and the SHA1 hash of the original content of that
file.  Then just before transaction commit, fossil re-extracts
the original content of all files that were written, computes
the SHA1 checksum again, and verifies that the checksums match.
If anything does not match up, an error
message is printed and the transaction rolls back.
</p>

<p>
So, in other words, fossil always checks to make sure it can
re-extract a file before it commits a check-in of that file.
Hence bugs in fossil are unlikely to corrupt the repository in
a way that prevents us from extracting historical versions of 
files.
</p>

<h2>Checksum Over All Files In A Baseline</h2>

<p>
Manifest files that define a baseline have two fields (the
R-line and Z-line) that record MD5 hashs of the manifest itself
and of all other files in the manifest.  Prior to any check-in
commit, these checksums are verified to ensure that the baseline
checked in agrees exactly with what is on disk.  Similarly,
the repository checksum is verified after a checkout to make
sure that the entire repository was checked out correctly.
Note that these added checks use a different hash (MD5 instead
of SHA1) in order to avoid common-mode failures in the hash
algorithm implementation.
</p>







|











|
>













|








|
|









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

<h2>Verification Of Delta Encodings Prior To Transaction Commit</h2>

<p>
The content files that comprise the global state of a fossil respository
are stored in the repository as a tree.  The leaves of the tree are
stored as zlib-compressed BLOBs.  Interior nodes are deltas from their
decendents.  A lot of encoding is going on.  There is
zlib-compression which is relatively well-tested but still might
cause corruption if used improperly.  And there is the relatively
new delta-encoding mechanism designed expressly for fossil.  We want
to make sure that bugs in these encoding mechanisms do not lead to
loss of data.
</p>

<p>
To increase our confidence that everything in the repository is
recoverable, fossil makes sure it can extract an exact replicate
of every content file that it changes just prior to transaction
commit.  So during the course of check-in (or other repository
operation) many different files
in the repository might be modified.  Some files are simply
compressed.  Other files are delta encoded and then compressed.
While all this is going on, fossil makes a record of every file
that is encoded and the SHA1 hash of the original content of that
file.  Then just before transaction commit, fossil re-extracts
the original content of all files that were written, computes
the SHA1 checksum again, and verifies that the checksums match.
If anything does not match up, an error
message is printed and the transaction rolls back.
</p>

<p>
So, in other words, fossil always checks to make sure it can
re-extract a file before it commits a change to that file.
Hence bugs in fossil are unlikely to corrupt the repository in
a way that prevents us from extracting historical versions of 
files.
</p>

<h2>Checksum Over All Files In A Baseline</h2>

<p>
Manifest artifacts that define a baseline have two fields (the
R-card and Z-card) that record MD5 hashs of the manifest itself
and of all other files in the manifest.  Prior to any check-in
commit, these checksums are verified to ensure that the baseline
checked in agrees exactly with what is on disk.  Similarly,
the repository checksum is verified after a checkout to make
sure that the entire repository was checked out correctly.
Note that these added checks use a different hash (MD5 instead
of SHA1) in order to avoid common-mode failures in the hash
algorithm implementation.
</p>