Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
| Comment: | Merge trunk |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | cleanX |
| Files: | files | file ages | folders |
| SHA3-256: |
69b450c1e028c077db3030487fb6ab4e |
| User & Date: | jan.nijtmans 2020-06-25 08:16:00.000 |
|
2020-06-25
| ||
| 08:16 | Merge trunk ... (Closed-Leaf check-in: 69b450c1e0 user: jan.nijtmans tags: cleanX) | |
| 00:17 | On webpages, render help text as HTML. ... (check-in: e58c76a1d0 user: drh tags: trunk) | |
|
2019-08-04
| ||
| 22:56 | Merge trunk ... (check-in: fc5f88d6d9 user: jan.nijtmans tags: cleanX) | |
> > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# EditorConfig (https://editorconfig.com) Configuration for Fossil
#
# Following https://fossil-scm.org/fossil/doc/trunk/www/style.wiki
#
# Defaults for all files
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
[{Makefile,Makefile.*,*.mk}]
indent_style = tab
|
| ︙ | ︙ | |||
97 98 99 100 101 102 103 104 105 | LIB += $(LIB.$(HOST_OS)) TCC.DragonFly += -DUSE_PREAD TCC.FreeBSD += -DUSE_PREAD TCC.NetBSD += -DUSE_PREAD TCC.OpenBSD += -DUSE_PREAD TCC += $(TCC.$(HOST_OS)) include $(SRCDIR)/main.mk | > > | 97 98 99 100 101 102 103 104 105 106 107 | LIB += $(LIB.$(HOST_OS)) TCC.DragonFly += -DUSE_PREAD TCC.FreeBSD += -DUSE_PREAD TCC.NetBSD += -DUSE_PREAD TCC.OpenBSD += -DUSE_PREAD TCC += $(TCC.$(HOST_OS)) APPNAME = fossil$(E) include $(SRCDIR)/main.mk |
| ︙ | ︙ | |||
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | TCCFLAGS = @EXTRA_CFLAGS@ @CPPFLAGS@ $(CFLAGS) -DHAVE_AUTOCONFIG_H -D_HAVE_SQLITE_CONFIG_H INSTALLDIR = $(DESTDIR)@prefix@/bin USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@ USE_LINENOISE = @USE_LINENOISE@ USE_MMAN_H = @USE_MMAN_H@ USE_SEE = @USE_SEE@ FOSSIL_ENABLE_MINIZ = @FOSSIL_ENABLE_MINIZ@ include $(SRCDIR)/main.mk distclean: clean -rm -f autoconfig.h config.log Makefile reconfig: @AUTOREMAKE@ # Automatically reconfigure whenever an autosetup file or one of the # make source files change. # # The "touch" is necessary to avoid a make loop due to a new upstream # feature in autosetup (GH 0a71e3c3b7) which rewrites *.in outputs only # if doing so will write different contents; otherwise, it leaves them # alone so the mtime doesn't change. This means that if you change one | > > > > > > > > | 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 | TCCFLAGS = @EXTRA_CFLAGS@ @CPPFLAGS@ $(CFLAGS) -DHAVE_AUTOCONFIG_H -D_HAVE_SQLITE_CONFIG_H INSTALLDIR = $(DESTDIR)@prefix@/bin USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@ USE_LINENOISE = @USE_LINENOISE@ USE_MMAN_H = @USE_MMAN_H@ USE_SEE = @USE_SEE@ FOSSIL_ENABLE_MINIZ = @FOSSIL_ENABLE_MINIZ@ APPNAME = fossil .PHONY: all tags include $(SRCDIR)/main.mk distclean: clean -rm -f autoconfig.h config.log Makefile -rm -f cscope.out tags reconfig: @AUTOREMAKE@ tags: ctags -R @srcdir@/src @COLLECT_CSCOPE_DATA@ # Automatically reconfigure whenever an autosetup file or one of the # make source files change. # # The "touch" is necessary to avoid a make loop due to a new upstream # feature in autosetup (GH 0a71e3c3b7) which rewrites *.in outputs only # if doing so will write different contents; otherwise, it leaves them # alone so the mtime doesn't change. This means that if you change one |
| ︙ | ︙ |
|
| | | 1 | 2.12 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# System autoconfiguration. Try: ./configure --help
use cc cc-lib
options {
with-openssl:path|auto|tree|none
=> {Look for OpenSSL in the given path, automatically, in the source tree, or none}
with-miniz=0 => {Use miniz from the source tree}
with-zlib:path|auto|tree
=> {Look for zlib in the given path, automatically, or in the source tree}
with-exec-rel-paths=0
=> {Enable relative paths for external diff/gdiff}
with-legacy-mv-rm=1 => {Enable legacy behavior for mv/rm (skip checkout files)}
with-th1-docs=0 => {Enable TH1 for embedded documentation pages}
with-th1-hooks=0 => {Enable TH1 hooks for commands and web pages}
with-tcl:path => {Enable Tcl integration, with Tcl in the specified path}
with-tcl-stubs=0 => {Enable Tcl integration via stubs library mechanism}
with-tcl-private-stubs=0
=> {Enable Tcl integration via private stubs mechanism}
with-mman=0 => {Enable use of POSIX memory APIs from "sys/mman.h"}
| > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# System autoconfiguration. Try: ./configure --help
use cc cc-lib
options {
with-openssl:path|auto|tree|none
=> {Look for OpenSSL in the given path, automatically, in the source tree, or none}
with-miniz=0 => {Use miniz from the source tree}
with-zlib:path|auto|tree
=> {Look for zlib in the given path, automatically, or in the source tree}
with-exec-rel-paths=0
=> {Enable relative paths for external diff/gdiff}
with-legacy-mv-rm=1 => {Enable legacy behavior for mv/rm (skip checkout files)}
with-sanitizer: => {Build with C compiler's -fsanitize=LIST; e.g. address,enum,null,undefined}
with-th1-docs=0 => {Enable TH1 for embedded documentation pages}
with-th1-hooks=0 => {Enable TH1 hooks for commands and web pages}
with-tcl:path => {Enable Tcl integration, with Tcl in the specified path}
with-tcl-stubs=0 => {Enable Tcl integration via stubs library mechanism}
with-tcl-private-stubs=0
=> {Enable Tcl integration via private stubs mechanism}
with-mman=0 => {Enable use of POSIX memory APIs from "sys/mman.h"}
|
| ︙ | ︙ | |||
30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# sqlite wants these types if possible
cc-with {-includes {stdint.h inttypes.h}} {
cc-check-types uint32_t uint16_t int16_t uint8_t
}
# Use pread/pwrite system calls in place of seek + read/write if possible
define USE_PREAD [cc-check-functions pread]
# Find tclsh for the test suite.
#
# We can't use jimsh for this: the test suite uses features of Tcl that
# Jim doesn't support, either statically or due to the way it's built by
# autosetup. For example, Jim supports `file normalize`, but only if
# you build it with HAVE_REALPATH, which won't ever be defined in this
| > > > > > > > | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# sqlite wants these types if possible
cc-with {-includes {stdint.h inttypes.h}} {
cc-check-types uint32_t uint16_t int16_t uint8_t
}
# Use pread/pwrite system calls in place of seek + read/write if possible
define USE_PREAD [cc-check-functions pread]
# If we have cscope here, we'll use it in the "tags" target
if {[cc-check-progs cscope]} {
define COLLECT_CSCOPE_DATA "cscope -bR -s$::autosetup(srcdir)/src"
} else {
define COLLECT_CSCOPE_DATA ""
}
# Find tclsh for the test suite.
#
# We can't use jimsh for this: the test suite uses features of Tcl that
# Jim doesn't support, either statically or due to the way it's built by
# autosetup. For example, Jim supports `file normalize`, but only if
# you build it with HAVE_REALPATH, which won't ever be defined in this
|
| ︙ | ︙ | |||
114 115 116 117 118 119 120 |
} else {
msg-result "no"
}
return $found
}
if {![opt-bool internal-sqlite]} {
| | < < < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > | 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 |
} else {
msg-result "no"
}
return $found
}
if {![opt-bool internal-sqlite]} {
proc find_system_sqlite {} {
# On some systems (slackware), libsqlite3 requires -ldl to link. So
# search for the system SQLite once with -ldl, and once without. If
# the library can only be found with $extralibs set to -ldl, then
# the code below will append -ldl to LIBS.
#
foreach extralibs {{} {-ldl}} {
# Locate the system SQLite by searching for sqlite3_open(). Then check
# if sqlite3_stmt_isexplain can be found as well. If we can find open() but
# not stmt_isexplain(), then the system SQLite is too old to link against
# fossil.
#
if {[check-function-in-lib sqlite3_open sqlite3 $extralibs]} {
# Success. Update symbols and return.
#
define USE_SYSTEM_SQLITE 1
define-append LIBS -lsqlite3
define-append LIBS $extralibs
return
}
}
user-error "system sqlite3 not found"
}
find_system_sqlite
proc test_system_sqlite {} {
# Check compatibility of the system SQLite library by running the sqlcompttest.c
# program in the source tree
#
set cmdline {}
lappend cmdline {*}[get-define CCACHE]
lappend cmdline {*}[get-define CC] {*}[get-define CFLAGS]
lappend cmdline $::autosetup(dir)/../src/sqlcompattest.c -o conftest__
lappend cmdline {*}[get-define LDFLAGS]
lappend cmdline {*}[get-define LIBS]
set ok 1
set err [catch {exec-with-stderr {*}$cmdline} result errinfo]
if {$err} {
configlog "Failed: [join $cmdline]"
if {[string length $result]>0} {configlog $result}
configlog "============"
set ok 0
} elseif {$::autosetup(debug)} {
configlog "Compiled OK: [join $cmdline]"
configlog "============"
}
if {!$ok} {
user-error "unable to compile SQLite compatibility test program"
}
set err [catch {exec-with-stderr ./conftest__} result errinfo]
if {$err} {
user-error $result
}
file delete ./conftest__
}
test_system_sqlite
}
proc is_mingw {} {
return [string match *mingw* [get-define host]]
}
if {[is_mingw]} {
|
| ︙ | ︙ | |||
241 242 243 244 245 246 247 |
define FOSSIL_DYNAMIC_BUILD
}
# Check for libraries that need to be sorted out early
cc-check-function-in-lib iconv iconv
# Helper for OpenSSL checking
| | | 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 |
define FOSSIL_DYNAMIC_BUILD
}
# Check for libraries that need to be sorted out early
cc-check-function-in-lib iconv iconv
# Helper for OpenSSL checking
proc check-for-openssl {msg {cflags {}} {libs {-lssl -lcrypto -lpthread}}} {
msg-checking "Checking for $msg..."
set rc 0
if {[is_mingw]} {
lappend libs -lgdi32 -lwsock32 -lcrypt32
}
if {[info exists ::zlib_lib]} {
lappend libs $::zlib_lib
|
| ︙ | ︙ | |||
319 320 321 322 323 324 325 |
set ssldir [file dirname $autosetup(dir)]/compat/openssl
if {![file isdirectory $ssldir]} {
user-error "The OpenSSL in source tree directory does not exist"
}
set msg "ssl in $ssldir"
set cflags "-I$ssldir/include"
set ldflags "-L$ssldir"
| | | 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
set ssldir [file dirname $autosetup(dir)]/compat/openssl
if {![file isdirectory $ssldir]} {
user-error "The OpenSSL in source tree directory does not exist"
}
set msg "ssl in $ssldir"
set cflags "-I$ssldir/include"
set ldflags "-L$ssldir"
set ssllibs "$ssldir/libssl.a $ssldir/libcrypto.a -lpthread"
set found [check-for-openssl "ssl in source tree" "$cflags $ldflags" $ssllibs]
} else {
if {$ssldirs in {auto ""}} {
catch {
set cflags [exec pkg-config openssl --cflags-only-I]
set ldflags [exec pkg-config openssl --libs-only-L]
set found [check-for-openssl "ssl via pkg-config" "$cflags $ldflags"]
|
| ︙ | ︙ | |||
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 |
if {[cc-check-function-in-lib fuse_mount fuse]} {
define-append EXTRA_CFLAGS -DFOSSIL_HAVE_FUSEFS
define FOSSIL_HAVE_FUSEFS 1
define-append LIBS -lfuse
msg-result "FuseFS support enabled"
}
}
# Finally, append -ldl to make sure it's the last in the list.
# The library order matters in case of static linking.
if {[check-function-in-lib dlopen dl]} {
# Some platforms (*BSD) have the dl functions already in libc and no libdl.
# In such case we can link directly without -ldl.
define-append LIBS [get-define lib_dlopen]
}
make-template Makefile.in
make-config-header autoconfig.h -auto {USE_* FOSSIL_*}
| > > > > > > > > > > > > > > | 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 |
if {[cc-check-function-in-lib fuse_mount fuse]} {
define-append EXTRA_CFLAGS -DFOSSIL_HAVE_FUSEFS
define FOSSIL_HAVE_FUSEFS 1
define-append LIBS -lfuse
msg-result "FuseFS support enabled"
}
}
# Add -fsanitize compile and link options late: we don't want the C
# checks above to run with those sanitizers enabled. It can not only
# be pointless, it can actually break correct tests.
set fsan [opt-val with-sanitizer]
if {[string length $fsan]} {
define-append EXTRA_CFLAGS -fsanitize=$fsan
define-append EXTRA_LDFLAGS -fsanitize=$fsan
if {[string first "undefined" $fsan] != -1} {
# We need to link with libubsan if we're compiling under
# GCC with -fsanitize=undefined.
cc-check-function-in-lib __ubsan_handle_add_overflow ubsan
}
}
# Finally, append -ldl to make sure it's the last in the list.
# The library order matters in case of static linking.
if {[check-function-in-lib dlopen dl]} {
# Some platforms (*BSD) have the dl functions already in libc and no libdl.
# In such case we can link directly without -ldl.
define-append LIBS [get-define lib_dlopen]
}
make-template Makefile.in
make-config-header autoconfig.h -auto {USE_* FOSSIL_*}
|
1 2 3 4 | Built-in Skins ============== Each subdirectory under this folder describes a built-in "skin". | | | > > > > > | | > > | | > | < | | | 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 |
Built-in Skins
==============
Each subdirectory under this folder describes a built-in "skin".
There are five key files in each subdirectory:
* `css.txt` → The CSS for the skin
* `details.txt` → Skin-specific settings
* `footer.txt` → Text of the Content Footer for each page
* `header.txt` → Text of the Content Header for each page
* `js.txt` → Javascript included in the Content Footer
To improve an existing built-in skin, simply edit the appropriate
files and recompile.
To add a new skin:
1. Create a new subdirectory under skins/. (The new directory is
called "skins/newskin" below but you should use a new original
name, of course.)
2. Add files skins/newskin/css.txt, skins/newskin/details.txt,
skins/newskin/footer.txt, skins/newskin/header.txt, and
skins/newskin/js.txt. Be sure to "fossil add" these files.
3. Go to the src/ directory and rerun "tclsh makemake.tcl". This
step rebuilds the various makefiles so that they have dependencies
on the skin files you just installed.
4. Edit the BuiltinSkin[] array near the top of the src/skins.c source
file so that it describes and references the "newskin" skin.
5. Type "make" to rebuild.
See the [custom skin documentation](../www/customskin.md) for more information.
Development Hints
-----------------
One way to develop a new skin is to copy the baseline files (css.txt,
details.txt, footer.txt, header.txt, and js.txt) into a working
directory $WORKDIR then launch Fossil with a command-line option
"--skin $WORKDIR". Example:
cp -r skins/default newskin
fossil ui --skin ./newskin
When the argument to --skin contains one or more '/' characters, the
appropriate skin files are read from disk from the directory specified.
So after launching fossil as shown above, you can edit the newskin/*.txt
files using your favorite text editor, then press Reload on your browser
to see immediate results.
|
| ︙ | ︙ | |||
289 290 291 292 293 294 295 |
max-width: 1200px;
margin: 0 auto;
box-sizing: border-box
}
.column,
.columns {
width: 100%;
| | | 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
max-width: 1200px;
margin: 0 auto;
box-sizing: border-box
}
.column,
.columns {
width: 100%;
/*float: left; can break README.md in /dir view*/
box-sizing: border-box
}
@media (min-width:400px) {
.container {
width: 95%;
padding: 0
}
|
| ︙ | ︙ | |||
800 801 802 803 804 805 806 807 808 809 810 811 812 813 |
ul.browser li.file:hover,
ul.browser li.file:hover * {
background-color: #333
}
td.browser,
td.tktDescLabel {
vertical-align: top
}
div.filetreeline {
display: table;
width: 100%;
white-space: nowrap
}
.filetree {
| > > > > > > | 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 |
ul.browser li.file:hover,
ul.browser li.file:hover * {
background-color: #333
}
td.browser,
td.tktDescLabel {
vertical-align: top
}
td.tktTlOpen {
color: #ffa0a0;
}
td.tktTlClosed {
color: #555;
}
div.filetreeline {
display: table;
width: 100%;
white-space: nowrap
}
.filetree {
|
| ︙ | ︙ | |||
945 946 947 948 949 950 951 952 953 954 955 956 957 958 |
.timelineSelected > .timelineVerboseCell {
padding: .75em;
border-radius: 5px;
border: solid #ff8000;
vertical-align: top;
text-align: left;
background: #442800
}
.timelineCurrent > .timelineColumnarCell,
.timelineCurrent > .timelineCompactCell,
.timelineCurrent > .timelineDetailCell,
.timelineCurrent > .timelineModernCell,
.timelineCurrent > .timelineVerboseCell {
vertical-align: top;
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
.timelineSelected > .timelineVerboseCell {
padding: .75em;
border-radius: 5px;
border: solid #ff8000;
vertical-align: top;
text-align: left;
background: #442800
}
span.timelineSelected {
border-radius: 5px;
border: solid #ff8000;
vertical-align: top;
text-align: left;
background: #442800
}
.timelineSelected {}
.timelineSecondary {}
.timelineSecondary > .timelineColumnarCell,
.timelineSecondary > .timelineCompactCell,
.timelineSecondary > .timelineDetailCell,
.timelineSecondary > .timelineModernCell,
.timelineSecondary > .timelineVerboseCell {
padding: .75em;
border-radius: 5px;
border: solid #0080ff;
vertical-align: top;
text-align: left;
background: #002844
}
span.timelineSecondary {
border-radius: 5px;
border: solid #0080ff;
vertical-align: top;
text-align: left;
background: #002844
}
.timelineCurrent > .timelineColumnarCell,
.timelineCurrent > .timelineCompactCell,
.timelineCurrent > .timelineDetailCell,
.timelineCurrent > .timelineModernCell,
.timelineCurrent > .timelineVerboseCell {
vertical-align: top;
|
| ︙ | ︙ |
| ︙ | ︙ | |||
1058 1059 1060 1061 1062 1063 1064 |
tr.timelineCurrent {
border-left: 2px solid orange;
background-color: #ffc;
border-bottom: 1px solid #ddd;
border-right: 1px solid #ddd;
}
| | > > > | 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 |
tr.timelineCurrent {
border-left: 2px solid orange;
background-color: #ffc;
border-bottom: 1px solid #ddd;
border-right: 1px solid #ddd;
}
.timelineSelected {
border-left: 2px solid orange;
background-color: #ffffe8;
border-bottom: 1px solid #ddd;
border-right: 1px solid #ddd;
}
.timelineSecondary {
background-color: #e8ffff;
}
tr.timelineCurrent td.timelineTableCell {
}
tr.timelineBottom td {
border-bottom: 0;
}
|
| ︙ | ︙ |
1 2 | <h4>$<title></h4> <table class="tktDsp"> | | | 1 2 3 4 5 6 7 8 9 10 |
<h4>$<title></h4>
<table class="tktDsp">
<tr><td class="tktDspLabel">Ticket Hash</td>
<th1>
if {[info exists tkt_uuid]} {
if {[hascap s]} {
html "<td class='tktDspValue' colspan='3'>$tkt_uuid "
html "($tkt_id)</td></tr>\n"
} else {
html "<td class='tktDspValue' colspan='3'>$tkt_uuid</td></tr>\n"
|
| ︙ | ︙ |
1 2 | <h4>$<title></h4> <table class="tktDsp"> | | | 1 2 3 4 5 6 7 8 9 10 |
<h4>$<title></h4>
<table class="tktDsp">
<tr><td class="tktDspLabel">Ticket Hash</td>
<th1>
if {[info exists tkt_uuid]} {
if {[hascap s]} {
html "<td class='tktDspValue' colspan='3'>$tkt_uuid "
html "($tkt_id)</td></tr>\n"
} else {
html "<td class='tktDspValue' colspan='3'>$tkt_uuid</td></tr>\n"
|
| ︙ | ︙ |
1 2 3 4 5 6 |
<html lang="en">
<head>
<meta charset="utf-8">
<base href="$baseurl/$current_page" />
<title>$<project_name>: $<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
| | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<html lang="en">
<head>
<meta charset="utf-8">
<base href="$baseurl/$current_page" />
<title>$<project_name>: $<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="$default_csp"/>
<link rel="alternate" type="application/rss+xml" title="RSS Feed" href="$home/timeline.rss" />
<link rel="stylesheet" href="$stylesheet_url" type="text/css" media="screen" />
<script nonce="$<nonce>">
function gebi(x){
if(/^#/.test(x)) x = x.substr(1);
var e = document.getElementById(x);
if(!e) throw new Error("Expecting element with ID "+x);
else return e;
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
143 144 145 146 147 148 149 |
overflow: auto;
border: 1px solid #ccc;
border-radius: 5px;
}
.content blockquote {
padding: 0 15px;
}
| | | 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
overflow: auto;
border: 1px solid #ccc;
border-radius: 5px;
}
.content blockquote {
padding: 0 15px;
}
div.forumHierRoot blockquote, div.forumHier blockquote, div.forumEdit blockquote, div.forumTime blockquote, div.forumTimeline blockquote {
background-color: rgba(65, 131, 196, 0.1);
border-left: 3px solid #254769;
padding: .1em 1em;
}
table.report {
cursor: auto;
|
| ︙ | ︙ |
1 2 3 4 |
<div class="header">
<div class="title"><h1>$<project_name></h1>$<title></div>
<div class="status"><th1>
if {[info exists login]} {
| | | > | | 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 |
<div class="header">
<div class="title"><h1>$<project_name></h1>$<title></div>
<div class="status"><th1>
if {[info exists login]} {
html "<a href='$home/login'>$login</a>\n"
} else {
html "<a href='$home/login'>Login</a>\n"
}
</th1></div>
</div>
<div class="mainmenu">
<th1>
proc menulink {url name cls} {
upvar current_page current
upvar home home
if {[string range $url 0 [string length $current]] eq "/$current"} {
html "<a href='$home$url' class='active $cls'>$name</a>\n"
} else {
html "<a href='$home$url' class='$cls'>$name</a>\n"
}
}
html "<a id='hbbtn' href='$home/sitemap' aria-label='Site Map'>☰</a>"
menulink $index_page Home {}
if {[anycap jor]} {
menulink /timeline Timeline {}
}
if {[hascap oh]} {
if {![info exists current_checkin]} {set current_checkin tip}
menulink /dir?ci=$current_checkin Files desktoponly
}
if {[hascap o]} {
menulink /brlist Branches desktoponly
menulink /taglist Tags wideonly
}
if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
menulink /forum Forum wideonly
|
| ︙ | ︙ |
| ︙ | ︙ | |||
166 167 168 169 170 171 172 | border: 0; cellpadding: 0; font-family: "courier new"; border-spacing: 0px 2px; // border-collapse: collapse; } | | > > > | 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
border: 0;
cellpadding: 0;
font-family: "courier new";
border-spacing: 0px 2px;
// border-collapse: collapse;
}
.timelineSelected {
background-color: #7EA2D9;
}
.timelineSecondary {
background-color: #7EA27E;
}
/* commit node */
.tl-node {
width: 10px;
height: 10px;
border: 1px solid #fff;
background: #485D7B;
|
| ︙ | ︙ | |||
283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
/* format for values on ticket display page */
td.tktDspValue {
text-align: left;
vertical-align: top;
background-color: #485D7B;
}
/* format for example table cells on the report edit page */
td.rpteditex {
border-width: thin;
border-color: white;
border-style: solid;
}
| > > > > > > > > | 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
/* format for values on ticket display page */
td.tktDspValue {
text-align: left;
vertical-align: top;
background-color: #485D7B;
}
/* Ticket display on timelines */
td.tktTlOpen {
color: #ffc0c0;
}
td.tktTlClose {
color: #c0c0c0;
}
/* format for example table cells on the report edit page */
td.rpteditex {
border-width: thin;
border-color: white;
border-style: solid;
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
693 694 695 696 697 698 699 700 701 702 703 704 705 706 |
/* format for values on ticket display page */
td.tktDspValue {
background-color: #111;
text-align: left;
vertical-align: top;
}
/* format for ticket error messages */
span.tktError {
color: #f00;
font-weight: bold;
}
| > > > > > | 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 |
/* format for values on ticket display page */
td.tktDspValue {
background-color: #111;
text-align: left;
vertical-align: top;
}
/* Tickets on timelines */
td.tktTlOpen {
color: #ffa0a0;
}
/* format for ticket error messages */
span.tktError {
color: #f00;
font-weight: bold;
}
|
| ︙ | ︙ | |||
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 |
}
.timelineTable .timelineSelected {
background: #222;
border: 0;
box-shadow: none;
}
.timelineTable .timelineSelected .timelineTime {
background: #333;
border-radius: 1rem 0 0 1rem;
box-shadow: 2px 2px 1px #000;
}
.timelineTable .timelineSelected .timelineColumnarCell {
background: #333;
box-shadow: 2px 2px 1px #000;
}
.timelineTable .timelineSelected .timelineModernCell ,
.timelineTable .timelineSelected .timelineCompactCell ,
.timelineTable .timelineSelected .timelineVerboseCell ,
.timelineTable .timelineSelected .timelineDetailCell {
background: #333;
border-radius: 0 1rem 1rem 0;
box-shadow: 2px 2px 1px #000;
}
.timelineTable .timelineModernCell .timelineModernComment ,
.timelineTable .timelineModernCell .timelineModernDetail ,
.timelineTable .timelineCompactCell .timelineCompactComment ,
.timelineTable .timelineCompactCell .timelineCompactDetail ,
.timelineTable .timelineVerboseCell .timelineVerboseComment ,
.timelineTable .timelineVerboseCell .timelineVerboseDetail {
| > > > > > > > > > | 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 |
}
.timelineTable .timelineSelected {
background: #222;
border: 0;
box-shadow: none;
}
.timelineSelected {}
.timelineSecondary {}
.timelineTable .timelineSelected .timelineTime {
background: #333;
border-radius: 1rem 0 0 1rem;
box-shadow: 2px 2px 1px #000;
}
.timelineTable .timelineSelected .timelineColumnarCell {
background: #333;
box-shadow: 2px 2px 1px #000;
}
.timelineTable .timelineSelected .timelineModernCell ,
.timelineTable .timelineSelected .timelineCompactCell ,
.timelineTable .timelineSelected .timelineVerboseCell ,
.timelineTable .timelineSelected .timelineDetailCell {
background: #333;
border-radius: 0 1rem 1rem 0;
box-shadow: 2px 2px 1px #000;
}
span.timelineSelected {
padding: 0 1em 0 1em;
border-radius: 1rem;
background: #333;
box-shadow: 2px 2px 1px #000;
}
.timelineTable .timelineModernCell .timelineModernComment ,
.timelineTable .timelineModernCell .timelineModernDetail ,
.timelineTable .timelineCompactCell .timelineCompactComment ,
.timelineTable .timelineCompactCell .timelineCompactDetail ,
.timelineTable .timelineVerboseCell .timelineVerboseComment ,
.timelineTable .timelineVerboseCell .timelineVerboseDetail {
|
| ︙ | ︙ |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
/* Attach appropriate javascript to each ".accordion" button so that
** it expands and contracts when clicked.
** The uncompressed source code for the SVG icons can be found on the
** wiki page "branch/accordion-experiments" in the Fossil repository.
*/
var acc_svgdata = ["data:image/svg+xml,"+
"%3Csvg xmlns='http:"+"/"+"/www.w3.org/2000/svg' viewBox='0 0 16 16'%3E"+
"%3Cpath style='fill:black;opacity:0' d='M16,16H0V0h16v16z'/%3E"+
"%3Cpath style='fill:rgb(240,240,240)' d='M14,14H2V2h12v12z'/%3E"+
"%3Cpath style='fill:rgb(64,64,64)' d='M13,13H3V3h10v10z'/%3E"+
"%3Cpath style='fill:rgb(248,248,248)' d='M12,12H4V4h8v8z'/%3E"+
"%3Cpath style='fill:rgb(80,128,208)' d='", "'/%3E%3C/svg%3E",
"M5,7h2v-2h2v2h2v2h-2v2h-2v-2h-2z", "M11,9H5V7h6v6z"];
var a = document.getElementsByClassName("accordion");
for(var i=0; i<a.length; i++){
var img = document.createElement("img");
img.src = acc_svgdata[0]+acc_svgdata[2]+acc_svgdata[1];
img.className = "accordion_btn accordion_btn_plus";
a[i].insertBefore(img,a[i].firstChild);
img = document.createElement("img");
img.src = acc_svgdata[0]+acc_svgdata[3]+acc_svgdata[1];
img.className = "accordion_btn accordion_btn_minus";
a[i].insertBefore(img,a[i].firstChild);
var p = a[i].nextElementSibling;
p.style.maxHeight = p.scrollHeight + "px";
a[i].addEventListener("click",function(){
var x = this.nextElementSibling;
if( this.classList.contains("accordion_closed") ){
x.style.maxHeight = x.scrollHeight + "px";
}else{
x.style.maxHeight = "0";
}
this.classList.toggle("accordion_closed");
});
}
|
| ︙ | ︙ | |||
238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
if( zReserved ) continue;
nAdd += add_one_file(zToAdd, vid);
}
db_finalize(&loop);
blob_reset(&repoName);
return nAdd;
}
/*
** COMMAND: add
**
** Usage: %fossil add ?OPTIONS? FILE1 ?FILE2 ...?
**
** Make arrangements to add one or more files or directories to the
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
if( zReserved ) continue;
nAdd += add_one_file(zToAdd, vid);
}
db_finalize(&loop);
blob_reset(&repoName);
return nAdd;
}
/*
** Resets the ADDED/DELETED state of a checkout, such that all
** newly-added (but not yet committed) files are no longer added and
** newly-removed (but not yet committed) files are no longer
** removed. If bIsAdd is true, it operates on the "add" state, else it
** operates on the "rm" state.
**
** If bDryRun is true it outputs what it would have done, but does not
** actually do it. In this case it rolls back the transaction it
** starts (so don't start a transaction before calling this).
**
** If bVerbose is true it outputs the name of each reset entry.
**
** This is intended to be called only in the context of the
** add/rm/addremove commands, after a call to verify_all_options().
**
** Un-added files are not modified but any un-rm'd files which are
** missing from the checkout are restored from the repo. un-rm'd files
** which exist in the checkout are left as-is, rather than restoring
** them using vfile_to_disk(), to avoid overwriting any local changes
** made to those files.
*/
static void addremove_reset(int bIsAdd, int bDryRun, int bVerbose){
int nReset = 0; /* # of entries which get reset */
Stmt stmt; /* vfile loop query */
db_begin_transaction();
db_prepare(&stmt, "SELECT id, pathname FROM vfile "
"WHERE %s ORDER BY pathname",
bIsAdd==0 ? "deleted<>0" : "rid=0"/*safe-for-%s*/);
while( db_step(&stmt)==SQLITE_ROW ){
/* This loop exists only so we can restore the contents of un-rm'd
** files and support verbose mode. All manipulation of vfile's
** contents happens after the loop. For the ADD case in non-verbose
** mode we "could" skip this loop entirely.
*/
int const id = db_column_int(&stmt, 0);
char const * zPathname = db_column_text(&stmt, 1);
Blob relName = empty_blob;
if(bIsAdd==0 || bVerbose!=0){
/* Make filename relative... */
char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
file_relative_name(zFullName, &relName, 0);
fossil_free(zFullName);
}
if(bIsAdd==0){
/* Restore contents of missing un-rm'd files. We don't do this
** unconditionally because we might cause data loss if a file
** is modified, rm'd, then un-rm'd.
*/
++nReset;
if(!file_isfile_or_link(blob_str(&relName))){
if(bDryRun==0){
vfile_to_disk(0, id, 0, 0);
if(bVerbose){
fossil_print("Restored missing file: %b\n", &relName);
}
}else{
fossil_print("Dry-run: not restoring missing file: %b\n", &relName);
}
}
if(bVerbose){
fossil_print("Un-removed: %b\n", &relName);
}
}else{
/* un-add... */
++nReset;
if(bVerbose){
fossil_print("Un-added: %b\n", &relName);
}
}
blob_reset(&relName);
}
db_finalize(&stmt);
if(nReset>0){
if(bIsAdd==0){
if(bDryRun==0){
db_exec_sql("UPDATE vfile SET deleted=0 WHERE deleted<>0");
}
fossil_print("Un-removed %d file(s).\n", nReset);
}else{
if(bDryRun==0){
db_exec_sql("DELETE FROM vfile WHERE rid=0");
}
fossil_print("Un-added %d file(s).\n", nReset);
}
}
db_end_transaction(bDryRun ? 1 : 0);
}
/*
** COMMAND: add
**
** Usage: %fossil add ?OPTIONS? FILE1 ?FILE2 ...?
**
** Make arrangements to add one or more files or directories to the
|
| ︙ | ︙ | |||
267 268 269 270 271 272 273 | ** ** The --case-sensitive option determines whether or not filenames should ** be treated case sensitive or not. If the option is not given, the default ** depends on the global setting, or the operating system default, if not set. ** ** Options: ** | | | | | > > > > > | > > > > > > > > > > > > > | 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 |
**
** The --case-sensitive option determines whether or not filenames should
** be treated case sensitive or not. If the option is not given, the default
** depends on the global setting, or the operating system default, if not set.
**
** Options:
**
** --case-sensitive BOOL Override the case-sensitive setting.
** --dotfiles include files beginning with a dot (".")
** -f|--force Add files without prompting
** --ignore CSG Ignore unmanaged files matching patterns from
** the Comma Separated Glob (CSG) pattern list
** --clean CSG Also ignore files matching patterns from
** the Comma Separated Glob (CSG) list
** --reset Reset the ADDED state of a checkout, such
** that all newly-added (but not yet committed)
** files are no longer added. No flags other
** than --verbose and --dry-run may be used
** with --reset.
**
** The following options are only valid with --reset:
** -v|--verbose Outputs information about each --reset file.
** -n|--dry-run Display instead of run actions.
**
** See also: addremove, rm
*/
void add_cmd(void){
int i; /* Loop counter */
int vid; /* Currently checked out version */
int nRoot; /* Full path characters in g.zLocalRoot */
const char *zCleanFlag; /* The --clean option or clean-glob setting */
const char *zIgnoreFlag; /* The --ignore option or ignore-glob setting */
Glob *pIgnore, *pClean; /* Ignore everything matching the glob patterns */
unsigned scanFlags = 0; /* Flags passed to vfile_scan() */
int forceFlag;
if(0!=find_option("reset",0,0)){
int const verboseFlag = find_option("verbose","v",0)!=0;
int const dryRunFlag = find_option("dry-run","n",0)!=0;
db_must_be_within_tree();
verify_all_options();
addremove_reset(1, dryRunFlag, verboseFlag);
return;
}
zCleanFlag = find_option("clean",0,1);
zIgnoreFlag = find_option("ignore",0,1);
forceFlag = find_option("force","f",0)!=0;
if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL;
/* We should be done with options.. */
|
| ︙ | ︙ | |||
337 338 339 340 341 342 343 344 345 346 347 348 349 350 |
char *zTreeName = &zName[nRoot];
if( !forceFlag && glob_match(pIgnore, zTreeName) ){
Blob ans;
char cReply;
char *prompt = mprintf("file \"%s\" matches \"ignore-glob\". "
"Add it (a=all/y/N)? ", zTreeName);
prompt_user(prompt, &ans);
cReply = blob_str(&ans)[0];
blob_reset(&ans);
if( cReply=='a' || cReply=='A' ){
forceFlag = 1;
}else if( cReply!='y' && cReply!='Y' ){
blob_reset(&fullName);
continue;
| > | 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 |
char *zTreeName = &zName[nRoot];
if( !forceFlag && glob_match(pIgnore, zTreeName) ){
Blob ans;
char cReply;
char *prompt = mprintf("file \"%s\" matches \"ignore-glob\". "
"Add it (a=all/y/N)? ", zTreeName);
prompt_user(prompt, &ans);
fossil_free(prompt);
cReply = blob_str(&ans)[0];
blob_reset(&ans);
if( cReply=='a' || cReply=='A' ){
forceFlag = 1;
}else if( cReply!='y' && cReply!='Y' ){
blob_reset(&fullName);
continue;
|
| ︙ | ︙ | |||
439 440 441 442 443 444 445 446 447 448 449 450 451 |
**
** Options:
** --soft Skip removing files from the checkout.
** This supersedes the --hard option.
** --hard Remove files from the checkout.
** --case-sensitive <BOOL> Override the case-sensitive setting.
** -n|--dry-run If given, display instead of run actions.
**
** See also: addremove, add
*/
void delete_cmd(void){
int i;
int removeFiles;
| > > > > > > > | > | > > > > > > | 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 |
**
** Options:
** --soft Skip removing files from the checkout.
** This supersedes the --hard option.
** --hard Remove files from the checkout.
** --case-sensitive <BOOL> Override the case-sensitive setting.
** -n|--dry-run If given, display instead of run actions.
** --reset Reset the DELETED state of a checkout, such
** that all newly-rm'd (but not yet committed)
** files are no longer removed. No flags other
** than --verbose or --dry-run may be used with
** --reset.
** --verbose|-v Outputs information about each --reset file.
** Only usable with --reset.
**
** See also: addremove, add
*/
void delete_cmd(void){
int i;
int removeFiles;
int dryRunFlag = find_option("dry-run","n",0)!=0;
int softFlag;
int hardFlag;
Stmt loop;
if(0!=find_option("reset",0,0)){
int const verboseFlag = find_option("verbose","v",0)!=0;
db_must_be_within_tree();
verify_all_options();
addremove_reset(0, dryRunFlag, verboseFlag);
return;
}
softFlag = find_option("soft",0,0)!=0;
hardFlag = find_option("hard",0,0)!=0;
/* We should be done with options.. */
verify_all_options();
db_must_be_within_tree();
|
| ︙ | ︙ | |||
613 614 615 616 617 618 619 | ** ** The -n|--dry-run option shows what would happen without actually doing ** anything. ** ** This command can be used to track third party software. ** ** Options: | | | | | | > > > > > > > > | | | > > > > > > > > > > > > > | 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 |
**
** The -n|--dry-run option shows what would happen without actually doing
** anything.
**
** This command can be used to track third party software.
**
** Options:
** --case-sensitive BOOL Override the case-sensitive setting.
** --dotfiles Include files beginning with a dot (".")
** --ignore CSG Ignore unmanaged files matching patterns from
** the Comma Separated Glob (CSG) list
** --clean CSG Also ignore files matching patterns from
** the Comma Separated Glob (CSG) list
** -n|--dry-run If given, display instead of run actions.
** --reset Reset the ADDED/DELETED state of a checkout,
** such that all newly-added (but not yet committed)
** files are no longer added and all newly-removed
** (but not yet committed) files are no longer
** removed. No flags other than --verbose and
** --dry-run may be used with --reset.
** --verbose|-v Outputs information about each --reset file.
** Only usable with --reset.
**
** See also: add, rm
*/
void addremove_cmd(void){
Blob path;
const char *zCleanFlag;
const char *zIgnoreFlag;
unsigned scanFlags;
int dryRunFlag = find_option("dry-run","n",0)!=0;
int n;
Stmt q;
int vid;
int nAdd = 0;
int nDelete = 0;
Glob *pIgnore, *pClean;
if( !dryRunFlag ){
dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
}
if(0!=find_option("reset",0,0)){
int const verboseFlag = find_option("verbose","v",0)!=0;
db_must_be_within_tree();
verify_all_options();
addremove_reset(0, dryRunFlag, verboseFlag);
addremove_reset(1, dryRunFlag, verboseFlag);
return;
}
zCleanFlag = find_option("clean",0,1);
zIgnoreFlag = find_option("ignore",0,1);
scanFlags = find_option("dotfiles",0,0)!=0 ? SCAN_ALL : 0;
/* We should be done with options.. */
verify_all_options();
/* Fail if unprocessed arguments are present, in case user expect the
** addremove command to accept a list of file or directory.
*/
if( g.argc>2 ){
|
| ︙ | ︙ | |||
707 708 709 710 711 712 713 |
}
db_finalize(&q);
/* show command summary */
fossil_print("added %d files, deleted %d files\n", nAdd, nDelete);
db_end_transaction(dryRunFlag);
}
| < | 852 853 854 855 856 857 858 859 860 861 862 863 864 865 |
}
db_finalize(&q);
/* show command summary */
fossil_print("added %d files, deleted %d files\n", nAdd, nDelete);
db_end_transaction(dryRunFlag);
}
/*
** Rename a single file.
**
** The original name of the file is zOrig. The new filename is zNew.
*/
static void mv_one_file(
|
| ︙ | ︙ | |||
933 934 935 936 937 938 939 |
int nOrig;
file_tree_name(g.argv[i], &orig, 0, 1);
zOrig = blob_str(&orig);
nOrig = blob_size(&orig);
db_prepare(&q,
"SELECT pathname FROM vfile"
" WHERE vid=%d"
| | | > | 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 |
int nOrig;
file_tree_name(g.argv[i], &orig, 0, 1);
zOrig = blob_str(&orig);
nOrig = blob_size(&orig);
db_prepare(&q,
"SELECT pathname FROM vfile"
" WHERE vid=%d"
" AND (pathname='%q' %s OR (pathname>'%q/' %s AND pathname<'%q0' %s))"
" ORDER BY 1",
vid, zOrig, filename_collation(), zOrig, filename_collation(),
zOrig, filename_collation()
);
while( db_step(&q)==SQLITE_ROW ){
const char *zPath = db_column_text(&q, 0);
int nPath = db_column_bytes(&q, 0);
const char *zTail;
if( nPath==nOrig ){
zTail = file_tail(zPath);
}else if( origType!=0 && destType==1 ){
zTail = &zPath[nOrig-strlen(file_tail(zOrig))];
}else{
zTail = &zPath[nOrig+1];
}
db_multi_exec(
"INSERT INTO mv VALUES('%q','%q%q')",
zPath, blob_str(&dest), zTail
);
}
db_finalize(&q);
}
}
db_prepare(&q, "SELECT f, t FROM mv ORDER BY f");
while( db_step(&q)==SQLITE_ROW ){
const char *zFrom = db_column_text(&q, 0);
const char *zTo = db_column_text(&q, 1);
mv_one_file(vid, zFrom, zTo, dryRunFlag);
if( moveFiles ) add_file_to_move(zFrom, zTo);
}
db_finalize(&q);
undo_reset();
db_end_transaction(0);
if( moveFiles ) process_files_to_move(dryRunFlag);
}
/*
** Function for stash_apply to be able to restore a file and indicate
** newly ADDED state.
*/
int stash_add_files_in_sfile(int vid){
return add_files_in_sfile(vid);
}
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
/*
** Copyright (c) 2020 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** 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.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains shared Ajax-related code for /fileedit, the wiki/forum
** editors, and friends.
*/
#include "config.h"
#include "ajax.h"
#include <assert.h>
#include <stdarg.h>
#if INTERFACE
/* enum ajax_render_preview_flags: */
#define AJAX_PREVIEW_LINE_NUMBERS 1
/* enum ajax_render_modes: */
#define AJAX_RENDER_GUESS 0 /* Guess rendering mode based on mimetype. */
/* GUESS must be 0. All others have unspecified values. */
#define AJAX_RENDER_PLAIN_TEXT 1 /* Render as plain text. */
#define AJAX_RENDER_HTML_IFRAME 2 /* Render as HTML inside an IFRAME. */
#define AJAX_RENDER_HTML_INLINE 3 /* Render as HTML without an IFRAME. */
#define AJAX_RENDER_WIKI 4 /* Render as wiki/markdown. */
#endif
/*
** Emits JS code which initializes the
** fossil.page.previewModes object to a map of AJAX_RENDER_xxx values
** and symbolic names for use by client-side scripts.
**
** If addScriptTag is true then the output is wrapped in a SCRIPT tag
** with the current nonce, else no SCRIPT tag is emitted.
**
** Requires that style_emit_script_fossil_bootstrap() has already been
** called in order to initialize the window.fossil.page object.
*/
void ajax_emit_js_preview_modes(int addScriptTag){
if(addScriptTag){
style_emit_script_tag(0,0);
CX("\n");
}
CX("fossil.page.previewModes={"
"guess: %d, %d: 'guess', wiki: %d, %d: 'wiki',"
"htmlIframe: %d, %d: 'htmlIframe', "
"htmlInline: %d, %d: 'htmlInline', "
"text: %d, %d: 'text'"
"};\n",
AJAX_RENDER_GUESS, AJAX_RENDER_GUESS,
AJAX_RENDER_WIKI, AJAX_RENDER_WIKI,
AJAX_RENDER_HTML_IFRAME, AJAX_RENDER_HTML_IFRAME,
AJAX_RENDER_HTML_INLINE, AJAX_RENDER_HTML_INLINE,
AJAX_RENDER_PLAIN_TEXT, AJAX_RENDER_PLAIN_TEXT);
if(addScriptTag){
style_emit_script_tag(1,0);
}
}
/*
** Returns a value from the ajax_render_modes enum, based on the
** given mime type string (which may be NULL), defaulting to
** AJAX_RENDER_PLAIN_TEXT.
*/
int ajax_render_mode_for_mimetype(const char * zMimetype){
int rc = AJAX_RENDER_PLAIN_TEXT;
if( zMimetype ){
if( fossil_strcmp(zMimetype, "text/html")==0 ){
rc = AJAX_RENDER_HTML_IFRAME;
}else if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0
|| fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
rc = AJAX_RENDER_WIKI;
}
}
return rc;
}
/*
** Renders text/wiki content preview for various /ajax routes.
**
** pContent is text/wiki content to preview. zName is the name of the
** content, for purposes of determining the mimetype based on the
** extension (if NULL, mimetype text/plain is assumed). flags may be a
** bitmask of values from the ajax_render_preview_flags
** enum. *renderMode must specify the render mode to use. If
** *renderMode==AJAX_RENDER_GUESS then *renderMode gets set to the
** mode which is guessed at for the rendering (based on the mimetype).
**
** nIframeHeightEm is only used for the AJAX_RENDER_HTML_IFRAME
** renderMode, and specifies the height, in EM's, of the resulting
** iframe. If passed 0, it defaults to "some sane value."
*/
void ajax_render_preview(Blob * pContent, const char *zName,
int flags, int * renderMode,
int nIframeHeightEm){
const char * zMime;
zMime = zName ? mimetype_from_name(zName) : "text/plain";
if(AJAX_RENDER_GUESS==*renderMode){
*renderMode = ajax_render_mode_for_mimetype(zMime);
}
switch(*renderMode){
case AJAX_RENDER_HTML_IFRAME:{
char * z64 = encode64(blob_str(pContent), blob_size(pContent));
CX("<iframe width='100%%' frameborder='0' "
"marginwidth='0' style='height:%dem' "
"marginheight='0' sandbox='allow-same-origin' "
"src='data:text/html;base64,%z'"
"></iframe>",
nIframeHeightEm ? nIframeHeightEm : 40,
z64);
break;
}
case AJAX_RENDER_HTML_INLINE:{
CX("%b",pContent);
break;
}
case AJAX_RENDER_WIKI:
safe_html_context(DOCSRC_FILE);
wiki_render_by_mimetype(pContent, zMime);
break;
default:{
const char *zContent = blob_str(pContent);
if(AJAX_PREVIEW_LINE_NUMBERS & flags){
output_text_with_line_numbers(zContent, "on");
}else{
const char *zExt = strrchr(zName,'.');
if(zExt && zExt[1]){
CX("<pre><code class='language-%s'>%h</code></pre>",
zExt+1, zContent);
}else{
CX("<pre>%h</pre>", zContent);
}
}
break;
}
}
}
/*
** Renders diffs for ajax routes. pOrig is the "original" (v1) content
** and pContent is the locally-edited (v2) content. diffFlags is any
** set of flags suitable for passing to text_diff().
*/
void ajax_render_diff(Blob * pOrig, Blob *pContent, u64 diffFlags){
Blob out = empty_blob;
text_diff(pOrig, pContent, &out, 0, diffFlags);
if(blob_size(&out)==0){
/* nothing to do */
}else if(DIFF_SIDEBYSIDE & diffFlags){
CX("%b",&out);
}else{
CX("<pre class='udiff'>%b</pre>",&out);
}
blob_reset(&out);
}
/*
** Helper for /ajax routes. Clears the CGI content buffer, sets an
** HTTP error status code, and queues up a JSON response in the form
** of an object:
**
** {error: formatted message}
**
** If httpCode<=0 then it defaults to 500.
**
** After calling this, the caller should immediately return.
*/
void ajax_route_error(int httpCode, const char * zFmt, ...){
Blob msg = empty_blob;
Blob content = empty_blob;
va_list vargs;
va_start(vargs,zFmt);
blob_vappendf(&msg, zFmt, vargs);
va_end(vargs);
blob_appendf(&content,"{\"error\":%!j}", blob_str(&msg));
blob_reset(&msg);
cgi_set_content(&content);
cgi_set_status(httpCode>0 ? httpCode : 500, "Error");
cgi_set_content_type("application/json");
}
/*
** Performs bootstrapping common to the /ajax/xyz AJAX routes, such as
** logging in the user.
**
** Returns false (0) if bootstrapping fails, in which case it has
** reported the error and the route should immediately return. Returns
** true on success.
**
** If requireWrite is true then write permissions are required.
** If requirePost is true then the request is assumed to be using
** POST'ed data and CSRF validation is performed.
**
*/
int ajax_route_bootstrap(int requireWrite, int requirePost){
login_check_credentials();
if( requireWrite!=0 && g.perm.Write==0 ){
ajax_route_error(403,"Write permissions required.");
return 0;
}else if(0==cgi_csrf_safe(requirePost)){
ajax_route_error(403,
"CSRF violation (make sure sending of HTTP "
"Referer headers is enabled for XHR "
"connections).");
return 0;
}
return 1;
}
/*
** Helper for collecting filename/checkin request parameters.
**
** If zFn is not NULL, it is assigned the value of the first one of
** the "filename" or "fn" CGI parameters which is set.
**
** If zCi is not NULL, it is assigned the value of the first one of
** the "checkin" or "ci" CGI parameters which is set.
**
** If a parameter is not NULL, it will be assigned NULL if the
** corresponding parameter is not set.
**
** Returns the number of non-NULL values it assigns to arguments. Thus
** if passed (&x, NULL), it returns 1 if it assigns non-NULL to *x and
** 0 if it assigns NULL to *x.
*/
int ajax_get_fnci_args( const char **zFn, const char **zCi ){
int rc = 0;
if(zCi!=0){
*zCi = PD("checkin",P("ci"));
if( *zCi ) ++rc;
}
if(zFn!=0){
*zFn = PD("filename",P("fn"));
if (*zFn) ++rc;
}
return rc;
}
/*
** AJAX route /ajax/preview-wiki
**
** Required query parameters:
**
** filename=name of content, for use in determining the
** mimetype/render mode. content=text
**
** Optional query parameters:
**
** render_mode=integer (AJAX_RENDER_xxx) (default=AJAX_RENDER_GUESS)
**
** ln=0 or 1 to disable/enable line number mode in
** AJAX_RENDER_PLAIN_TEXT mode.
**
** iframe_height=integer (default=40) Height, in EMs of HTML preview
** iframe.
**
** User must have Write access to use this page.
**
** Responds with the HTML content of the preview. On error it produces
** a JSON response as documented for ajax_route_error().
**
** Extra response headers:
**
** x-ajax-render-mode: string representing the rendering mode
** which was really used (which will differ from the requested mode
** only if mode 0 (guess) was requested). The names are documented
** below in code and match those in the emitted JS object
** fossil.page.previewModes.
*/
void ajax_route_preview_text(void){
const char * zFilename = 0;
const char * zContent = P("content");
int renderMode = atoi(PD("render_mode","0"));
int ln = atoi(PD("ln","0"));
int iframeHeight = atoi(PD("iframe_height","40"));
Blob content = empty_blob;
const char * zRenderMode = 0;
ajax_get_fnci_args( &zFilename, 0 );
if(!ajax_route_bootstrap(1,1)){
return;
}
if(zFilename==0){
/* The filename is only used for mimetype determination,
** so we can default it... */
zFilename = "foo.txt";
}
cgi_set_content_type("text/html");
blob_init(&content, zContent, -1);
ajax_render_preview(&content, zFilename,
ln ? AJAX_PREVIEW_LINE_NUMBERS : 0,
&renderMode, iframeHeight);
/*
** Now tell the caller if we did indeed use AJAX_RENDER_WIKI, so that
** they can re-set the <base href> to an appropriate value (which
** requires knowing the content's current checkin version, which we
** don't have here).
*/
switch(renderMode){
/* The strings used here MUST correspond to those used in the JS-side
** fossil.page.previewModes map.
*/
case AJAX_RENDER_WIKI: zRenderMode = "wiki"; break;
case AJAX_RENDER_HTML_INLINE: zRenderMode = "htmlInline"; break;
case AJAX_RENDER_HTML_IFRAME: zRenderMode = "htmlIframe"; break;
case AJAX_RENDER_PLAIN_TEXT: zRenderMode = "text"; break;
case AJAX_RENDER_GUESS:
assert(!"cannot happen");
}
if(zRenderMode!=0){
cgi_printf_header("x-ajax-render-mode: %s\r\n", zRenderMode);
}
}
/*
** Internal mapping of ajax sub-route names to various metadata.
*/
struct AjaxRoute {
const char *zName; /* Name part of the route after "ajax/" */
void (*xCallback)(); /* Impl function for the route. */
int bWriteMode; /* True if requires write mode */
int bPost; /* True if requires POST (i.e. CSRF
** verification) */
};
typedef struct AjaxRoute AjaxRoute;
/*
** Comparison function for bsearch() for searching an AjaxRoute
** list for a matching name.
*/
static int cmp_ajax_route_name(const void *a, const void *b){
const AjaxRoute * rA = (const AjaxRoute*)a;
const AjaxRoute * rB = (const AjaxRoute*)b;
return fossil_strcmp(rA->zName, rB->zName);
}
/*
** WEBPAGE: ajax
**
** The main dispatcher for shared ajax-served routes. Requires the
** 'name' parameter be the main route's name (as defined in a list in
** this function), noting that fossil automatically assigns all path
** parts after "ajax" to "name", e.g. /ajax/foo/bar assigns
** name=foo/bar.
**
** This "page" is only intended to be used by higher-level pages which
** have certain Ajax-driven features in common. It is not intended to
** be used by clients and NONE of its HTTP interfaces are considered
** documented/stable/supported - they may change on any given build of
** fossil.
**
** The exact response type depends on the route which gets called. In
** the case of an initialization error it emits a JSON-format response
** as documented for ajax_route_error(). Individual routes may emit
** errors in different formats, e.g. HTML.
*/
void ajax_route_dispatcher(void){
const char * zName = P("name");
AjaxRoute routeName = {0,0,0,0};
const AjaxRoute * pRoute = 0;
const AjaxRoute routes[] = {
/* Keep these sorted by zName (for bsearch()) */
{"preview-text", ajax_route_preview_text, 1, 1}
};
if(zName==0 || zName[0]==0){
ajax_route_error(400,"Missing required [route] 'name' parameter.");
return;
}
routeName.zName = zName;
pRoute = (const AjaxRoute *)bsearch(&routeName, routes,
count(routes), sizeof routes[0],
cmp_ajax_route_name);
if(pRoute==0){
ajax_route_error(404,"Ajax route not found.");
return;
}else if(0==ajax_route_bootstrap(pRoute->bWriteMode, pRoute->bPost)){
return;
}
pRoute->xCallback();
}
|
| ︙ | ︙ | |||
44 45 46 47 48 49 50 51 52 53 54 55 56 57 | @ -- In the last case the suname column points from the subscriber entry @ -- to the USER entry. @ -- @ -- The ssub field is a string where each character indicates a particular @ -- type of event to subscribe to. Choices: @ -- a - Announcements @ -- c - Check-ins @ -- t - Ticket changes @ -- w - Wiki changes @ -- Probably different codes will be added in the future. In the future @ -- we might also add a separate table that allows subscribing to email @ -- notifications for specific branches or tags or tickets. @ -- @ CREATE TABLE repository.subscriber( | > | 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | @ -- In the last case the suname column points from the subscriber entry @ -- to the USER entry. @ -- @ -- The ssub field is a string where each character indicates a particular @ -- type of event to subscribe to. Choices: @ -- a - Announcements @ -- c - Check-ins @ -- f - Forum posts @ -- t - Ticket changes @ -- w - Wiki changes @ -- Probably different codes will be added in the future. In the future @ -- we might also add a separate table that allows subscribing to email @ -- notifications for specific branches or tags or tickets. @ -- @ CREATE TABLE repository.subscriber( |
| ︙ | ︙ | |||
108 109 110 111 112 113 114 |
**
** If the bOnlyIfEnabled option is true, then tables are only created
** if the email-send-method is something other than "off".
*/
void alert_schema(int bOnlyIfEnabled){
if( !alert_tables_exist() ){
if( bOnlyIfEnabled
| | | | 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
**
** If the bOnlyIfEnabled option is true, then tables are only created
** if the email-send-method is something other than "off".
*/
void alert_schema(int bOnlyIfEnabled){
if( !alert_tables_exist() ){
if( bOnlyIfEnabled
&& fossil_strcmp(db_get("email-send-method",0),"off")==0
){
return; /* Don't create table for disabled email */
}
db_exec_sql(zAlertInit);
alert_triggers_enable();
}else if( !db_table_has_column("repository","pending_alert","sentMod") ){
db_multi_exec(
"ALTER TABLE repository.pending_alert"
" ADD COLUMN sentMod BOOLEAN DEFAULT false;"
);
}
|
| ︙ | ︙ | |||
156 157 158 159 160 161 162 |
}
/*
** Return true if email alerts are active.
*/
int alert_enabled(void){
if( !alert_tables_exist() ) return 0;
| | | 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
}
/*
** Return true if email alerts are active.
*/
int alert_enabled(void){
if( !alert_tables_exist() ) return 0;
if( fossil_strcmp(db_get("email-send-method",0),"off")==0 ) return 0;
return 1;
}
/*
** If the subscriber table does not exist, then paint an error message
** web page and return true.
**
|
| ︙ | ︙ | |||
181 182 183 184 185 186 187 |
/*
** Insert a "Subscriber List" submenu link if the current user
** is an administrator.
*/
void alert_submenu_common(void){
if( g.perm.Admin ){
if( fossil_strcmp(g.zPath,"subscribers") ){
| | | 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
/*
** Insert a "Subscriber List" submenu link if the current user
** is an administrator.
*/
void alert_submenu_common(void){
if( g.perm.Admin ){
if( fossil_strcmp(g.zPath,"subscribers") ){
style_submenu_element("Subscribers","%R/subscribers");
}
if( fossil_strcmp(g.zPath,"subscribe") ){
style_submenu_element("Add New Subscriber","%R/subscribe");
}
}
}
|
| ︙ | ︙ | |||
236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
"eurl", "", 0);
@ <p><b>Required.</b>
@ This is URL used as the basename for hyperlinks included in
@ email alert text. Omit the trailing "/".
@ Suggested value: "%h(g.zBaseURL)"
@ (Property: "email-url")</p>
@ <hr>
entry_attribute("\"Return-Path\" email address", 20, "email-self",
"eself", "", 0);
@ <p><b>Required.</b>
@ This is the email to which email notification bounces should be sent.
@ In cases where the email notification does not align with a specific
@ Fossil login account (for example, digest messages), this is also
| > > > > > > > | 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
"eurl", "", 0);
@ <p><b>Required.</b>
@ This is URL used as the basename for hyperlinks included in
@ email alert text. Omit the trailing "/".
@ Suggested value: "%h(g.zBaseURL)"
@ (Property: "email-url")</p>
@ <hr>
entry_attribute("Administrator email address", 40, "email-admin",
"eadmin", "", 0);
@ <p>This is the email for the human administrator for the system.
@ Abuse and trouble reports and password reset requests are send here.
@ (Property: "email-admin")</p>
@ <hr>
entry_attribute("\"Return-Path\" email address", 20, "email-self",
"eself", "", 0);
@ <p><b>Required.</b>
@ This is the email to which email notification bounces should be sent.
@ In cases where the email notification does not align with a specific
@ Fossil login account (for example, digest messages), this is also
|
| ︙ | ︙ | |||
295 296 297 298 299 300 301 | @ transmitted via the SMTP protocol (rfc5321) to a "Mail Submission @ Agent" or "MSA" (rfc4409) at the hostname shown here. Optionally @ append a colon and TCP port number (ex: smtp.example.com:587). @ The default TCP port number is 25. @ (Property: "email-send-relayhost")</p> @ <hr> | < < < < < < < | 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | @ transmitted via the SMTP protocol (rfc5321) to a "Mail Submission @ Agent" or "MSA" (rfc4409) at the hostname shown here. Optionally @ append a colon and TCP port number (ex: smtp.example.com:587). @ The default TCP port number is 25. @ (Property: "email-send-relayhost")</p> @ <hr> @ <p><input type="submit" name="submit" value="Apply Changes" /></p> @ </div></form> db_end_transaction(0); style_footer(); } #if 0 |
| ︙ | ︙ | |||
480 481 482 483 484 485 486 |
p = fossil_malloc(sizeof(*p));
memset(p, 0, sizeof(*p));
blob_init(&p->out, 0, 0);
p->mFlags = mFlags;
if( zAltDest ){
p->zDest = zAltDest;
}else{
| | | 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 |
p = fossil_malloc(sizeof(*p));
memset(p, 0, sizeof(*p));
blob_init(&p->out, 0, 0);
p->mFlags = mFlags;
if( zAltDest ){
p->zDest = zAltDest;
}else{
p->zDest = db_get("email-send-method",0);
}
if( fossil_strcmp(p->zDest,"off")==0 ) return p;
if( emailerGetSetting(p, &p->zFrom, "email-self") ) return p;
if( fossil_strcmp(p->zDest,"db")==0 ){
char *zErr;
int rc;
if( emailerGetSetting(p, &p->zDb, "email-send-db") ) return p;
|
| ︙ | ︙ | |||
572 573 574 575 576 577 578 |
return 1;
}
}
return 0;
}
/*
| > | | < | < < | < < | | 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 |
return 1;
}
}
return 0;
}
/*
** Determine whether or not the input string is a valid email address.
** Only look at character up to but not including the first \000 or
** the first cTerm character, whichever comes first.
**
** Return the length of the email addresss string in bytes if the email
** address is valid. If the email address is misformed, return 0.
*/
int email_address_is_valid(const char *z, char cTerm){
int i;
int nAt = 0;
int nDot = 0;
char c;
if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */
for(i=0; (c = z[i])!=0 && c!=cTerm; i++){
if( fossil_isalnum(c) ){
|
| ︙ | ︙ | |||
617 618 619 620 621 622 623 |
}else{
return 0; /* Anything else is an error */
}
}
if( c!=cTerm ) return 0; /* Missing terminator */
if( nAt==0 ) return 0; /* No "@" found anywhere */
if( nDot==0 ) return 0; /* No "." in the domain */
| > | | > > > > > > > > > > > > > > | | 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 |
}else{
return 0; /* Anything else is an error */
}
}
if( c!=cTerm ) return 0; /* Missing terminator */
if( nAt==0 ) return 0; /* No "@" found anywhere */
if( nDot==0 ) return 0; /* No "." in the domain */
return i;
}
/*
** Make a copy of the input string up to but not including the
** first cTerm character.
**
** Verify that the string really that is to be copied really is a
** valid email address. If it is not, then return NULL.
**
** This routine is more restrictive than necessary. It does not
** allow comments, IP address, quoted strings, or certain uncommon
** characters. The only non-alphanumerics allowed in the local
** part are "_", "+", "-" and "+".
*/
char *email_copy_addr(const char *z, char cTerm ){
int i = email_address_is_valid(z, cTerm);
return i==0 ? 0 : mprintf("%.*s", i, z);
}
/*
** Scan the input string for a valid email address enclosed in <...>
** If the string contains one or more email addresses, extract the first
** one into memory obtained from mprintf() and return a pointer to it.
** If no valid email address can be found, return NULL.
|
| ︙ | ︙ | |||
657 658 659 660 661 662 663 664 665 666 667 668 669 670 |
){
const char *zIn = (const char*)sqlite3_value_text(argv[0]);
char *zOut = alert_find_emailaddr(zIn);
if( zOut ){
sqlite3_result_text(context, zOut, -1, fossil_free);
}
}
/*
** Return the hostname portion of an email address - the part following
** the @
*/
char *alert_hostname(const char *zAddr){
char *z = strchr(zAddr, '@');
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
){
const char *zIn = (const char*)sqlite3_value_text(argv[0]);
char *zOut = alert_find_emailaddr(zIn);
if( zOut ){
sqlite3_result_text(context, zOut, -1, fossil_free);
}
}
/*
** SQL function: display_name(X)
**
** If X is a string, search for a user name at the beginning of that
** string. The user name must be followed by an email address. If
** found, return the user name. If not found, return NULL.
**
** This routine is used to extract the display name from the USER.INFO
** field.
*/
void alert_display_name_func(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
const char *zIn = (const char*)sqlite3_value_text(argv[0]);
int i;
if( zIn==0 ) return;
while( fossil_isspace(zIn[0]) ) zIn++;
for(i=0; zIn[i] && zIn[i]!='<' && zIn[i]!='\n'; i++){}
if( zIn[i]=='<' ){
while( i>0 && fossil_isspace(zIn[i-1]) ){ i--; }
if( i>0 ){
sqlite3_result_text(context, zIn, i, SQLITE_TRANSIENT);
}
}
}
/*
** Return the hostname portion of an email address - the part following
** the @
*/
char *alert_hostname(const char *zAddr){
char *z = strchr(zAddr, '@');
|
| ︙ | ︙ | |||
760 761 762 763 764 765 766 | ** ** From: ** Date: ** Message-Id: ** Content-Type: ** Content-Transfer-Encoding: ** MIME-Version: | | | | | > < | | 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 |
**
** From:
** Date:
** Message-Id:
** Content-Type:
** Content-Transfer-Encoding:
** MIME-Version:
** Sender:
**
** The caller maintains ownership of the input Blobs. This routine will
** read the Blobs and send them onward to the email system, but it will
** not free them.
**
** The Message-Id: field is added if there is not already a Message-Id
** in the pHdr parameter.
**
** If the zFromName argument is not NULL, then it should be a human-readable
** name or handle for the sender. In that case, "From:" becomes a made-up
** email address based on a hash of zFromName and the domain of email-self,
** and an additional "Sender:" field is inserted with the email-self
** address. Downstream software might use the Sender header to set
** the envelope-from address of the email. If zFromName is a NULL pointer,
** then the "From:" is set to the email-self value and Sender is
** omitted.
*/
void alert_send(
AlertSender *p, /* Emailer context */
Blob *pHdr, /* Email header (incomplete) */
Blob *pBody, /* Email body */
const char *zFromName /* Optional human-readable name of sender */
){
Blob all, *pOut;
u64 r1, r2;
if( p->mFlags & ALERT_TRACE ){
fossil_print("Sending email\n");
}
if( fossil_strcmp(p->zDest, "off")==0 ){
return;
}
blob_init(&all, 0, 0);
if( fossil_strcmp(p->zDest, "blob")==0 ){
pOut = &p->out;
if( blob_size(pOut) ){
blob_appendf(pOut, "%.72c\n", '=');
}
}else{
pOut = &all;
}
blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr));
if( p->zFrom==0 || p->zFrom[0]==0 ){
return; /* email-self is not set. Error will be reported separately */
}else if( zFromName ){
blob_appendf(pOut, "From: %s <%s@%s>\r\n",
zFromName, alert_mailbox_name(zFromName), alert_hostname(p->zFrom));
blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom);
}else{
blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
}
blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));
if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){
/* Message-id format: "<$(date)x$(random)@$(from-host)>" where $(date) is
** the current unix-time in hex, $(random) is a 64-bit random number,
|
| ︙ | ︙ | |||
877 878 879 880 881 882 883 884 885 886 887 888 889 890 |
email_header_to_free(nTo, azTo);
blob_add_final_newline(&all);
fossil_print("%s", blob_str(&all));
}
blob_reset(&all);
}
/*
** SETTING: email-send-method width=5 default=off
** Determine the method used to send email. Allowed values are
** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value
** means no email is ever sent. The "relay" value means emails are sent
** to an Mail Sending Agent using SMTP located at email-send-relayhost.
** The "pipe" value means email messages are piped into a command
| > > > > > > > > > > > > > > > > | 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 |
email_header_to_free(nTo, azTo);
blob_add_final_newline(&all);
fossil_print("%s", blob_str(&all));
}
blob_reset(&all);
}
/*
** SETTING: email-url width=40
** This URL is used as the basename for hyperlinks included in email alert
** text. Omit the trailing "/".
*/
/*
** SETTING: email-admin width=40
** This is the email address for the human administrator for the system.
** Abuse and trouble reports and password reset requests are send here.
*/
/*
** SETTING: email-subname width=16
** This is a short name used to identifies the repository in the Subject:
** line of email alerts. Traditionally this name is included in square
** brackets. Examples: "[fossil-src]", "[sqlite-src]".
*/
/*
** SETTING: email-send-method width=5 default=off
** Determine the method used to send email. Allowed values are
** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value
** means no email is ever sent. The "relay" value means emails are sent
** to an Mail Sending Agent using SMTP located at email-send-relayhost.
** The "pipe" value means email messages are piped into a command
|
| ︙ | ︙ | |||
1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 |
){
const char *zEAddr;
int i, j, n;
char c;
*peErr = 0;
*pzErr = 0;
/* Check the validity of the email address.
**
** (1) Exactly one '@' character.
** (2) No other characters besides [a-zA-Z0-9._+-]
**
** The local part is currently more restrictive than RFC 5322 allows:
** https://stackoverflow.com/a/2049510/142454 We will expand this as
** necessary.
*/
zEAddr = P("e");
| > > > > > > > > > | > > > > | 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 |
){
const char *zEAddr;
int i, j, n;
char c;
*peErr = 0;
*pzErr = 0;
/* Verify the captcha first */
if( needCaptcha ){
if( !captcha_is_correct(1) ){
*peErr = 2;
*pzErr = mprintf("incorrect security code");
return 0;
}
}
/* Check the validity of the email address.
**
** (1) Exactly one '@' character.
** (2) No other characters besides [a-zA-Z0-9._+-]
**
** The local part is currently more restrictive than RFC 5322 allows:
** https://stackoverflow.com/a/2049510/142454 We will expand this as
** necessary.
*/
zEAddr = P("e");
if( zEAddr==0 ){
*peErr = 1;
*pzErr = mprintf("required");
return 0;
}
for(i=j=n=0; (c = zEAddr[i])!=0; i++){
if( c=='@' ){
n = i;
j++;
continue;
}
if( !fossil_isalnum(c) && c!='.' && c!='_' && c!='-' && c!='+' ){
|
| ︙ | ︙ | |||
1191 1192 1193 1194 1195 1196 1197 |
}
if( n>i-5 ){
*peErr = 1;
*pzErr = mprintf("email domain too short");
return 0;
}
| | < | | | 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 |
}
if( n>i-5 ){
*peErr = 1;
*pzErr = mprintf("email domain too short");
return 0;
}
if( authorized_subscription_email(zEAddr)==0 ){
*peErr = 1;
*pzErr = mprintf("not an authorized email address");
return 0;
}
/* Check to make sure the email address is available for reuse */
if( db_exists("SELECT 1 FROM subscriber WHERE semail=%Q", zEAddr) ){
*peErr = 1;
*pzErr = mprintf("this email address is used by someone else");
|
| ︙ | ︙ | |||
1257 1258 1259 1260 1261 1262 1263 |
**
** The Alerts permission ("7") is required to access this
** page. To allow anonymous passers-by to sign up for email
** notification, set Email-Alerts on user "nobody" or "anonymous".
*/
void subscribe_page(void){
int needCaptcha;
| | | 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 |
**
** The Alerts permission ("7") is required to access this
** page. To allow anonymous passers-by to sign up for email
** notification, set Email-Alerts on user "nobody" or "anonymous".
*/
void subscribe_page(void){
int needCaptcha;
unsigned int uSeed = 0;
const char *zDecoded;
char *zCaptcha = 0;
char *zErr = 0;
int eErr = 0;
int di;
if( alert_webpages_disabled() ) return;
|
| ︙ | ︙ | |||
1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 |
style_submenu_element("My Subscription","%R/alerts");
}else{
/* Everybody else jumps to the page to administer their own
** account only. */
cgi_redirectf("%R/alerts");
return;
}
}
alert_submenu_common();
needCaptcha = !login_is_individual();
if( P("submit")
&& cgi_csrf_safe(1)
&& subscribe_error_check(&eErr,&zErr,needCaptcha)
){
| > > > > | 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 |
style_submenu_element("My Subscription","%R/alerts");
}else{
/* Everybody else jumps to the page to administer their own
** account only. */
cgi_redirectf("%R/alerts");
return;
}
}
if( !g.perm.Admin && !db_get_boolean("anon-subscribe",1) ){
register_page();
return;
}
alert_submenu_common();
needCaptcha = !login_is_individual();
if( P("submit")
&& cgi_csrf_safe(1)
&& subscribe_error_check(&eErr,&zErr,needCaptcha)
){
|
| ︙ | ︙ | |||
1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 |
if( suname==0 && needCaptcha==0 && !g.perm.Admin ) suname = g.zLogin;
if( suname && suname[0]==0 ) suname = 0;
if( PB("sa") ) ssub[nsub++] = 'a';
if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c';
if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f';
if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't';
if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w';
ssub[nsub] = 0;
db_multi_exec(
"INSERT INTO subscriber(semail,suname,"
" sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)"
"VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)",
/* semail */ zEAddr,
/* suname */ suname,
/* sverified */ needCaptcha==0,
/* sdigest */ PB("di"),
/* ssub */ ssub,
/* smip */ g.zIpAddr
);
id = db_last_insert_rowid();
zCode = db_text(0,
"SELECT hex(subscriberCode) FROM subscriber WHERE subscriberId=%lld",
id);
if( !needCaptcha ){
/* The new subscription has been added on behalf of a logged-in user.
** No verification is required. Jump immediately to /alerts page.
*/
| > > | > > > | 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 |
if( suname==0 && needCaptcha==0 && !g.perm.Admin ) suname = g.zLogin;
if( suname && suname[0]==0 ) suname = 0;
if( PB("sa") ) ssub[nsub++] = 'a';
if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c';
if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f';
if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't';
if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w';
if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x';
ssub[nsub] = 0;
db_multi_exec(
"INSERT INTO subscriber(semail,suname,"
" sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)"
"VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)",
/* semail */ zEAddr,
/* suname */ suname,
/* sverified */ needCaptcha==0,
/* sdigest */ PB("di"),
/* ssub */ ssub,
/* smip */ g.zIpAddr
);
id = db_last_insert_rowid();
zCode = db_text(0,
"SELECT hex(subscriberCode) FROM subscriber WHERE subscriberId=%lld",
id);
if( !needCaptcha ){
/* The new subscription has been added on behalf of a logged-in user.
** No verification is required. Jump immediately to /alerts page.
*/
if( g.perm.Admin ){
cgi_redirectf("%R/alerts/%.32s", zCode);
}else{
cgi_redirectf("%R/alerts");
}
return;
}else{
/* We need to send a verification email */
Blob hdr, body;
AlertSender *pSender = alert_sender_new(0,0);
blob_init(&hdr,0,0);
blob_init(&body,0,0);
|
| ︙ | ︙ | |||
1352 1353 1354 1355 1356 1357 1358 |
@ <p>The following internal error was encountered while trying
@ to send the confirmation email:
@ <blockquote><pre>
@ %h(pSender->zErr)
@ </pre></blockquote>
}else{
@ <p>An email has been sent to "%h(zEAddr)". That email contains a
| | | 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 |
@ <p>The following internal error was encountered while trying
@ to send the confirmation email:
@ <blockquote><pre>
@ %h(pSender->zErr)
@ </pre></blockquote>
}else{
@ <p>An email has been sent to "%h(zEAddr)". That email contains a
@ hyperlink that you must click to activate your
@ subscription.</p>
}
alert_sender_free(pSender);
style_footer();
}
return;
}
|
| ︙ | ︙ | |||
1384 1385 1386 1387 1388 1389 1390 |
@ <td><input type="text" name="e" value="%h(PD("e",""))" size="30"></td>
@ <tr>
if( eErr==1 ){
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr>
}
@ </tr>
if( needCaptcha ){
| > > > > > | > | > | 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 |
@ <td><input type="text" name="e" value="%h(PD("e",""))" size="30"></td>
@ <tr>
if( eErr==1 ){
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr>
}
@ </tr>
if( needCaptcha ){
const char *zInit = "";
if( P("captchaseed")!=0 && eErr!=2 ){
uSeed = strtoul(P("captchaseed"),0,10);
zInit = P("captcha");
}else{
uSeed = captcha_seed();
}
zDecoded = captcha_decode(uSeed);
zCaptcha = captcha_render(zDecoded);
@ <tr>
@ <td class="form_label">Security Code:</td>
@ <td><input type="text" name="captcha" value="%h(zInit)" size="30">
captcha_speakit_button(uSeed, "Speak the code");
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
@ </tr>
if( eErr==2 ){
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr>
}
@ </tr>
}
|
| ︙ | ︙ | |||
1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 |
if( g.perm.Read ){
@ <label><input type="checkbox" name="sc" %s(PCK("sc"))> \
@ Check-ins</label><br>
}
if( g.perm.RdForum ){
@ <label><input type="checkbox" name="sf" %s(PCK("sf"))> \
@ Forum Posts</label><br>
}
if( g.perm.RdTkt ){
@ <label><input type="checkbox" name="st" %s(PCK("st"))> \
@ Ticket changes</label><br>
}
if( g.perm.RdWiki ){
@ <label><input type="checkbox" name="sw" %s(PCK("sw"))> \
| > > | 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 |
if( g.perm.Read ){
@ <label><input type="checkbox" name="sc" %s(PCK("sc"))> \
@ Check-ins</label><br>
}
if( g.perm.RdForum ){
@ <label><input type="checkbox" name="sf" %s(PCK("sf"))> \
@ Forum Posts</label><br>
@ <label><input type="checkbox" name="sx" %s(PCK("sx"))> \
@ Forum Edits</label><br>
}
if( g.perm.RdTkt ){
@ <label><input type="checkbox" name="st" %s(PCK("st"))> \
@ Ticket changes</label><br>
}
if( g.perm.RdWiki ){
@ <label><input type="checkbox" name="sw" %s(PCK("sw"))> \
|
| ︙ | ︙ | |||
1459 1460 1461 1462 1463 1464 1465 |
}
@ </tr>
@ </table>
if( needCaptcha ){
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
@ %h(zCaptcha)
@ </pre>
| | | | | < | | > > > > | > > > > | > > > > > > | | | | > | | | | | | | | > > > > | > > > > < > | | < < | > | < < | < | > | > > > > > > > > > > > > > > > > > > > > | | | | | > | < < | < < | > | < < < | > > > > > > > > | < > | < > | < < < < > | > | < > | | < < < > | > | > | > | > < > < > | < | | | > > > > > > < > | | > | > > > > > > > > > > > > | | | | > > > > > > > > | > > > > > > > > > > | > > > > > | > > > > > > > < < < < | | 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 |
}
@ </tr>
@ </table>
if( needCaptcha ){
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
@ %h(zCaptcha)
@ </pre>
@ Enter the 8 characters above in the "Security Code" box<br/>
@ </td></tr></table></div>
}
@ </form>
fossil_free(zErr);
style_footer();
}
/*
** Either shutdown or completely delete a subscription entry given
** by the hex value zName. Then paint a webpage that explains that
** the entry has been removed.
*/
static void alert_unsubscribe(int sid){
char *zEmail;
zEmail = db_text(0, "SELECT semail FROM subscriber"
" WHERE subscriberId=%d", sid);
if( zEmail==0 ){
style_header("Unsubscribe Fail");
@ <p>Unable to locate a subscriber with the requested key</p>
}else{
db_multi_exec(
"DELETE FROM subscriber WHERE subscriberId=%d", sid
);
style_header("Unsubscribed");
@ <p>The "%h(zEmail)" email address has been delisted.
@ All traces of that email address have been removed</p>
}
style_footer();
return;
}
/*
** WEBPAGE: alerts
**
** Edit email alert and notification settings.
**
** The subscriber is identified in several ways:
**
** (1) The name= query parameter contains the complete subscriberCode.
** This only happens when the user receives a verification
** email and clicks on the link in the email. When a
** compilete subscriberCode is seen on the name= query parameter,
** that constitutes verification of the email address.
**
** (2) The sid= query parameter contains an integer subscriberId.
** This only works for the administrator. It allows the
** administrator to edit any subscription.
**
** (3) The user is logged into an account other than "nobody" or
** "anonymous". In that case the notification settings
** associated with that account can be edited without needing
** to know the subscriber code.
**
** (4) The name= query parameter contains a 32-digit prefix of
** subscriber code. (Subscriber codes are normally 64 hex digits
** in length.) This uniquely identifies the subscriber without
** revealing the complete subscriber code, and hence without
** verifying the email address.
*/
void alert_page(void){
const char *zName = 0; /* Value of the name= query parameter */
Stmt q; /* For querying the database */
int sa, sc, sf, st, sw, sx; /* Types of notifications requested */
int sdigest = 0, sdonotcall = 0, sverified = 0; /* Other fields */
int isLogin; /* True if logged in as an individual */
const char *ssub = 0; /* Subscription flags */
const char *semail = 0; /* Email address */
const char *smip; /* */
const char *suname = 0; /* Corresponding user.login value */
const char *mtime; /* */
const char *sctime; /* Time subscription created */
int eErr = 0; /* Type of error */
char *zErr = 0; /* Error message text */
int sid = 0; /* Subscriber ID */
int nName; /* Length of zName in bytes */
char *zHalfCode; /* prefix of subscriberCode */
db_begin_transaction();
if( alert_webpages_disabled() ){
db_commit_transaction();
return;
}
login_check_credentials();
if( !g.perm.EmailAlert ){
db_commit_transaction();
login_needed(g.anon.EmailAlert);
/*NOTREACHED*/
}
isLogin = login_is_individual();
zName = P("name");
nName = zName ? (int)strlen(zName) : 0;
if( g.perm.Admin && P("sid")!=0 ){
sid = atoi(P("sid"));
}
if( sid==0 && nName>=32 ){
sid = db_int(0,
"SELECT CASE WHEN hex(subscriberCode) LIKE (%Q||'%%')"
" THEN subscriberId ELSE 0 END"
" FROM subscriber WHERE subscriberCode>=hextoblob(%Q)"
" LIMIT 1", zName, zName);
}
if( sid==0 && isLogin ){
sid = db_int(0, "SELECT subscriberId FROM subscriber"
" WHERE suname=%Q", g.zLogin);
}
if( sid==0 ){
db_commit_transaction();
cgi_redirect("subscribe");
/*NOTREACHED*/
}
alert_submenu_common();
if( P("submit")!=0 && cgi_csrf_safe(1) ){
char newSsub[10];
int nsub = 0;
Blob update;
sdonotcall = PB("sdonotcall");
sdigest = PB("sdigest");
semail = P("semail");
if( PB("sa") ) newSsub[nsub++] = 'a';
if( g.perm.Read && PB("sc") ) newSsub[nsub++] = 'c';
if( g.perm.RdForum && PB("sf") ) newSsub[nsub++] = 'f';
if( g.perm.RdTkt && PB("st") ) newSsub[nsub++] = 't';
if( g.perm.RdWiki && PB("sw") ) newSsub[nsub++] = 'w';
if( g.perm.RdForum && PB("sx") ) newSsub[nsub++] = 'x';
newSsub[nsub] = 0;
ssub = newSsub;
blob_init(&update, "UPDATE subscriber SET", -1);
blob_append_sql(&update,
" sdonotcall=%d,"
" sdigest=%d,"
" ssub=%Q,"
" mtime=strftime('%%s','now'),"
" smip=%Q",
sdonotcall,
sdigest,
ssub,
g.zIpAddr
);
if( g.perm.Admin ){
suname = PT("suname");
sverified = PB("sverified");
if( suname && suname[0]==0 ) suname = 0;
blob_append_sql(&update,
", suname=%Q,"
" sverified=%d",
suname,
sverified
);
}
if( isLogin ){
if( semail==0 || email_address_is_valid(semail,0)==0 ){
eErr = 8;
}
blob_append_sql(&update, ", semail=%Q", semail);
}
blob_append_sql(&update," WHERE subscriberId=%d", sid);
if( eErr==0 ){
db_exec_sql(blob_str(&update));
ssub = 0;
}
blob_reset(&update);
}
if( P("delete")!=0 && cgi_csrf_safe(1) ){
if( !PB("dodelete") ){
eErr = 9;
zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to"
" unsubscribe");
}else{
alert_unsubscribe(sid);
db_commit_transaction();
return;
}
}
style_header("Update Subscription");
db_prepare(&q,
"SELECT"
" semail," /* 0 */
" sverified," /* 1 */
" sdonotcall," /* 2 */
" sdigest," /* 3 */
" ssub," /* 4 */
" smip," /* 5 */
" suname," /* 6 */
" datetime(mtime,'unixepoch')," /* 7 */
" datetime(sctime,'unixepoch')," /* 8 */
" hex(subscriberCode)" /* 9 */
" FROM subscriber WHERE subscriberId=%d", sid);
if( db_step(&q)!=SQLITE_ROW ){
db_finalize(&q);
db_commit_transaction();
cgi_redirect("subscribe");
/*NOTREACHED*/
}
if( ssub==0 ){
semail = db_column_text(&q, 0);
sdonotcall = db_column_int(&q, 2);
sdigest = db_column_int(&q, 3);
ssub = db_column_text(&q, 4);
}
if( suname==0 ){
suname = db_column_text(&q, 6);
sverified = db_column_int(&q, 1);
}
sa = strchr(ssub,'a')!=0;
sc = strchr(ssub,'c')!=0;
sf = strchr(ssub,'f')!=0;
st = strchr(ssub,'t')!=0;
sw = strchr(ssub,'w')!=0;
sx = strchr(ssub,'x')!=0;
smip = db_column_text(&q, 5);
mtime = db_column_text(&q, 7);
sctime = db_column_text(&q, 8);
if( !g.perm.Admin && !sverified ){
if( nName==64 ){
db_multi_exec(
"UPDATE subscriber SET sverified=1"
" WHERE subscriberCode=hextoblob(%Q)",
zName);
if( db_get_boolean("selfreg-verify",0) ){
char *zNewCap = db_get("default-perms","u");
db_multi_exec(
"UPDATE user"
" SET cap=%Q"
" WHERE cap='7' AND login=("
" SELECT suname FROM subscriber"
" WHERE subscriberCode=hextoblob(%Q))",
zNewCap, zName
);
login_set_capabilities(zNewCap, 0);
}
@ <h1>Your email alert subscription has been verified!</h1>
@ <p>Use the form below to update your subscription information.</p>
@ <p>Hint: Bookmark this page so that you can more easily update
@ your subscription information in the future</p>
}else{
@ <h2>Your email address is unverified</h2>
@ <p>You should have received an email message containing a link
@ that you must visit to verify your account. No email notifications
@ will be sent until your email address has been verified.</p>
}
}else{
@ <p>Make changes to the email subscription shown below and
@ press "Submit".</p>
}
form_begin(0, "%R/alerts");
zHalfCode = db_text("x","SELECT hex(substr(subscriberCode,1,16))"
" FROM subscriber WHERE subscriberId=%d", sid);
@ <input type="hidden" name="name" value="%h(zHalfCode)">
@ <table class="subscribe">
@ <tr>
@ <td class="form_label">Email Address:</td>
if( isLogin ){
@ <td><input type="text" name="semail" value="%h(semail)" size="30">\
if( eErr==8 ){
@ <span class='loginError'>← not a valid email address!</span>
}else if( g.perm.Admin ){
@ <a href="%R/announce?to=%t(semail)">\
@ (Send a message to %h(semail))</a>\
}
@ </td>
}else{
@ <td>%h(semail)</td>
}
@ </tr>
if( g.perm.Admin ){
int uid;
@ <tr>
@ <td class='form_label'>Created:</td>
@ <td>%h(sctime)</td>
@ </tr>
@ <tr>
@ <td class='form_label'>Last Modified:</td>
@ <td>%h(mtime)</td>
@ </tr>
@ <tr>
@ <td class='form_label'>IP Address:</td>
@ <td>%h(smip)</td>
@ </tr>
@ <tr>
@ <td class='form_label'>Subscriber Code:</td>
@ <td>%h(db_column_text(&q,9))</td>
@ <tr>
@ <td class="form_label">User:</td>
@ <td><input type="text" name="suname" value="%h(suname?suname:"")" \
@ size="30">\
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", suname);
if( uid ){
@ <a href='%R/setup_uedit?id=%d(uid)'>\
@ (login info for %h(suname))</a>\
}
@ </tr>
}
@ <tr>
@ <td class="form_label">Topics:</td>
@ <td><label><input type="checkbox" name="sa" %s(sa?"checked":"")>\
@ Announcements</label><br>
if( g.perm.Read ){
@ <label><input type="checkbox" name="sc" %s(sc?"checked":"")>\
@ Check-ins</label><br>
}
if( g.perm.RdForum ){
@ <label><input type="checkbox" name="sf" %s(sf?"checked":"")>\
@ Forum Posts</label><br>
@ <label><input type="checkbox" name="sx" %s(sx?"checked":"")>\
@ Forum Edits</label><br>
}
if( g.perm.RdTkt ){
@ <label><input type="checkbox" name="st" %s(st?"checked":"")>\
@ Ticket changes</label><br>
}
if( g.perm.RdWiki ){
@ <label><input type="checkbox" name="sw" %s(sw?"checked":"")>\
@ Wiki</label>
}
@ </td></tr>
@ <tr>
@ <td class="form_label">Delivery:</td>
@ <td><select size="1" name="sdigest">
@ <option value="0" %s(sdigest?"":"selected")>Individual Emails</option>
@ <option value="1" %s(sdigest?"selected":"")>Daily Digest</option>
@ </select></td>
@ </tr>
if( g.perm.Admin ){
@ <tr>
@ <td class="form_label">Admin Options:</td><td>
@ <label><input type="checkbox" name="sdonotcall" \
@ %s(sdonotcall?"checked":"")> Do not disturb</label><br>
@ <label><input type="checkbox" name="sverified" \
@ %s(sverified?"checked":"")>\
@ Verified</label></td></tr>
}
if( eErr==9 ){
@ <tr>
@ <td class="form_label">Verify:</td><td>
|
| ︙ | ︙ | |||
1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 | @ <input type="submit" name="delete" value="Unsubscribe"> @ </tr> @ </table> @ </form> fossil_free(zErr); db_finalize(&q); style_footer(); } /* This is the message that gets sent to describe how to change ** or modify a subscription */ static const char zUnsubMsg[] = @ To changes your subscription settings at %s visit this link: | > > | 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 | @ <input type="submit" name="delete" value="Unsubscribe"> @ </tr> @ </table> @ </form> fossil_free(zErr); db_finalize(&q); style_footer(); db_commit_transaction(); return; } /* This is the message that gets sent to describe how to change ** or modify a subscription */ static const char zUnsubMsg[] = @ To changes your subscription settings at %s visit this link: |
| ︙ | ︙ | |||
1758 1759 1760 1761 1762 1763 1764 |
** an email address to which will be sent the unsubscribe link that
** contains the correct subscriber code.
*/
void unsubscribe_page(void){
const char *zName = P("name");
char *zErr = 0;
int eErr = 0;
| | > > | < | | 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 |
** an email address to which will be sent the unsubscribe link that
** contains the correct subscriber code.
*/
void unsubscribe_page(void){
const char *zName = P("name");
char *zErr = 0;
int eErr = 0;
unsigned int uSeed = 0;
const char *zDecoded;
char *zCaptcha = 0;
int dx;
int bSubmit;
const char *zEAddr;
char *zCode = 0;
int sid = 0;
/* If a valid subscriber code is supplied, then unsubscribe immediately.
*/
if( zName
&& (sid = db_int(0, "SELECT subscriberId FROM subscriber"
" WHERE subscriberCode=hextoblob(%Q)", zName))!=0
){
alert_unsubscribe(sid);
return;
}
/* Logged in users are redirected to the /alerts page */
login_check_credentials();
if( login_is_individual() ){
cgi_redirectf("%R/alerts");
|
| ︙ | ︙ | |||
1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 |
@ </tr>
uSeed = captcha_seed();
zDecoded = captcha_decode(uSeed);
zCaptcha = captcha_render(zDecoded);
@ <tr>
@ <td class="form_label">Security Code:</td>
@ <td><input type="text" name="captcha" value="" size="30">
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
if( eErr==2 ){
@ <td><span class="loginError">← %h(zErr)</span></td>
}
@ </tr>
@ <tr>
@ <td class="form_label">Options:</td>
@ <td><label><input type="radio" name="dx" value="0" %s(dx?"":"checked")>\
@ Modify subscription</label><br>
@ <label><input type="radio" name="dx" value="1" %s(dx?"checked":"")>\
@ Completely unsubscribe</label><br>
@ <tr>
@ <td></td>
@ <td><input type="submit" name="submit" value="Submit"></td>
@ </tr>
@ </table>
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
@ %h(zCaptcha)
@ </pre>
| > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > | 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 |
@ </tr>
uSeed = captcha_seed();
zDecoded = captcha_decode(uSeed);
zCaptcha = captcha_render(zDecoded);
@ <tr>
@ <td class="form_label">Security Code:</td>
@ <td><input type="text" name="captcha" value="" size="30">
captcha_speakit_button(uSeed, "Speak the code");
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
if( eErr==2 ){
@ <td><span class="loginError">← %h(zErr)</span></td>
}
@ </tr>
@ <tr>
@ <td class="form_label">Options:</td>
@ <td><label><input type="radio" name="dx" value="0" %s(dx?"":"checked")>\
@ Modify subscription</label><br>
@ <label><input type="radio" name="dx" value="1" %s(dx?"checked":"")>\
@ Completely unsubscribe</label><br>
@ <tr>
@ <td></td>
@ <td><input type="submit" name="submit" value="Submit"></td>
@ </tr>
@ </table>
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
@ %h(zCaptcha)
@ </pre>
@ Enter the 8 characters above in the "Security Code" box<br/>
@ </td></tr></table></div>
@ </form>
fossil_free(zErr);
style_footer();
}
/*
** WEBPAGE: subscribers
**
** This page, accessible to administrators only,
** shows a list of subscriber email addresses.
** Clicking on an email takes one to the /alerts page
** for that email where the delivery settings can be
** modified.
*/
void subscriber_list_page(void){
Blob sql;
Stmt q;
sqlite3_int64 iNow;
int nTotal;
int nPending;
int nDel = 0;
if( alert_webpages_disabled() ) return;
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
alert_submenu_common();
style_submenu_element("Users","setup_ulist");
style_header("Subscriber List");
nTotal = db_int(0, "SELECT count(*) FROM subscriber");
nPending = db_int(0, "SELECT count(*) FROM subscriber WHERE NOT sverified");
if( nPending>0 && P("purge") && cgi_csrf_safe(0) ){
int nNewPending;
db_multi_exec(
"DELETE FROM subscriber"
" WHERE NOT sverified AND mtime<0+strftime('%%s','now','-1 day')"
);
nNewPending = db_int(0, "SELECT count(*) FROM subscriber"
" WHERE NOT sverified");
nDel = nPending - nNewPending;
nPending = nNewPending;
nTotal -= nDel;
}
if( nPending>0 ){
@ <h1>%,d(nTotal) Subscribers, %,d(nPending) Pending</h1>
if( nDel==0 && 0<db_int(0,"SELECT count(*) FROM subscriber"
" WHERE NOT sverified AND mtime<0+strftime('%%s','now','-1 day')")
){
style_submenu_element("Purge Pending","subscribers?purge");
}
}else{
@ <h1>%,d(nTotal) Subscribers</h1>
}
if( nDel>0 ){
@ <p>*** %d(nDel) pending subscriptions deleted ***</p>
}
blob_init(&sql, 0, 0);
blob_append_sql(&sql,
"SELECT subscriberId," /* 0 */
" semail," /* 1 */
" ssub," /* 2 */
" suname," /* 3 */
" sverified," /* 4 */
" sdigest," /* 5 */
" mtime," /* 6 */
" date(sctime,'unixepoch')," /* 7 */
" (SELECT uid FROM user WHERE login=subscriber.suname)" /* 8 */
" FROM subscriber"
);
if( P("only")!=0 ){
blob_append_sql(&sql, " WHERE ssub LIKE '%%%q%%'", P("only"));
style_submenu_element("Show All","%R/subscribers");
}
blob_append_sql(&sql," ORDER BY mtime DESC");
|
| ︙ | ︙ | |||
1935 1936 1937 1938 1939 1940 1941 1942 |
@ <th>Last change
@ <th>Created
@ </tr>
@ </thead><tbody>
while( db_step(&q)==SQLITE_ROW ){
sqlite3_int64 iMtime = db_column_int64(&q, 6);
double rAge = (iNow - iMtime)/86400.0;
@ <tr>
| > > | > > > | > > > > > > > > > | | 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 |
@ <th>Last change
@ <th>Created
@ </tr>
@ </thead><tbody>
while( db_step(&q)==SQLITE_ROW ){
sqlite3_int64 iMtime = db_column_int64(&q, 6);
double rAge = (iNow - iMtime)/86400.0;
int uid = db_column_int(&q, 8);
const char *zUname = db_column_text(&q, 3);
@ <tr>
@ <td><a href='%R/alerts?sid=%d(db_column_int(&q,0))'>\
@ %h(db_column_text(&q,1))</a></td>
@ <td>%h(db_column_text(&q,2))</td>
@ <td>%s(db_column_int(&q,5)?"digest":"")</td>
if( uid ){
@ <td><a href='%R/setup_uedit?id=%d(uid)'>%h(zUname)</a>
}else{
@ <td>%h(zUname)</td>
}
@ <td>%s(db_column_int(&q,4)?"yes":"pending")</td>
@ <td data-sortkey='%010llx(iMtime)'>%z(human_readable_age(rAge))</td>
@ <td>%h(db_column_text(&q,7))</td>
@ </tr>
}
@ </tbody></table>
db_finalize(&q);
style_table_sorter();
style_footer();
}
#if LOCAL_INTERFACE
/*
** A single event that might appear in an alert is recorded as an
** instance of the following object.
**
** type values:
**
** c A new check-in
** f An original forum post
** x An edit to a prior forum post
** t A new ticket or a change to an existing ticket
** w A change to a wiki page
*/
struct EmailEvent {
int type; /* 'c', 'f', 't', 'w', 'x' */
int needMod; /* Pending moderator approval */
Blob hdr; /* Header content, for forum entries */
Blob txt; /* Text description to appear in an alert */
char *zFromName; /* Human name of the sender */
EmailEvent *pNext; /* Next in chronological order */
};
#endif
|
| ︙ | ︙ | |||
2001 2002 2003 2004 2005 2006 2007 |
const char *zFrom;
const char *zSub;
/* First do non-forum post events */
db_prepare(&q,
"SELECT"
| > > > | | 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 |
const char *zFrom;
const char *zSub;
/* First do non-forum post events */
db_prepare(&q,
"SELECT"
" CASE WHEN event.type='t'"
" THEN (SELECT substr(tagname,5) FROM tag"
" WHERE tagid=event.tagid AND tagname LIKE 'tkt-%%')"
" ELSE blob.uuid END," /* 0 */
" datetime(event.mtime)," /* 1 */
" coalesce(ecomment,comment)"
" || ' (user: ' || coalesce(euser,user,'?')"
" || (SELECT case when length(x)>0 then ' tags: ' || x else '' end"
" FROM (SELECT group_concat(substr(tagname,5), ', ') AS x"
" FROM tag, tagxref"
" WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
|
| ︙ | ︙ | |||
2034 2035 2036 2037 2038 2039 2040 |
pLast = p;
p->type = db_column_text(&q, 3)[0];
p->needMod = db_column_int(&q, 4);
p->zFromName = 0;
p->pNext = 0;
switch( p->type ){
case 'c': zType = "Check-In"; break;
| | | | 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 |
pLast = p;
p->type = db_column_text(&q, 3)[0];
p->needMod = db_column_int(&q, 4);
p->zFromName = 0;
p->pNext = 0;
switch( p->type ){
case 'c': zType = "Check-In"; break;
/* case 'f': -- forum posts omitted from this loop. See below */
case 't': zType = "Ticket Change"; break;
case 'w': zType = "Wiki Edit"; break;
}
blob_init(&p->hdr, 0, 0);
blob_init(&p->txt, 0, 0);
blob_appendf(&p->txt,"== %s %s ==\n%s\n%s/info/%.20s\n",
db_column_text(&q,1),
zType,
db_column_text(&q, 2),
zUrl,
db_column_text(&q,0)
);
if( p->needMod ){
blob_appendf(&p->txt,
"** Pending moderator approval (%s/modreq) **\n",
zUrl
|
| ︙ | ︙ | |||
2073 2074 2075 2076 2077 2078 2079 |
/* If we reach this point, it means that forumposts exist and this
** is a normal email alert. Construct full-text forum post alerts
** using a format that enables them to be sent as separate emails.
*/
db_prepare(&q,
"SELECT"
| | | | | > > > > > > > > | > > | | > | | 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 |
/* If we reach this point, it means that forumposts exist and this
** is a normal email alert. Construct full-text forum post alerts
** using a format that enables them to be sent as separate emails.
*/
db_prepare(&q,
"SELECT"
" forumpost.fpid," /* 0: fpid */
" (SELECT uuid FROM blob WHERE rid=forumpost.fpid)," /* 1: hash */
" datetime(event.mtime)," /* 2: date/time */
" substr(comment,instr(comment,':')+2)," /* 3: comment */
" (WITH thread(fpid,fprev) AS ("
" SELECT fpid,fprev FROM forumpost AS tx"
" WHERE tx.froot=forumpost.froot),"
" basepid(fpid,bpid) AS ("
" SELECT fpid, fpid FROM thread WHERE fprev IS NULL"
" UNION ALL"
" SELECT thread.fpid, basepid.bpid FROM basepid, thread"
" WHERE basepid.fpid=thread.fprev)"
" SELECT uuid FROM blob, basepid"
" WHERE basepid.fpid=forumpost.firt"
" AND blob.rid=basepid.bpid)," /* 4: in-reply-to */
" wantalert.needMod," /* 5: moderated */
" coalesce(display_name(info),euser,user)," /* 6: user */
" forumpost.fprev IS NULL" /* 7: is an edit */
" FROM temp.wantalert, event, forumpost"
" LEFT JOIN user ON (login=coalesce(euser,user))"
" WHERE event.objid=substr(wantalert.eventId,2)+0"
" AND eventId GLOB 'f*'"
" AND forumpost.fpid=event.objid"
" ORDER BY event.mtime"
);
zFrom = db_get("email-self",0);
zSub = db_get("email-subname","");
while( db_step(&q)==SQLITE_ROW ){
Manifest *pPost = manifest_get(db_column_int(&q,0), CFTYPE_FORUM, 0);
const char *zIrt;
const char *zUuid;
const char *zTitle;
const char *z;
if( pPost==0 ) continue;
p = fossil_malloc( sizeof(EmailEvent) );
pLast->pNext = p;
pLast = p;
p->type = db_column_int(&q,7) ? 'f' : 'x';
p->needMod = db_column_int(&q, 5);
z = db_column_text(&q,6);
p->zFromName = z && z[0] ? fossil_strdup(z) : 0;
p->pNext = 0;
blob_init(&p->hdr, 0, 0);
zUuid = db_column_text(&q, 1);
zTitle = db_column_text(&q, 3);
|
| ︙ | ︙ | |||
2431 2432 2433 2434 2435 2436 2437 |
if( p->needMod ){
/* For events that require moderator approval, only send an alert
** if the recipient is a moderator for that type of event. Setup
** and Admin users always get notified. */
char xType = '*';
if( strpbrk(zCap,"as")==0 ){
switch( p->type ){
| | | | | | | | | 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 |
if( p->needMod ){
/* For events that require moderator approval, only send an alert
** if the recipient is a moderator for that type of event. Setup
** and Admin users always get notified. */
char xType = '*';
if( strpbrk(zCap,"as")==0 ){
switch( p->type ){
case 'x': case 'f': xType = '5'; break;
case 't': xType = 'q'; break;
case 'w': xType = 'l'; break;
}
if( strchr(zCap,xType)==0 ) continue;
}
}else if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){
/* Setup and admin users can get any notification that does not
** require moderation */
}else{
/* Other users only see the alert if they have sufficient
** privilege to view the event itself */
char xType = '*';
switch( p->type ){
case 'c': xType = 'o'; break;
case 'x': case 'f': xType = '2'; break;
case 't': xType = 'r'; break;
case 'w': xType = 'j'; break;
}
if( strchr(zCap,xType)==0 ) continue;
}
if( blob_size(&p->hdr)>0 ){
/* This alert should be sent as a separate email */
Blob fhdr, fbody;
blob_init(&fhdr, 0, 0);
|
| ︙ | ︙ | |||
2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 |
form_begin(0, "%R/contact_admin");
@ <p>Enter a message to the repository administrator below:</p>
@ <table class="subscribe">
if( zCaptcha ){
@ <tr>
@ <td class="form_label">Security Code:</td>
@ <td><input type="text" name="captcha" value="" size="10">
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
@ </tr>
}
@ <tr>
@ <td class="form_label">Your Email Address:</td>
@ <td><input type="text" name="from" value="%h(PT("from"))" size="30"></td>
@ </tr>
| > | 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 |
form_begin(0, "%R/contact_admin");
@ <p>Enter a message to the repository administrator below:</p>
@ <table class="subscribe">
if( zCaptcha ){
@ <tr>
@ <td class="form_label">Security Code:</td>
@ <td><input type="text" name="captcha" value="" size="10">
captcha_speakit_button(uSeed, "Speak the code");
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
@ </tr>
}
@ <tr>
@ <td class="form_label">Your Email Address:</td>
@ <td><input type="text" name="from" value="%h(PT("from"))" size="30"></td>
@ </tr>
|
| ︙ | ︙ | |||
2615 2616 2617 2618 2619 2620 2621 |
@ <td><input type="submit" name="submit" value="Send Message">
@ </tr>
@ </table>
if( zCaptcha ){
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
@ %h(zCaptcha)
@ </pre>
| | > | > | | > > > | > > > > > > > > | 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 |
@ <td><input type="submit" name="submit" value="Send Message">
@ </tr>
@ </table>
if( zCaptcha ){
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
@ %h(zCaptcha)
@ </pre>
@ Enter the 8 characters above in the "Security Code" box<br/>
@ </td></tr></table></div>
}
@ </form>
style_footer();
}
/*
** Send an annoucement message described by query parameter.
** Permission to do this has already been verified.
*/
static char *alert_send_announcement(void){
AlertSender *pSender;
char *zErr;
const char *zTo = PT("to");
char *zSubject = PT("subject");
int bAll = PB("all");
int bAA = PB("aa");
int bMods = PB("mods");
const char *zSub = db_get("email-subname", "[Fossil Repo]");
int bTest2 = fossil_strcmp(P("name"),"test2")==0;
Blob hdr, body;
blob_init(&body, 0, 0);
blob_init(&hdr, 0, 0);
blob_appendf(&body, "%s", PT("msg")/*safe-for-%s*/);
pSender = alert_sender_new(bTest2 ? "blob" : 0, 0);
if( zTo[0] ){
blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject);
alert_send(pSender, &hdr, &body, 0);
}
if( bAll || bAA || bMods ){
Stmt q;
int nUsed = blob_size(&body);
const char *zURL = db_get("email-url",0);
if( bAll ){
db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber "
" WHERE sverified AND NOT sdonotcall");
}else if( bAA ){
db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber "
" WHERE sverified AND NOT sdonotcall"
" AND ssub LIKE '%%a%%'");
}else if( bMods ){
db_prepare(&q,
"SELECT semail, hex(subscriberCode)"
" FROM subscriber, user "
" WHERE sverified AND NOT sdonotcall"
" AND suname=login"
" AND fullcap(cap) GLOB '*5*'");
}
while( db_step(&q)==SQLITE_ROW ){
const char *zCode = db_column_text(&q, 1);
zTo = db_column_text(&q, 0);
blob_truncate(&hdr, 0);
blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject);
if( zURL ){
blob_truncate(&body, nUsed);
|
| ︙ | ︙ | |||
2711 2712 2713 2714 2715 2716 2717 |
if( zErr ){
@ <h1>Internal Error</h1>
@ <p>The following error was reported by the system:
@ <blockquote><pre>
@ %h(zErr)
@ </pre></blockquote>
}else{
| | > > > | > > > > > > | > | 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 |
if( zErr ){
@ <h1>Internal Error</h1>
@ <p>The following error was reported by the system:
@ <blockquote><pre>
@ %h(zErr)
@ </pre></blockquote>
}else{
@ <p>The announcement has been sent.
@ <a href="%h(PD("REQUEST_URI","/"))">Send another</a></p>
}
style_footer();
return;
} else if( !alert_enabled() ){
style_header("Cannot Send Announcement");
@ <p>Either you have no subscribers yet, or email alerts are not yet
@ <a href="https://fossil-scm.org/fossil/doc/trunk/www/alerts.md">set up</a>
@ for this repository.</p>
return;
}
style_header("Send Announcement");
@ <form method="POST">
@ <table class="subscribe">
if( g.perm.Admin ){
int aa = PB("aa");
int all = PB("all");
int aMod = PB("mods");
const char *aack = aa ? "checked" : "";
const char *allck = all ? "checked" : "";
const char *modck = aMod ? "checked" : "";
@ <tr>
@ <td class="form_label">To:</td>
@ <td><input type="text" name="to" value="%h(PT("to"))" size="30"><br>
@ <label><input type="checkbox" name="aa" %s(aack)> \
@ All "announcement" subscribers</label> \
@ <a href="%R/subscribers?only=a" target="_blank">(list)</a><br>
@ <label><input type="checkbox" name="all" %s(allck)> \
@ All subscribers</label> \
@ <a href="%R/subscribers" target="_blank">(list)</a><br>
@ <label><input type="checkbox" name="mods" %s(modck)> \
@ All moderators</label> \
@ <a href="%R/setup_ulist?with=5" target="_blank">(list)</a><br></td>
@ </tr>
}
@ <tr>
@ <td class="form_label">Subject:</td>
@ <td><input type="text" name="subject" value="%h(PT("subject"))"\
@ size="80"></td>
@ </tr>
@ <tr>
@ <td class="form_label">Message:</td>
@ <td><textarea name="msg" cols="80" rows="10" wrap="virtual">\
@ %h(PT("msg"))</textarea>
@ </tr>
@ <tr>
@ <td></td>
if( fossil_strcmp(P("name"),"test2")==0 ){
@ <td><input type="submit" name="submit" value="Dry Run">
}else{
@ <td><input type="submit" name="submit" value="Send Message">
}
@ </tr>
@ </table>
@ </form>
style_footer();
}
|
| ︙ | ︙ | |||
17 18 19 20 21 22 23 | ** ** This file contains code to implement the "all" command-line method. */ #include "config.h" #include "allrepo.h" #include <assert.h> | < < < < < < < < < < < < < < < < < < < < < < < < < < | | | 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 |
**
** This file contains code to implement the "all" command-line method.
*/
#include "config.h"
#include "allrepo.h"
#include <assert.h>
/*
** Build a string that contains all of the command-line options
** specified as arguments. If the option name begins with "+" then
** it takes an argument. Without the "+" it does not.
*/
static void collect_argument(Blob *pExtra,const char *zArg,const char *zShort){
const char *z = find_option(zArg, zShort, 0);
if( z!=0 ){
blob_appendf(pExtra, " %s", z);
}
}
static void collect_argument_value(Blob *pExtra, const char *zArg){
const char *zValue = find_option(zArg, 0, 1);
if( zValue ){
if( zValue[0] ){
blob_appendf(pExtra, " --%s %$", zArg, zValue);
}else{
blob_appendf(pExtra, " --%s \"\"", zArg);
}
}
}
static void collect_argv(Blob *pExtra, int iStart){
int i;
|
| ︙ | ︙ | |||
126 127 128 129 130 131 132 | ** ** rebuild Rebuild on all repositories. The command line options ** supported by the rebuild command itself, if any are ** present, are passed along verbatim. The --force and ** --randomize options are not supported. ** ** sync Run a "sync" on all repositories. Only the --verbose | | | | | | 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | ** ** rebuild Rebuild on all repositories. The command line options ** supported by the rebuild command itself, if any are ** present, are passed along verbatim. The --force and ** --randomize options are not supported. ** ** sync Run a "sync" on all repositories. Only the --verbose ** and --unversioned options are supported. ** ** set|unset Run the "setting", "set", or "unset" commands on all ** repositories. These command are particularly useful in ** conjunction with the "max-loadavg" setting which cannot ** otherwise be set globally. ** ** server Run the "ui" or "server" commands on all repositories. ** ui The root URI gives a listing of all repos. ** ** ** In addition, the following maintenance operations are supported: |
| ︙ | ︙ | |||
167 168 169 170 171 172 173 |
** --dry-run If given, display instead of run actions.
*/
void all_cmd(void){
int n;
Stmt q;
const char *zCmd;
char *zSyscmd;
| < < | 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
** --dry-run If given, display instead of run actions.
*/
void all_cmd(void){
int n;
Stmt q;
const char *zCmd;
char *zSyscmd;
Blob extra;
int useCheckouts = 0;
int quiet = 0;
int dryRunFlag = 0;
int showFile = find_option("showfile",0,0)!=0;
int stopOnError = find_option("dontstop",0,0)==0;
int nToDel = 0;
|
| ︙ | ︙ | |||
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
zCmd = "fts-config -R";
collect_argv(&extra, 3);
}else if( strncmp(zCmd, "sync", n)==0 ){
zCmd = "sync -autourl -R";
collect_argument(&extra, "verbose","v");
collect_argument(&extra, "unversioned","u");
}else if( strncmp(zCmd, "test-integrity", n)==0 ){
collect_argument(&extra, "parse", 0);
zCmd = "test-integrity";
}else if( strncmp(zCmd, "test-orphans", n)==0 ){
zCmd = "test-orphans -R";
}else if( strncmp(zCmd, "test-missing", n)==0 ){
zCmd = "test-missing -q -R";
collect_argument(&extra, "notshunned",0);
}else if( strncmp(zCmd, "changes", n)==0 ){
| > > | 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
zCmd = "fts-config -R";
collect_argv(&extra, 3);
}else if( strncmp(zCmd, "sync", n)==0 ){
zCmd = "sync -autourl -R";
collect_argument(&extra, "verbose","v");
collect_argument(&extra, "unversioned","u");
}else if( strncmp(zCmd, "test-integrity", n)==0 ){
collect_argument(&extra, "db-only", "d");
collect_argument(&extra, "parse", 0);
collect_argument(&extra, "quick", "q");
zCmd = "test-integrity";
}else if( strncmp(zCmd, "test-orphans", n)==0 ){
zCmd = "test-orphans -R";
}else if( strncmp(zCmd, "test-missing", n)==0 ){
zCmd = "test-missing -q -R";
collect_argument(&extra, "notshunned",0);
}else if( strncmp(zCmd, "changes", n)==0 ){
|
| ︙ | ︙ | |||
366 367 368 369 370 371 372 |
collect_argv(&extra, 3);
}else{
fossil_fatal("\"all\" subcommand should be one of: "
"add cache changes clean dbstat extras fts-config ignore "
"info list ls pull push rebuild server setting sync ui unset");
}
verify_all_options();
| < | 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
collect_argv(&extra, 3);
}else{
fossil_fatal("\"all\" subcommand should be one of: "
"add cache changes clean dbstat extras fts-config ignore "
"info list ls pull push rebuild server setting sync ui unset");
}
verify_all_options();
db_multi_exec("CREATE TEMP TABLE repolist(name,tag);");
if( useCheckouts ){
db_multi_exec(
"INSERT INTO repolist "
"SELECT DISTINCT substr(name, 7), name COLLATE nocase"
" FROM global_config"
" WHERE substr(name, 1, 6)=='ckout:'"
|
| ︙ | ︙ | |||
408 409 410 411 412 413 414 |
if( zCmd[0]=='l' ){
fossil_print("%s\n", zFilename);
continue;
}else if( showFile ){
fossil_print("%s: %s\n", useCheckouts ? "checkout" : "repository",
zFilename);
}
| < | | < | 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 |
if( zCmd[0]=='l' ){
fossil_print("%s\n", zFilename);
continue;
}else if( showFile ){
fossil_print("%s: %s\n", useCheckouts ? "checkout" : "repository",
zFilename);
}
zSyscmd = mprintf("%$ %s %$%s",
g.nameOfExe, zCmd, zFilename, blob_str(&extra));
if( showLabel ){
int len = (int)strlen(zFilename);
int nStar = 80 - (len + 15);
if( nStar<2 ) nStar = 1;
fossil_print("%.13c %s %.*c\n", '*', zFilename, nStar, '*');
fflush(stdout);
}
if( !quiet || dryRunFlag ){
fossil_print("%s\n", zSyscmd);
fflush(stdout);
}
rc = dryRunFlag ? 0 : fossil_system(zSyscmd);
free(zSyscmd);
if( stopOnError && rc ){
break;
}
}
db_finalize(&q);
blob_reset(&extra);
|
| ︙ | ︙ |
| ︙ | ︙ | |||
21 22 23 24 25 26 27 | #include "attach.h" #include <assert.h> /* ** WEBPAGE: attachlist ** List attachments. ** | | > | > | | > | | | | 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 |
#include "attach.h"
#include <assert.h>
/*
** WEBPAGE: attachlist
** List attachments.
**
** tkt=HASH
** page=WIKIPAGE
** technote=HASH
**
** At most one of technote=, tkt= or page= may be supplied.
**
** If none are given, all attachments are listed. If one is given, only
** attachments for the designated technote, ticket or wiki page are shown.
**
** HASH may be just a prefix of the relevant technical note or ticket
** artifact hash, in which case all attachments of all technical notes or
** tickets with the prefix will be listed.
*/
void attachlist_page(void){
const char *zPage = P("page");
const char *zTkt = P("tkt");
const char *zTechNote = P("technote");
Blob sql;
Stmt q;
|
| ︙ | ︙ | |||
150 151 152 153 154 155 156 157 158 | /* ** WEBPAGE: attachdownload ** WEBPAGE: attachimage ** WEBPAGE: attachview ** ** Download or display an attachment. ** Query parameters: ** | > | | | 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
/*
** WEBPAGE: attachdownload
** WEBPAGE: attachimage
** WEBPAGE: attachview
**
** Download or display an attachment.
**
** Query parameters:
**
** tkt=HASH
** page=WIKIPAGE
** technote=HASH
** file=FILENAME
** attachid=ID
**
*/
void attachview_page(void){
const char *zPage = P("page");
const char *zTkt = P("tkt");
|
| ︙ | ︙ | |||
248 249 250 251 252 253 254 | /* ** Commit a new attachment into the repository */ void attach_commit( const char *zName, /* The filename of the attachment */ | | | 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
/*
** Commit a new attachment into the repository
*/
void attach_commit(
const char *zName, /* The filename of the attachment */
const char *zTarget, /* The artifact hash to attach to */
const char *aContent, /* The content of the attachment */
int szContent, /* The length of the attachment */
int needModerator, /* Moderate the attachment? */
const char *zComment /* The comment for the attachment */
){
Blob content;
Blob manifest;
|
| ︙ | ︙ | |||
303 304 305 306 307 308 309 |
db_end_transaction(0);
}
/*
** WEBPAGE: attachadd
** Add a new attachment.
**
| | | | 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
db_end_transaction(0);
}
/*
** WEBPAGE: attachadd
** Add a new attachment.
**
** tkt=HASH
** page=WIKIPAGE
** technote=HASH
** from=URL
**
*/
void attachadd_page(void){
const char *zPage = P("page");
const char *zTkt = P("tkt");
const char *zTechNote = P("technote");
|
| ︙ | ︙ | |||
418 419 420 421 422 423 424 |
**
** Show the details of an attachment artifact.
*/
void ainfo_page(void){
int rid; /* RID for the control artifact */
int ridSrc; /* RID for the attached file */
char *zDate; /* Date attached */
| | | | 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 |
**
** Show the details of an attachment artifact.
*/
void ainfo_page(void){
int rid; /* RID for the control artifact */
int ridSrc; /* RID for the attached file */
char *zDate; /* Date attached */
const char *zUuid; /* Hash of the control artifact */
Manifest *pAttach; /* Parse of the control artifact */
const char *zTarget; /* Wiki, ticket or tech note attached to */
const char *zSrc; /* Hash of the attached file */
const char *zName; /* Name of the attached file */
const char *zDesc; /* Description of the attached file */
const char *zWikiName = 0; /* Wiki page name when attached to Wiki */
const char *zTNUuid = 0; /* Tech Note ID when attached to tech note */
const char *zTktUuid = 0; /* Ticket ID when attached to a ticket */
int modPending; /* True if awaiting moderation */
const char *zModAction; /* Moderation action or NULL */
|
| ︙ | ︙ | |||
543 544 545 546 547 548 549 |
cgi_redirectf("%R/tktview/%!S", zTktUuid);
}else{
cgi_redirectf("%R/wiki?name=%t", zWikiName);
}
return;
}
if( strcmp(zModAction,"approve")==0 ){
| | | 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 |
cgi_redirectf("%R/tktview/%!S", zTktUuid);
}else{
cgi_redirectf("%R/wiki?name=%t", zWikiName);
}
return;
}
if( strcmp(zModAction,"approve")==0 ){
moderation_approve('a', rid);
}
}
style_header("Attachment Details");
style_submenu_element("Raw", "%R/artifact/%s", zUuid);
if(fShowContent){
style_submenu_element("Line Numbers", "%R/ainfo/%s%s", zUuid,
((zLn&&*zLn) ? "" : "?ln=0"));
|
| ︙ | ︙ | |||
679 680 681 682 683 684 685 | } /* ** COMMAND: attachment* ** ** Usage: %fossil attachment add ?PAGENAME? FILENAME ?OPTIONS? ** | | > < | | | | | > | | | | 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 | } /* ** COMMAND: attachment* ** ** Usage: %fossil attachment add ?PAGENAME? FILENAME ?OPTIONS? ** ** Add an attachment to an existing wiki page or tech note. ** Options: ** ** -t|--technote DATETIME Specifies the timestamp of ** the technote to which the attachment ** is to be made. The attachment will be ** to the most recently modified tech note ** with the specified timestamp. ** ** -t|--technote TECHNOTE-ID Specifies the technote to be ** updated by its technote id. ** ** One of PAGENAME, DATETIME or TECHNOTE-ID must be specified. ** ** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in ** year-month-day form, it may be truncated, the "T" may be replaced by ** a space, and it may also name a timezone offset from UTC as "-HH:MM" ** (westward) or "+HH:MM" (eastward). Either no timezone suffix or "Z" ** means UTC. */ |
| ︙ | ︙ | |||
761 762 763 764 765 766 767 |
);
zFile = g.argv[3];
}
blob_read_from_file(&content, zFile, ExtFILE);
user_select();
attach_commit(
zFile, /* The filename of the attachment */
| | | 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 |
);
zFile = g.argv[3];
}
blob_read_from_file(&content, zFile, ExtFILE);
user_select();
attach_commit(
zFile, /* The filename of the attachment */
zTarget, /* The artifact hash to attach to */
blob_buffer(&content), /* The content of the attachment */
blob_size(&content), /* The length of the attachment */
0, /* No need to moderate the attachment */
"" /* Empty attachment comment */
);
if( !zETime ){
fossil_print("Attached %s to wiki page %s.\n", zFile, zPageName);
|
| ︙ | ︙ |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
/*
** Copyright (c) 2020 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
** 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.
**
** Author contact information:
** drh@sqlite.org
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code to implement for managing backlinks and
** the "backlink" table of the repository database.
**
** A backlink is a reference in Fossil-Wiki or Markdown to some other
** object in the repository.
*/
#include "config.h"
#include "backlink.h"
#include <assert.h>
/*
** Show a graph all wiki, tickets, and check-ins that refer to object zUuid.
**
** If zLabel is not NULL and the graph is not empty, then output zLabel as
** a prefix to the graph.
*/
void render_backlink_graph(const char *zUuid, const char *zLabel){
Blob sql;
Stmt q;
char *zGlob;
zGlob = mprintf("%.5s*", zUuid);
db_multi_exec(
"CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);\n"
"DELETE FROM ok;\n"
"INSERT OR IGNORE INTO ok(rid)\n"
" SELECT CASE srctype\n"
" WHEN 2 THEN (SELECT rid FROM tagxref WHERE tagid=backlink.srcid\n"
" ORDER BY mtime DESC LIMIT 1)\n"
" ELSE srcid END\n"
" FROM backlink\n"
" WHERE target GLOB %Q"
" AND %Q GLOB (target || '*');",
zGlob, zUuid
);
if( !db_exists("SELECT 1 FROM ok") ) return;
if( zLabel ) cgi_printf("%s", zLabel);
blob_zero(&sql);
blob_append(&sql, timeline_query_for_www(), -1);
blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC");
db_prepare(&q, "%s", blob_sql_text(&sql));
www_print_timeline(&q,
TIMELINE_DISJOINT|TIMELINE_GRAPH|TIMELINE_NOSCROLL|TIMELINE_REFS,
0, 0, 0, 0, 0, 0);
db_finalize(&q);
}
/*
** WEBPAGE: test-backlink-timeline
**
** Show a timeline of all check-ins and other events that have entries
** in the backlink table. This is used for testing the rendering
** of the "References" section of the /info page.
*/
void backlink_timeline_page(void){
Blob sql;
Stmt q;
login_check_credentials();
if( !g.perm.Read || !g.perm.RdTkt || !g.perm.RdWiki ){
login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
return;
}
style_header("Backlink Timeline (Internal Testing Use)");
db_multi_exec(
"CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
"DELETE FROM ok;"
"INSERT OR IGNORE INTO ok"
" SELECT blob.rid FROM backlink, blob"
" WHERE blob.uuid BETWEEN backlink.target AND (backlink.target||'x')"
);
blob_zero(&sql);
blob_append(&sql, timeline_query_for_www(), -1);
blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC");
db_prepare(&q, "%s", blob_sql_text(&sql));
www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH|TIMELINE_NOSCROLL,
0, 0, 0, 0, 0, 0);
db_finalize(&q);
style_footer();
}
/*
** WEBPAGE: test-backlinks
**
** Show a table of all backlinks. Admin access only.
*/
void backlink_table_page(void){
Stmt q;
int n;
login_check_credentials();
if( !g.perm.Admin ){
login_needed(g.anon.Admin);
return;
}
style_header("Backlink Table (Internal Testing Use)");
n = db_int(0, "SELECT count(*) FROM backlink");
@ <p>%d(n) backlink table entries:</p>
db_prepare(&q,
"SELECT target, srctype, srcid, datetime(mtime),"
" CASE srctype"
" WHEN 2 THEN (SELECT substr(tagname,6) FROM tag"
" WHERE tagid=srcid AND tagname GLOB 'wiki-*')"
" ELSE null END FROM backlink"
);
style_table_sorter();
@ <table border="1" cellpadding="2" cellspacing="0" \
@ class='sortable' data-column-types='ttt' data-init-sort='0'>
@ <thead><tr><th> Source <th> Target <th> mtime </tr></thead>
@ <tbody>
while( db_step(&q)==SQLITE_ROW ){
const char *zTarget = db_column_text(&q, 0);
int srctype = db_column_int(&q, 1);
int srcid = db_column_int(&q, 2);
const char *zMtime = db_column_text(&q, 3);
@ <tr><td><a href="%R/info/%h(zTarget)">%h(zTarget)</a>
switch( srctype ){
case BKLNK_COMMENT: {
@ <td><a href="%R/info?name=rid:%d(srcid)">comment-%d(srcid)</a>
break;
}
case BKLNK_TICKET: {
@ <td><a href="%R/info?name=rid:%d(srcid)">ticket-%d(srcid)</a>
break;
}
case BKLNK_WIKI: {
const char *zName = db_column_text(&q, 4);
@ <td><a href="%R/wiki?name=%h(zName)&p">wiki-%d(srcid)</a>
break;
}
default: {
@ <td>unknown(%d(srctype)) - %d(srcid)
break;
}
}
@ <td>%h(zMtime)</tr>
}
@ </tbody>
@ </table>
db_finalize(&q);
style_footer();
}
/*
** Remove all prior backlinks for the wiki page given. Then
** add new backlinks for the latest version of the wiki page.
*/
void backlink_wiki_refresh(const char *zWikiTitle){
int tagid = wiki_tagid(zWikiTitle);
int rid;
Manifest *pWiki;
if( tagid==0 ) return;
rid = db_int(0, "SELECT rid FROM tagxref WHERE tagid=%d"
" ORDER BY mtime DESC LIMIT 1", tagid);
if( rid==0 ) return;
pWiki = manifest_get(rid, CFTYPE_WIKI, 0);
if( pWiki ){
backlink_extract(pWiki->zWiki, pWiki->zMimetype, tagid, 2, pWiki->rDate,1);
manifest_destroy(pWiki);
}
}
/*
** Structure used to pass down state information through the
** markup formatters into the BACKLINK generator.
*/
#if INTERFACE
struct Backlink {
int srcid; /* srcid for the source document */
int srctype; /* One of BKLNK_*. 0=comment 1=ticket 2=wiki */
double mtime; /* mtime field for new BACKLINK table entries */
};
#endif
/*
** zTarget is a hyperlink target in some markup format. If this
** target is a self-reference to some other object in the repository,
** then create an appropriate backlink.
*/
void backlink_create(Backlink *p, const char *zTarget, int nTarget){
char zLink[HNAME_MAX+4];
if( zTarget==0 ) return;
if( nTarget<4 ) return;
if( nTarget>=10 && strncmp(zTarget,"/info/",6)==0 ){
zTarget += 6;
nTarget -= 6;
}
if( nTarget>HNAME_MAX ) return;
if( !validate16(zTarget, nTarget) ) return;
memcpy(zLink, zTarget, nTarget);
zLink[nTarget] = 0;
canonical16(zLink, nTarget);
db_multi_exec(
"REPLACE INTO backlink(target,srctype,srcid,mtime)"
"VALUES(%Q,%d,%d,%.17g)", zLink, p->srctype, p->srcid, p->mtime
);
}
/*
** This routine is called by the markdown formatter for each hyperlink.
** If the hyperlink is a backlink, add it to the BACKLINK table.
*/
static int backlink_md_link(
Blob *ob, /* Write output text here (not used in this case) */
Blob *target, /* The hyperlink target */
Blob *title, /* Hyperlink title */
Blob *content, /* Content of the link */
void *opaque
){
Backlink *p = (Backlink*)opaque;
char *zTarget = blob_buffer(target);
int nTarget = blob_size(target);
backlink_create(p, zTarget, nTarget);
return 1;
}
/* No-op routine for the rendering callbacks that we do not need */
static void mkdn_noop0(Blob *x){ return; }
static int mkdn_noop1(Blob *x){ return 1; }
/*
** Scan markdown text and add self-hyperlinks to the BACKLINK table.
*/
void markdown_extract_links(
char *zInputText,
Backlink *p
){
struct mkd_renderer html_renderer = {
/* prolog */ (void(*)(Blob*,void*))mkdn_noop0,
/* epilog */ (void(*)(Blob*,void*))mkdn_noop0,
/* blockcode */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
/* blockquote */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
/* blockhtml */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
/* header */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
/* hrule */ (void(*)(Blob*,void*))mkdn_noop0,
/* list */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
/* listitem */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
/* paragraph */ (void(*)(Blob*,Blob*,void*))mkdn_noop0,
/* table */ (void(*)(Blob*,Blob*,Blob*,void*))mkdn_noop0,
/* table_cell */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
/* table_row */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0,
/* autolink */ (int(*)(Blob*,Blob*,enum mkd_autolink,void*))mkdn_noop1,
/* codespan */ (int(*)(Blob*,Blob*,int,void*))mkdn_noop1,
/* dbl_emphas */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1,
/* emphasis */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1,
/* image */ (int(*)(Blob*,Blob*,Blob*,Blob*,void*))mkdn_noop1,
/* linebreak */ (int(*)(Blob*,void*))mkdn_noop1,
/* link */ backlink_md_link,
/* r_html_tag */ (int(*)(Blob*,Blob*,void*))mkdn_noop1,
/* tri_emphas */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1,
0, /* entity */
0, /* normal_text */
"*_", /* emphasis characters */
0 /* client data */
};
Blob out, in;
html_renderer.opaque = (void*)p;
blob_init(&out, 0, 0);
blob_init(&in, zInputText, -1);
markdown(&out, &in, &html_renderer);
blob_reset(&out);
blob_reset(&in);
}
/*
** Parse text looking for hyperlinks. Insert references into the
** BACKLINK table.
*/
void backlink_extract(
char *zSrc, /* Input text from which links are extracted */
const char *zMimetype, /* Mimetype of input. NULL means fossil-wiki */
int srcid, /* srcid for the source document */
int srctype, /* One of BKLNK_*. 0=comment 1=ticket 2=wiki */
double mtime, /* mtime field for new BACKLINK table entries */
int replaceFlag /* True to overwrite prior BACKLINK entries */
){
Backlink bklnk;
if( replaceFlag ){
db_multi_exec("DELETE FROM backlink WHERE srctype=%d AND srcid=%d",
srctype, srcid);
}
bklnk.srcid = srcid;
assert( ValidBklnk(srctype) );
bklnk.srctype = srctype;
bklnk.mtime = mtime;
if( zMimetype==0 || strstr(zMimetype,"wiki")!=0 ){
wiki_extract_links(zSrc, &bklnk, srctype==BKLNK_COMMENT ? WIKI_INLINE : 0);
}else if( strstr(zMimetype,"markdown")!=0 ){
markdown_extract_links(zSrc, &bklnk);
}
}
/*
** COMMAND: test-backlinks
**
** Usage: %fossil test-backlinks SRCTYPE SRCID ?OPTIONS? INPUT-FILE
**
** Read the content of INPUT-FILE and pass it into the backlink_extract()
** routine. But instead of adding backlinks to the backlink table,
** just print them on stdout. SRCID and SRCTYPE are integers.
**
** Options:
** --mtime DATETIME Use an alternative date/time. Defaults to the
** current date/time.
** --mimetype TYPE Use an alternative mimetype.
*/
void test_backlinks_cmd(void){
const char *zMTime = find_option("mtime",0,1);
const char *zMimetype = find_option("mimetype",0,1);
Blob in;
int srcid;
int srctype;
double mtime;
verify_all_options();
if( g.argc!=5 ){
usage("SRCTYPE SRCID INPUTFILE");
}
srctype = atoi(g.argv[2]);
if( srctype<0 || srctype>2 ){
fossil_fatal("SRCTYPE should be a integer 0, 1, or 2");
}
srcid = atoi(g.argv[3]);
blob_read_from_file(&in, g.argv[4], ExtFILE);
sqlite3_open(":memory:",&g.db);
if( zMTime==0 ) zMTime = "now";
mtime = db_double(1721059.5,"SELECT julianday(%Q)",zMTime);
g.fSqlPrint = 1;
sqlite3_create_function(g.db, "print", -1, SQLITE_UTF8, 0,db_sql_print,0,0);
db_multi_exec(
"CREATE TEMP TABLE backlink(target,srctype,srcid,mtime);\n"
"CREATE TRIGGER backlink_insert BEFORE INSERT ON backlink BEGIN\n"
" SELECT print("
" 'target='||quote(new.target)||"
" ' srctype='||quote(new.srctype)||"
" ' srcid='||quote(new.srcid)||"
" ' mtime='||datetime(new.mtime));\n"
" SELECT raise(ignore);\n"
"END;"
);
backlink_extract(blob_str(&in),zMimetype,srcid,srctype,mtime,0);
blob_reset(&in);
}
/*
** COMMAND: test-wiki-relink
**
** Usage: %fossil test-wiki-relink WIKI-PAGE-NAME
**
** Run the backlink_wiki_refresh() procedure on the wiki page
** named. WIKI-PAGE-NAME can be a glob pattern or a prefix
** of the wiki page.
*/
void test_wiki_relink_cmd(void){
Stmt q;
db_find_and_open_repository(0, 0);
if( g.argc!=3 ) usage("WIKI-PAGE-NAME");
db_prepare(&q,
"SELECT substr(tagname,6) FROM tag WHERE tagname GLOB 'wiki-%q*'",
g.argv[2]
);
while( db_step(&q)==SQLITE_ROW ){
const char *zPage = db_column_text(&q,0);
fossil_print("Relinking page: %s\n", zPage);
backlink_wiki_refresh(zPage);
}
db_finalize(&q);
}
|
| ︙ | ︙ | |||
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 |
** to run this command as a daemon that will periodically invoke backoffice
** on collection of repositories.
**
** OPTIONS:
**
** --debug Show what this command is doing.
**
** --nodelay Do not queue up or wait for a backoffice job
** to complete. If no work is available or if
** backoffice has run recently, return immediately.
** The --nodelay option is implied if more than
** one repository is listed on the command-line.
**
** --poll N Repeat backoffice calls for repositories that
** change in appoximately N-second intervals.
** N less than 1 turns polling off (the default).
**
** --trace Enable debugging output on stderr
*/
void backoffice_command(void){
int nPoll;
const char *zPoll;
int bDebug = 0;
unsigned int nCmd = 0;
if( find_option("trace",0,0)!=0 ) g.fAnyTrace = 1;
if( find_option("nodelay",0,0)!=0 ) backofficeNoDelay = 1;
zPoll = find_option("poll",0,1);
nPoll = zPoll ? atoi(zPoll) : 0;
bDebug = find_option("debug",0,0)!=0;
/* Silently consume the -R or --repository flag, leaving behind its
** argument. This is for legacy compatibility. Older versions of the
** backoffice command only ran on a single repository that was specified
** using the -R option. */
(void)find_option("repository","R",0);
verify_all_options();
if( g.argc>3 || nPoll>0 ){
/* Either there are multiple repositories named on the command-line
** or we are polling. In either case, each backoffice should be run
** using a separate sub-process */
int i;
time_t iNow = 0;
time_t ix;
while( 1 /* exit via "break;" */){
time_t iNext = time(0);
for(i=2; i<g.argc; i++){
Blob cmd;
| > > > > > > > > > > > | > > > | > > > > > > > | 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 |
** to run this command as a daemon that will periodically invoke backoffice
** on collection of repositories.
**
** OPTIONS:
**
** --debug Show what this command is doing.
**
** --min N When polling, invoke backoffice at least
** once every N seconds even if the repository
** never changes. 0 or negative means disable
** this feature. Default: 3600 (once per hour).
**
** --nodelay Do not queue up or wait for a backoffice job
** to complete. If no work is available or if
** backoffice has run recently, return immediately.
** The --nodelay option is implied if more than
** one repository is listed on the command-line.
**
** --poll N Repeat backoffice calls for repositories that
** change in appoximately N-second intervals.
** N less than 1 turns polling off (the default).
** Recommended polling interval: 60 seconds.
**
** --trace Enable debugging output on stderr
*/
void backoffice_command(void){
int nPoll;
int nMin;
const char *zPoll;
int bDebug = 0;
unsigned int nCmd = 0;
if( find_option("trace",0,0)!=0 ) g.fAnyTrace = 1;
if( find_option("nodelay",0,0)!=0 ) backofficeNoDelay = 1;
zPoll = find_option("poll",0,1);
nPoll = zPoll ? atoi(zPoll) : 0;
zPoll = find_option("min",0,1);
nMin = zPoll ? atoi(zPoll) : 3600;
bDebug = find_option("debug",0,0)!=0;
/* Silently consume the -R or --repository flag, leaving behind its
** argument. This is for legacy compatibility. Older versions of the
** backoffice command only ran on a single repository that was specified
** using the -R option. */
(void)find_option("repository","R",0);
verify_all_options();
if( g.argc>3 || nPoll>0 ){
/* Either there are multiple repositories named on the command-line
** or we are polling. In either case, each backoffice should be run
** using a separate sub-process */
int i;
time_t iNow = 0;
time_t ix;
i64 *aLastRun = fossil_malloc( sizeof(i64)*g.argc );
memset(aLastRun, 0, sizeof(i64)*g.argc );
while( 1 /* exit via "break;" */){
time_t iNext = time(0);
for(i=2; i<g.argc; i++){
Blob cmd;
if( !file_isfile(g.argv[i], ExtFILE) ){
continue; /* Repo no longer exists. Ignore it. */
}
if( iNow
&& iNow>file_mtime(g.argv[i], ExtFILE)
&& (nMin<=0 || aLastRun[i]+nMin>iNow)
){
continue; /* Not yet time to run this one */
}
blob_init(&cmd, 0, 0);
blob_append_escaped_arg(&cmd, g.nameOfExe);
blob_append(&cmd, " backoffice --nodelay", -1);
if( g.fAnyTrace ){
blob_append(&cmd, " --trace", -1);
}
blob_append_escaped_arg(&cmd, g.argv[i]);
nCmd++;
if( bDebug ){
fossil_print("COMMAND[%u]: %s\n", nCmd, blob_str(&cmd));
}
fossil_system(blob_str(&cmd));
aLastRun[i] = iNext;
blob_reset(&cmd);
}
if( nPoll<1 ) break;
iNow = iNext;
ix = time(0);
if( ix < iNow+nPoll ){
sqlite3_int64 nMS = (iNow + nPoll - ix)*1000;
if( bDebug )fossil_print("SLEEP: %lld\n", nMS);
sqlite3_sleep((int)nMS);
}
}
}else{
/* Not polling and only one repository named. Backoffice is run
** once by this process, which then exits */
if( g.argc==3 ){
g.zRepositoryOption = g.argv[2];
g.argc--;
}
db_find_and_open_repository(0,0);
backoffice_thread();
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
*/
struct Bag {
int cnt; /* Number of integers in the bag */
int sz; /* Number of slots in a[] */
int used; /* Number of used slots in a[] */
int *a; /* Hash table of integers that are in the bag */
};
#endif
/*
** Initialize a Bag structure
*/
void bag_init(Bag *p){
memset(p, 0, sizeof(*p));
| > > > > > > | 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
*/
struct Bag {
int cnt; /* Number of integers in the bag */
int sz; /* Number of slots in a[] */
int used; /* Number of used slots in a[] */
int *a; /* Hash table of integers that are in the bag */
};
/*
** An expression for statically initializing a Bag instance, to be
** assigned to Bag instances at their declaration point. It has
** the same effect as passing the Bag to bag_init().
*/
#define Bag_INIT {0,0,0,0}
#endif
/*
** Initialize a Bag structure
*/
void bag_init(Bag *p){
memset(p, 0, sizeof(*p));
|
| ︙ | ︙ |
| ︙ | ︙ | |||
302 303 304 305 306 307 308 | /* ** COMMAND: bisect ** ** Usage: %fossil bisect SUBCOMMAND ... ** ** Run various subcommands useful for searching for bugs. ** | | | | | | | | | | | | | | | | | | | | | | | | | | | | < | | | | | | | | | | | 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 |
/*
** COMMAND: bisect
**
** Usage: %fossil bisect SUBCOMMAND ...
**
** Run various subcommands useful for searching for bugs.
**
** > fossil bisect bad ?VERSION?
**
** Identify version VERSION as non-working. If VERSION is omitted,
** the current checkout is marked as non-working.
**
** > fossil bisect good ?VERSION?
**
** Identify version VERSION as working. If VERSION is omitted,
** the current checkout is marked as working.
**
** > fossil bisect log
** > fossil bisect chart
**
** Show a log of "good" and "bad" versions. "bisect log" shows the
** events in the order that they were tested. "bisect chart" shows
** them in order of check-in.
**
** > fossil bisect next
**
** Update to the next version that is halfway between the working and
** non-working versions.
**
** > fossil bisect options ?NAME? ?VALUE?
**
** List all bisect options, or the value of a single option, or set the
** value of a bisect option.
**
** > fossil bisect reset
**
** Reinitialize a bisect session. This cancels prior bisect history
** and allows a bisect session to start over from the beginning.
**
** > fossil bisect vlist|ls|status ?-a|--all?
**
** List the versions in between "bad" and "good".
**
** > fossil bisect ui
**
** Like "fossil ui" except start on a timeline that shows only the
** check-ins that are part of the current bisect.
**
** > fossil bisect undo
**
** Undo the most recent "good" or "bad" command.
**
** Summary:
** * fossil bisect bad ?VERSION?
** * fossil bisect good ?VERSION?
** * fossil bisect log
** * fossil bisect chart
** * fossil bisect next
** * fossil bisect options
** * fossil bisect reset
** * fossil bisect status
** * fossil bisect ui
** * fossil bisect undo
*/
void bisect_cmd(void){
int n;
const char *zCmd;
int foundCmd = 0;
db_must_be_within_tree();
if( g.argc<3 ){
|
| ︙ | ︙ |
| ︙ | ︙ | |||
275 276 277 278 279 280 281 282 | pBlob->iCursor = 0; pBlob->blobFlags = 0; pBlob->xRealloc = blobReallocStatic; } /* ** Append text or data to the end of a blob. */ | > > > > > > > | | 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
pBlob->iCursor = 0;
pBlob->blobFlags = 0;
pBlob->xRealloc = blobReallocStatic;
}
/*
** Append text or data to the end of a blob.
**
** The blob_append_full() routine is a complete implementation.
** The blob_append() routine only works for cases where nData>0 and
** no resizing is required, and falls back to blob_append_full() if
** either condition is not met, but runs faster in the common case
** where all conditions are met. The use of blob_append() is
** recommended, unless it is known in advance that nData<0.
*/
void blob_append_full(Blob *pBlob, const char *aData, int nData){
sqlite3_int64 nNew;
assert( aData!=0 || nData==0 );
blob_is_init(pBlob);
if( nData<0 ) nData = strlen(aData);
if( nData==0 ) return;
nNew = pBlob->nUsed;
nNew += nData;
|
| ︙ | ︙ | |||
299 300 301 302 303 304 305 306 307 308 309 310 311 |
blob_panic();
}
}
memcpy(&pBlob->aData[pBlob->nUsed], aData, nData);
pBlob->nUsed += nData;
pBlob->aData[pBlob->nUsed] = 0; /* Blobs are always nul-terminated */
}
/*
** Append a single character to the blob
*/
void blob_append_char(Blob *pBlob, char c){
if( pBlob->nUsed+1 >= pBlob->nAlloc ){
| > > > > > > > > > > > > > > > > > > > > < < | | < | > > | > | 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 |
blob_panic();
}
}
memcpy(&pBlob->aData[pBlob->nUsed], aData, nData);
pBlob->nUsed += nData;
pBlob->aData[pBlob->nUsed] = 0; /* Blobs are always nul-terminated */
}
void blob_append(Blob *pBlob, const char *aData, int nData){
sqlite3_int64 nUsed;
assert( aData!=0 || nData==0 );
/* blob_is_init(pBlob); // omitted for speed */
if( nData<=0 || pBlob->nUsed + nData >= pBlob->nAlloc ){
blob_append_full(pBlob, aData, nData);
return;
}
nUsed = pBlob->nUsed;
pBlob->nUsed += nData;
pBlob->aData[pBlob->nUsed] = 0;
memcpy(&pBlob->aData[nUsed], aData, nData);
}
/*
** Append a string literal to a blob.
*/
#if INTERFACE
#define blob_append_string(BLOB,STR) blob_append(BLOB,STR,sizeof(STR)-1)
#endif
/*
** Append a single character to the blob
*/
void blob_append_char(Blob *pBlob, char c){
if( pBlob->nUsed+1 >= pBlob->nAlloc ){
blob_append_full(pBlob, &c, 1);
}else{
pBlob->aData[pBlob->nUsed++] = c;
}
}
/*
** Copy a blob
*/
void blob_copy(Blob *pTo, Blob *pFrom){
blob_is_init(pFrom);
blob_zero(pTo);
blob_append(pTo, blob_buffer(pFrom), blob_size(pFrom));
}
/*
** Return a pointer to a null-terminated string for a blob.
*/
char *blob_str(Blob *p){
blob_is_init(p);
if( p->nUsed==0 ){
blob_append_char(p, 0); /* NOTE: Changes nUsed. */
p->nUsed = 0;
}
if( p->nUsed<p->nAlloc ){
p->aData[p->nUsed] = 0;
}else{
blob_materialize(p);
}
return p->aData;
}
/*
** Return a pointer to a null-terminated string for a blob that has
|
| ︙ | ︙ | |||
448 449 450 451 452 453 454 455 456 457 458 459 460 461 |
** nByte in size. The blob is truncated if necessary.
*/
void blob_resize(Blob *pBlob, unsigned int newSize){
pBlob->xRealloc(pBlob, newSize+1);
pBlob->nUsed = newSize;
pBlob->aData[newSize] = 0;
}
/*
** Make sure a blob is nul-terminated and is not a pointer to unmanaged
** space. Return a pointer to the data.
*/
char *blob_materialize(Blob *pBlob){
blob_resize(pBlob, pBlob->nUsed);
| > > > > > > > > > > > > > > > > > > > | 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 |
** nByte in size. The blob is truncated if necessary.
*/
void blob_resize(Blob *pBlob, unsigned int newSize){
pBlob->xRealloc(pBlob, newSize+1);
pBlob->nUsed = newSize;
pBlob->aData[newSize] = 0;
}
/*
** Ensures that the given blob has at least the given amount of memory
** allocated to it. Does not modify pBlob->nUsed nor will it reduce
** the currently-allocated amount of memory.
**
** For semantic compatibility with blob_append_full(), if newSize is
** >=0x7fff000 (~2GB) then this function will trigger blob_panic(). If
** it didn't, it would be possible to bypass that hard-coded limit via
** this function.
*/
void blob_reserve(Blob *pBlob, unsigned int newSize){
if(newSize>=0x7fff0000 ){
blob_panic();
}else if(newSize>pBlob->nUsed){
pBlob->xRealloc(pBlob, newSize);
pBlob->aData[newSize] = 0;
}
}
/*
** Make sure a blob is nul-terminated and is not a pointer to unmanaged
** space. Return a pointer to the data.
*/
char *blob_materialize(Blob *pBlob){
blob_resize(pBlob, pBlob->nUsed);
|
| ︙ | ︙ | |||
1138 1139 1140 1141 1142 1143 1144 |
blob_reset(&b1);
blob_reset(&b2);
blob_reset(&b3);
}
fossil_print("ok\n");
}
| < | 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 |
blob_reset(&b1);
blob_reset(&b2);
blob_reset(&b3);
}
fossil_print("ok\n");
}
/*
** Convert every \n character in the given blob into \r\n.
*/
void blob_add_cr(Blob *p){
char *z = p->aData;
int j = p->nUsed;
int i, n;
|
| ︙ | ︙ | |||
1162 1163 1164 1165 1166 1167 1168 |
z[j] = 0;
while( j>i ){
if( (z[--j] = z[--i]) =='\n' ){
z[--j] = '\r';
}
}
}
| < | 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 |
z[j] = 0;
while( j>i ){
if( (z[--j] = z[--i]) =='\n' ){
z[--j] = '\r';
}
}
}
/*
** Remove every \r character from the given blob, replacing each one with
** a \n character if it was not already part of a \r\n pair.
*/
void blob_to_lf_only(Blob *p){
int i, j;
|
| ︙ | ︙ | |||
1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 |
void blob_append_escaped_arg(Blob *pBlob, const char *zIn){
int i;
char c;
int needEscape = 0;
int n = blob_size(pBlob);
char *z = blob_buffer(pBlob);
#if defined(_WIN32)
const char cQuote = '"'; /* Use "..." quoting on windows */
#else
const char cQuote = '\''; /* Use '...' quoting on unix */
#endif
for(i=0; (c = zIn[i])!=0; i++){
if( c==cQuote || (unsigned char)c<' ' ||
| > > > > | | | > > > > > > > > > > > > > > | > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > | 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 |
void blob_append_escaped_arg(Blob *pBlob, const char *zIn){
int i;
char c;
int needEscape = 0;
int n = blob_size(pBlob);
char *z = blob_buffer(pBlob);
#if defined(_WIN32)
const char cDirSep = '\\'; /* Use \ as directory separator */
const char cQuote = '"'; /* Use "..." quoting on windows */
const char cEscape = '^'; /* Use ^X escaping on windows */
#else
const char cDirSep = '/'; /* Use / as directory separator */
const char cQuote = '\''; /* Use '...' quoting on unix */
const char cEscape = '\\'; /* Use \X escaping on unix */
#endif
for(i=0; (c = zIn[i])!=0; i++){
if( c==cQuote || (unsigned char)c<' ' ||
c==cEscape || c==';' || c=='*' || c=='?' || c=='[' ){
Blob bad;
blob_token(pBlob, &bad);
fossil_fatal("the [%s] argument to the \"%s\" command contains "
"a character (ascii 0x%02x) that is a security risk",
zIn, blob_str(&bad), c);
}
if( !needEscape && !fossil_isalnum(c) && c!=cDirSep && c!='.' && c!='_' ){
needEscape = 1;
}
}
if( n>0 && !fossil_isspace(z[n-1]) ){
blob_append_char(pBlob, ' ');
}
if( needEscape ) blob_append_char(pBlob, cQuote);
if( zIn[0]=='-' ){
blob_append_char(pBlob, '.');
blob_append_char(pBlob, cDirSep);
#if defined(_WIN32)
}else if( zIn[0]=='/' ){
blob_append_char(pBlob, '.');
#endif
}
#if defined(_WIN32)
if( needEscape ){
for(i=0; (c = zIn[i])!=0; i++){
if( c==cQuote ) blob_append_char(pBlob, cDirSep);
blob_append_char(pBlob, c);
}
}else{
blob_append(pBlob, zIn, -1);
}
#else
blob_append(pBlob, zIn, -1);
#endif
if( needEscape ){
#if defined(_WIN32)
/* NOTE: Trailing backslash must be doubled before final double quote. */
if( pBlob->aData[pBlob->nUsed-1]==cDirSep ){
blob_append_char(pBlob, cDirSep);
}
#endif
blob_append_char(pBlob, cQuote);
}
}
/*
** COMMAND: test-escaped-arg
**
** Usage %fossil ARG ...
**
** Run each argment through blob_append_escaped_arg() and show the
** result. Append each argument to "fossil test-echo" and run that
** using fossil_system() to verify that it really does get escaped
** correctly.
*/
void test_escaped_arg__cmd(void){
int i;
Blob x;
blob_init(&x, 0, 0);
for(i=2; i<g.argc; i++){
fossil_print("%3d [%s]: ", i, g.argv[i]);
blob_appendf(&x, "fossil test-echo %$", g.argv[i]);
fossil_print("%s\n", blob_str(&x));
fossil_system(blob_str(&x));
blob_reset(&x);
}
}
/*
** A read(2)-like impl for the Blob class. Reads (copies) up to nLen
** bytes from pIn, starting at position pIn->iCursor, and copies them
** to pDest (which must be valid memory at least nLen bytes long).
**
|
| ︙ | ︙ | |||
1342 1343 1344 1345 1346 1347 1348 |
blob_swap(pBlob, &temp);
blob_reset(&temp);
}else if( starts_with_utf16_bom(pBlob, &bomSize, &bomReverse) ){
zUtf8 = blob_buffer(pBlob);
if( bomReverse ){
/* Found BOM, but with reversed bytes */
unsigned int i = blob_size(pBlob);
| | | > | 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 |
blob_swap(pBlob, &temp);
blob_reset(&temp);
}else if( starts_with_utf16_bom(pBlob, &bomSize, &bomReverse) ){
zUtf8 = blob_buffer(pBlob);
if( bomReverse ){
/* Found BOM, but with reversed bytes */
unsigned int i = blob_size(pBlob);
while( i>1 ){
/* swap bytes of unicode representation */
char zTemp = zUtf8[--i];
zUtf8[i] = zUtf8[i-1];
zUtf8[--i] = zTemp;
}
}
/* Make sure the blob contains two terminating 0-bytes */
blob_append(pBlob, "\000\000", 3);
zUtf8 = blob_str(pBlob) + bomSize;
zUtf8 = fossil_unicode_to_utf8(zUtf8);
blob_reset(pBlob);
blob_set_dynamic(pBlob, zUtf8);
}else if( useMbcs && invalid_utf8(pBlob) ){
#if defined(_WIN32) || defined(__CYGWIN__)
zUtf8 = fossil_mbcs_to_utf8(blob_str(pBlob));
blob_reset(pBlob);
blob_append(pBlob, zUtf8, -1);
fossil_mbcs_free(zUtf8);
#else
blob_cp1252_to_utf8(pBlob);
#endif /* _WIN32 */
}
}
|
| ︙ | ︙ | |||
16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
*******************************************************************************
**
** This file contains code used to create new branches within a repository.
*/
#include "config.h"
#include "branch.h"
#include <assert.h>
/*
** fossil branch new NAME BASIS ?OPTIONS?
** argv0 argv1 argv2 argv3 argv4
*/
void branch_new(void){
int rootid; /* RID of the root check-in - what we branch off of */
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
*******************************************************************************
**
** This file contains code used to create new branches within a repository.
*/
#include "config.h"
#include "branch.h"
#include <assert.h>
/*
** Return true if zBr is the branch name associated with check-in with
** blob.uuid value of zUuid
*/
int branch_includes_uuid(const char *zBr, const char *zUuid){
return db_exists(
"SELECT 1 FROM tagxref, blob"
" WHERE blob.uuid=%Q AND tagxref.rid=blob.rid"
" AND tagxref.value=%Q AND tagxref.tagtype>0"
" AND tagxref.tagid=%d",
zUuid, zBr, TAG_BRANCH
);
}
/*
** If RID refers to a check-in, return the name of the branch for that
** check-in.
**
** Space to hold the returned value is obtained from fossil_malloc()
** and should be freed by the caller.
*/
char *branch_of_rid(int rid){
char *zBr = 0;
static Stmt q;
db_static_prepare(&q,
"SELECT value FROM tagxref"
" WHERE rid=$rid AND tagid=%d"
" AND tagtype>0", TAG_BRANCH);
db_bind_int(&q, "$rid", rid);
if( db_step(&q)==SQLITE_ROW ){
zBr = fossil_strdup(db_column_text(&q,0));
}
db_reset(&q);
if( zBr==0 ){
static char *zMain = 0;
if( zMain==0 ) zMain = db_get("main-branch",0);
zBr = fossil_strdup(zMain);
}
return zBr;
}
/*
** fossil branch new NAME BASIS ?OPTIONS?
** argv0 argv1 argv2 argv3 argv4
*/
void branch_new(void){
int rootid; /* RID of the root check-in - what we branch off of */
|
| ︙ | ︙ | |||
115 116 117 118 119 120 121 |
if( isPrivate && zColor==0 ) zColor = "#fec084";
if( zColor!=0 ){
blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
}
blob_appendf(&branch, "T *branch * %F\n", zBranch);
blob_appendf(&branch, "T *sym-%F *\n", zBranch);
if( isPrivate ){
| < | 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
if( isPrivate && zColor==0 ) zColor = "#fec084";
if( zColor!=0 ){
blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
}
blob_appendf(&branch, "T *branch * %F\n", zBranch);
blob_appendf(&branch, "T *sym-%F *\n", zBranch);
if( isPrivate ){
noSign = 1;
}
/* Cancel all other symbolic tags */
db_prepare(&q,
"SELECT tagname FROM tagxref, tag"
" WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
|
| ︙ | ︙ | |||
216 217 218 219 220 221 222 |
@ AND tag.tagname='branch'
@ AND event.objid=tagxref.rid
@ GROUP BY 1;
;
/* Call this routine to create the TEMP table */
static void brlist_create_temp_table(void){
| | | 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
@ AND tag.tagname='branch'
@ AND event.objid=tagxref.rid
@ GROUP BY 1;
;
/* Call this routine to create the TEMP table */
static void brlist_create_temp_table(void){
db_exec_sql(createBrlistQuery);
}
#if INTERFACE
/*
** Allows bits in the mBplqFlags parameter to branch_prepare_list_query().
*/
|
| ︙ | ︙ | |||
305 306 307 308 309 310 311 | ** COMMAND: branch ** ** Usage: %fossil branch SUBCOMMAND ... ?OPTIONS? ** ** Run various subcommands to manage branches of the open repository or ** of the repository identified by the -R or --repository option. ** | | | | | | > | | | 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 |
** COMMAND: branch
**
** Usage: %fossil branch SUBCOMMAND ... ?OPTIONS?
**
** Run various subcommands to manage branches of the open repository or
** of the repository identified by the -R or --repository option.
**
** > fossil branch current
**
** Print the name of the branch for the current check-out
**
** > fossil branch info BRANCH-NAME
**
** Print information about a branch
**
** > fossil branch list|ls ?OPTIONS?
**
** List all branches. Options:
** -a|--all List all branches. Default show only open branches
** -c|--closed List closed branches.
** -r Reverse the sort order
** -t Show recently changed branches first
**
** > fossil branch new BRANCH-NAME BASIS ?OPTIONS?
**
** Create a new branch BRANCH-NAME off of check-in BASIS.
** Supported options for this subcommand include:
** --private branch is private (i.e., remains local)
** --bgcolor COLOR use COLOR instead of automatic background
** --nosign do not sign contents on this branch
** --date-override DATE DATE to use instead of 'now'
** --user-override USER USER to use instead of the current default
**
** DATE may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in
** year-month-day form, it may be truncated, the "T" may be
** replaced by a space, and it may also name a timezone offset
** from UTC as "-HH:MM" (westward) or "+HH:MM" (eastward).
** Either no timezone suffix or "Z" means UTC.
**
** Options valid for all subcommands:
**
** -R|--repository FILE Run commands on repository FILE
**
** Summary:
** fossil branch current
** fossil branch info BRANCH-NAME
** fossil branch [list|ls]
** fossil branch new BRANCH-NAME BASIS
*/
void branch_cmd(void){
int n;
const char *zCmd = "list";
db_find_and_open_repository(0, 0);
if( g.argc>=3 ) zCmd = g.argv[2];
n = strlen(zCmd);
|
| ︙ | ︙ | |||
659 660 661 662 663 664 665 |
blob_reset(&sql);
/* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too
** many descenders to (off-screen) parents. */
tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL;
if( PB("ng")==0 ) tmFlags |= TIMELINE_GRAPH;
if( PB("brbg")!=0 ) tmFlags |= TIMELINE_BRCOLOR;
if( PB("ubg")!=0 ) tmFlags |= TIMELINE_UCOLOR;
| | | 700 701 702 703 704 705 706 707 708 709 710 |
blob_reset(&sql);
/* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too
** many descenders to (off-screen) parents. */
tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL;
if( PB("ng")==0 ) tmFlags |= TIMELINE_GRAPH;
if( PB("brbg")!=0 ) tmFlags |= TIMELINE_BRCOLOR;
if( PB("ubg")!=0 ) tmFlags |= TIMELINE_UCOLOR;
www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, brtimeline_extra);
db_finalize(&q);
style_footer();
}
|
| ︙ | ︙ | |||
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
sqlite3_result_text(context, (char*)&z[n], len-n, SQLITE_TRANSIENT);
}else{
zOut = sqlite3_mprintf("/%.*s", i-n, &z[n]);
sqlite3_result_text(context, zOut, i-n+1, sqlite3_free);
}
}
/*
** Given a pathname which is a relative path from the root of
** the repository to a file or directory, compute a string which
** is an HTML rendering of that path with hyperlinks on each
** directory component of the path where the hyperlink redirects
** to the "dir" page for the directory.
**
** There is no hyperlink on the file element of the path.
**
** The computed string is appended to the pOut blob. pOut should
** have already been initialized.
*/
void hyperlinked_path(
const char *zPath, /* Path to render */
Blob *pOut, /* Write into this blob */
const char *zCI, /* check-in name, or NULL */
const char *zURI, /* "dir" or "tree" */
| > > > > > > > > | > | > > > > > > > > > | | | | | | | | < < < | 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 |
sqlite3_result_text(context, (char*)&z[n], len-n, SQLITE_TRANSIENT);
}else{
zOut = sqlite3_mprintf("/%.*s", i-n, &z[n]);
sqlite3_result_text(context, zOut, i-n+1, sqlite3_free);
}
}
/*
** Flag arguments for hyperlinked_path()
*/
#if INTERFACE
# define LINKPATH_FINFO 0x0001 /* Link final term to /finfo */
# define LINKPATH_FILE 0x0002 /* Link final term to /file */
#endif
/*
** Given a pathname which is a relative path from the root of
** the repository to a file or directory, compute a string which
** is an HTML rendering of that path with hyperlinks on each
** directory component of the path where the hyperlink redirects
** to the "dir" page for the directory.
**
** There is no hyperlink on the file element of the path.
**
** The computed string is appended to the pOut blob. pOut should
** have already been initialized.
*/
void hyperlinked_path(
const char *zPath, /* Path to render */
Blob *pOut, /* Write into this blob */
const char *zCI, /* check-in name, or NULL */
const char *zURI, /* "dir" or "tree" */
const char *zREx, /* Extra query parameters */
unsigned int mFlags /* Extra flags */
){
int i, j;
char *zSep = "";
for(i=0; zPath[i]; i=j){
for(j=i; zPath[j] && zPath[j]!='/'; j++){}
if( zPath[j]==0 ){
if( mFlags & LINKPATH_FILE ){
zURI = "file";
}else if( mFlags & LINKPATH_FINFO ){
zURI = "finfo";
}else{
blob_appendf(pOut, "/%h", zPath+i);
break;
}
}
if( zCI ){
char *zLink = href("%R/%s?name=%#T%s&ci=%T", zURI, j, zPath, zREx,zCI);
blob_appendf(pOut, "%s%z%#h</a>",
zSep, zLink, j-i, &zPath[i]);
}else{
char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx);
blob_appendf(pOut, "%s%z%#h</a>",
zSep, zLink, j-i, &zPath[i]);
}
zSep = "/";
while( zPath[j]=='/' ){ j++; }
}
}
|
| ︙ | ︙ | |||
114 115 116 117 118 119 120 121 122 123 124 125 |
**
** Query parameters:
**
** name=PATH Directory to display. Optional. Top-level if missing
** ci=LABEL Show only files in this check-in. Optional.
** type=TYPE TYPE=flat: use this display
** TYPE=tree: use the /tree display instead
*/
void page_dir(void){
char *zD = fossil_strdup(P("name"));
int nD = zD ? strlen(zD)+1 : 0;
int mxLen;
| > < < > > > > < < < < < < > > > > > > > > > > > > > > > > > > > > > > > > > < > | | > > | > > > > > > > > > > > > > > > > > < < < | | < < < < < < < < < < < < < < | 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 |
**
** Query parameters:
**
** name=PATH Directory to display. Optional. Top-level if missing
** ci=LABEL Show only files in this check-in. Optional.
** type=TYPE TYPE=flat: use this display
** TYPE=tree: use the /tree display instead
** noreadme Do not attempt to display the README file.
*/
void page_dir(void){
char *zD = fossil_strdup(P("name"));
int nD = zD ? strlen(zD)+1 : 0;
int mxLen;
char *zPrefix;
Stmt q;
const char *zCI = P("ci");
int rid = 0;
char *zUuid = 0;
Manifest *pM = 0;
const char *zSubdirLink;
int linkTrunk = 1;
int linkTip = 1;
HQuery sURI;
int isSymbolicCI = 0; /* ci= is symbolic name, not a hash prefix */
int isBranchCI = 0; /* True if ci= refers to a branch name */
char *zHeader = 0;
if( zCI && strlen(zCI)==0 ){ zCI = 0; }
if( strcmp(PD("type","flat"),"tree")==0 ){ page_tree(); return; }
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
/* If the name= parameter is an empty string, make it a NULL pointer */
if( zD && strlen(zD)==0 ){ zD = 0; }
/* If a specific check-in is requested, fetch and parse it. If the
** specific check-in does not exist, clear zCI. zCI==0 will cause all
** files from all check-ins to be displayed.
*/
if( zCI ){
pM = manifest_get_by_name(zCI, &rid);
if( pM ){
int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
linkTrunk = trunkRid && rid != trunkRid;
linkTip = rid != symbolic_name_to_rid("tip", "ci");
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0);
isBranchCI = branch_includes_uuid(zCI, zUuid);
Th_Store("current_checkin", zCI);
}else{
zCI = 0;
}
}
assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) );
if( zD==0 ){
if( zCI ){
zHeader = mprintf("Top-level Files of %s", zCI);
}else{
zHeader = mprintf("All Top-level Files");
}
}else{
if( zCI ){
zHeader = mprintf("Files in %s/ of %s", zD, zCI);
}else{
zHeader = mprintf("All File in %s/", zD);
}
}
style_header("%s", zHeader);
fossil_free(zHeader);
style_adunit_config(ADUNIT_RIGHT_OK);
sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
pathelementFunc, 0, 0);
url_initialize(&sURI, "dir");
cgi_query_parameters_to_url(&sURI);
/* Compute the title of the page */
if( zD ){
Blob dirname;
blob_init(&dirname, 0, 0);
hyperlinked_path(zD, &dirname, zCI, "dir", "", 0);
@ <h2>Files in directory %s(blob_str(&dirname)) \
blob_reset(&dirname);
zPrefix = mprintf("%s/", zD);
style_submenu_element("Top-Level", "%s",
url_render(&sURI, "name", 0, 0, 0));
}else{
@ <h2>Files in the top-level directory \
zPrefix = "";
}
if( zCI ){
if( fossil_strcmp(zCI,"tip")==0 ){
@ from the %z(href("%R/info?name=%T",zCI))latest check-in</a></h2>
}else if( isBranchCI ){
@ from the %z(href("%R/info?name=%T",zCI))latest check-in</a> \
@ of branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2>
}else {
@ of check-in %z(href("%R/info?name=%T",zCI))%h(zCI)</a></h2>
}
zSubdirLink = mprintf("%R/dir?ci=%T&name=%T", zCI, zPrefix);
if( nD==0 ){
style_submenu_element("File Ages", "%R/fileage?name=%T", zCI);
}
}else{
@ in any check-in</h2>
zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
}
if( linkTrunk ){
style_submenu_element("Trunk", "%s",
url_render(&sURI, "ci", "trunk", 0, 0));
}
if( linkTip ){
style_submenu_element("Tip", "%s", url_render(&sURI, "ci", "tip", 0, 0));
}
if( zD ){
style_submenu_element("History","%R/timeline?chng=%T/*", zD);
}
style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0));
style_submenu_element("Tree-View", "%s",
url_render(&sURI, "type", "tree", 0, 0));
/* Compute the temporary table "localfiles" containing the names
** of all files and subdirectories in the zD[] directory.
|
| ︙ | ︙ | |||
266 267 268 269 270 271 272 |
);
}
/* Generate a multi-column table listing the contents of zD[]
** directory.
*/
mxLen = db_int(12, "SELECT max(length(x)) FROM localfiles /*scan*/");
| < | < | > > > > > > > > | 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 |
);
}
/* Generate a multi-column table listing the contents of zD[]
** directory.
*/
mxLen = db_int(12, "SELECT max(length(x)) FROM localfiles /*scan*/");
if( mxLen<12 ) mxLen = 12;
mxLen += (mxLen+9)/10;
db_prepare(&q, "SELECT x, u FROM localfiles ORDER BY x /*scan*/");
@ <div class="columns files" style="columns: %d(mxLen)ex auto">
@ <ul class="browser">
while( db_step(&q)==SQLITE_ROW ){
const char *zFN;
zFN = db_column_text(&q, 0);
if( zFN[0]=='/' ){
zFN++;
@ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li>
}else{
const char *zLink;
if( zCI ){
zLink = href("%R/file?name=%T%T&ci=%T",zPrefix,zFN,zCI);
}else{
zLink = href("%R/finfo?name=%T%T",zPrefix,zFN);
}
@ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li>
}
}
db_finalize(&q);
manifest_destroy(pM);
@ </ul></div>
/* If the "noreadme" query parameter is present, do not try to
** show the content of the README file.
*/
if( P("noreadme")!=0 ){
style_footer();
return;
}
/* If the directory contains a readme file, then display its content below
** the list of files
*/
db_prepare(&q,
"SELECT x, u FROM localfiles"
" WHERE x COLLATE nocase IN"
|
| ︙ | ︙ | |||
343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
@ sandbox="allow-same-origin"
@ onload="this.height=this.contentDocument.documentElement.scrollHeight;">
@ </iframe>
}else{
Blob content;
const char *zMime = mimetype_from_name(zName);
content_get(rid, &content);
wiki_render_by_mimetype(&content, zMime);
}
}
}
db_finalize(&q);
style_footer();
}
| > | 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 |
@ sandbox="allow-same-origin"
@ onload="this.height=this.contentDocument.documentElement.scrollHeight;">
@ </iframe>
}else{
Blob content;
const char *zMime = mimetype_from_name(zName);
content_get(rid, &content);
safe_html_context(DOCSRC_FILE);
wiki_render_by_mimetype(&content, zMime);
}
}
}
db_finalize(&q);
style_footer();
}
|
| ︙ | ︙ | |||
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 |
FileTreeNode *p; /* One line of the tree */
FileTree sTree; /* The complete tree of files */
HQuery sURI; /* Hyperlink */
int startExpanded; /* True to start out with the tree expanded */
int showDirOnly; /* Show directories only. Omit files */
int nDir = 0; /* Number of directories. Used for ID attributes */
char *zProjectName = db_get("project-name", 0);
if( strcmp(PD("type","flat"),"flat")==0 ){ page_dir(); return; }
memset(&sTree, 0, sizeof(sTree));
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
pathelementFunc, 0, 0);
url_initialize(&sURI, "tree");
cgi_query_parameters_to_url(&sURI);
if( PB("nofiles") ){
showDirOnly = 1;
| > > > > < < | 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 |
FileTreeNode *p; /* One line of the tree */
FileTree sTree; /* The complete tree of files */
HQuery sURI; /* Hyperlink */
int startExpanded; /* True to start out with the tree expanded */
int showDirOnly; /* Show directories only. Omit files */
int nDir = 0; /* Number of directories. Used for ID attributes */
char *zProjectName = db_get("project-name", 0);
int isSymbolicCI = 0; /* ci= is a symbolic name, not a hash prefix */
int isBranchCI = 0; /* ci= refers to a branch name */
char *zHeader = 0;
if( zCI && strlen(zCI)==0 ){ zCI = 0; }
if( strcmp(PD("type","flat"),"flat")==0 ){ page_dir(); return; }
memset(&sTree, 0, sizeof(sTree));
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
pathelementFunc, 0, 0);
url_initialize(&sURI, "tree");
cgi_query_parameters_to_url(&sURI);
if( PB("nofiles") ){
showDirOnly = 1;
}else{
showDirOnly = 0;
}
style_adunit_config(ADUNIT_RIGHT_OK);
if( PB("expand") ){
startExpanded = 1;
}else{
startExpanded = 0;
}
|
| ︙ | ︙ | |||
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 |
int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
linkTrunk = trunkRid && rid != trunkRid;
linkTip = rid != symbolic_name_to_rid("tip", "ci");
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
zNow = db_text("", "SELECT datetime(mtime,toLocal())"
" FROM event WHERE objid=%d", rid);
}else{
zCI = 0;
}
}
if( zCI==0 ){
rNow = db_double(0.0, "SELECT max(mtime) FROM event");
zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event");
}
/* Compute the title of the page */
blob_zero(&dirname);
if( zD ){
blob_append(&dirname, "within directory ", -1);
| > > > > > > > > > > > > > > > > > > > > | < | | < | | 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 |
int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
linkTrunk = trunkRid && rid != trunkRid;
linkTip = rid != symbolic_name_to_rid("tip", "ci");
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
zNow = db_text("", "SELECT datetime(mtime,toLocal())"
" FROM event WHERE objid=%d", rid);
isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0);
isBranchCI = branch_includes_uuid(zCI, zUuid);
Th_Store("current_checkin", zCI);
}else{
zCI = 0;
}
}
if( zCI==0 ){
rNow = db_double(0.0, "SELECT max(mtime) FROM event");
zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event");
}
assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) );
if( zD==0 ){
if( zCI ){
zHeader = mprintf("Top-level Files of %s", zCI);
}else{
zHeader = mprintf("All Top-level Files");
}
}else{
if( zCI ){
zHeader = mprintf("Files in %s/ of %s", zD, zCI);
}else{
zHeader = mprintf("All File in %s/", zD);
}
}
style_header("%s", zHeader);
fossil_free(zHeader);
/* Compute the title of the page */
blob_zero(&dirname);
if( zD ){
blob_append(&dirname, "within directory ", -1);
hyperlinked_path(zD, &dirname, zCI, "tree", zREx, 0);
if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE);
style_submenu_element("Top-Level", "%s",
url_render(&sURI, "name", 0, 0, 0));
}else if( zRE ){
blob_appendf(&dirname, "matching \"%s\"", zRE);
}
style_submenu_binary("mtime","Sort By Time","Sort By Filename", 0);
if( zCI ){
style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0));
if( nD==0 && !showDirOnly ){
style_submenu_element("File Ages", "%R/fileage?name=%T", zCI);
}
}
if( linkTrunk ){
style_submenu_element("Trunk", "%s",
url_render(&sURI, "ci", "trunk", 0, 0));
}
if( linkTip ){
|
| ︙ | ︙ | |||
736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 |
}
if( pRE && re_match(pRE, (const u8*)zName, -1)==0 ) continue;
tree_add_node(&sTree, zName, zUuid, mtime);
nFile++;
}
db_finalize(&q);
}
if( showDirOnly ){
for(nFile=0, p=sTree.pFirst; p; p=p->pNext){
if( p->pChild!=0 && p->nFullName>nD ) nFile++;
}
zObjType = "Folders";
}else{
zObjType = "Files";
}
| > | > > > > > > | | | < | > > < | 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 |
}
if( pRE && re_match(pRE, (const u8*)zName, -1)==0 ) continue;
tree_add_node(&sTree, zName, zUuid, mtime);
nFile++;
}
db_finalize(&q);
}
style_submenu_checkbox("nofiles", "Folders Only", 0, 0);
if( showDirOnly ){
for(nFile=0, p=sTree.pFirst; p; p=p->pNext){
if( p->pChild!=0 && p->nFullName>nD ) nFile++;
}
zObjType = "Folders";
}else{
zObjType = "Files";
}
if( zCI && strcmp(zCI,"tip")==0 ){
@ <h2>%s(zObjType) in the %z(href("%R/info?name=tip"))latest check-in</a>
}else if( isBranchCI ){
@ <h2>%s(zObjType) in the %z(href("%R/info?name=%T",zCI))latest check-in\
@ </a> for branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a>
if( blob_size(&dirname) ){
@ and %s(blob_str(&dirname))</h2>
}
}else if( zCI ){
@ <h2>%s(zObjType) for check-in \
@ %z(href("%R/info?name=%T",zCI))%h(zCI)</a></h2>
if( blob_size(&dirname) ){
@ and %s(blob_str(&dirname))</h2>
}
}else{
int n = db_int(0, "SELECT count(*) FROM plink");
@ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname))
}
if( useMtime ){
@ sorted by modification time</h2>
}else{
|
| ︙ | ︙ | |||
815 816 817 818 819 820 821 |
@ <ul id="dir%d(nDir)" class="collapsed">
}
nDir++;
}else if( !showDirOnly ){
const char *zFileClass = fileext_class(p->zName);
char *zLink;
if( zCI ){
| | | 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 |
@ <ul id="dir%d(nDir)" class="collapsed">
}
nDir++;
}else if( !showDirOnly ){
const char *zFileClass = fileext_class(p->zName);
char *zLink;
if( zCI ){
zLink = href("%R/file?name=%T&ci=%T",p->zFullName,zCI);
}else{
zLink = href("%R/finfo?name=%T",p->zFullName);
}
@ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline">
@ %z(zLink)%h(p->zName)</a>
if( p->mtime>0 ){
char *zAge = human_readable_age(rNow - p->mtime);
|
| ︙ | ︙ | |||
909 910 911 912 913 914 915 |
** temporary table named "fileage" that contains the file-id for each
** files, the pathname, the check-in where the file was added, and the
** mtime on that check-in. If zGlob and *zGlob then only files matching
** the given glob are computed.
*/
int compute_fileage(int vid, const char* zGlob){
Stmt q;
| | | 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 |
** temporary table named "fileage" that contains the file-id for each
** files, the pathname, the check-in where the file was added, and the
** mtime on that check-in. If zGlob and *zGlob then only files matching
** the given glob are computed.
*/
int compute_fileage(int vid, const char* zGlob){
Stmt q;
db_exec_sql(zComputeFileAgeSetup);
db_prepare(&q, zComputeFileAgeRun /*works-like:"constant"*/);
db_bind_int(&q, ":ckin", vid);
db_bind_text(&q, ":glob", zGlob && zGlob[0] ? zGlob : "*");
db_exec(&q);
db_finalize(&q);
return 0;
}
|
| ︙ | ︙ | |||
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 |
*/
void fileage_page(void){
int rid;
const char *zName;
const char *zGlob;
const char *zUuid;
const char *zNow; /* Time of check-in */
int showId = PB("showid");
Stmt q1, q2;
double baseTime;
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
if( exclude_spiders() ) return;
zName = P("name");
if( zName==0 ) zName = "tip";
rid = symbolic_name_to_rid(zName, "ci");
if( rid==0 ){
fossil_fatal("not a valid check-in: %s", zName);
}
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid);
zNow = db_text("", "SELECT datetime(mtime,toLocal()) FROM event"
" WHERE objid=%d", rid);
style_submenu_element("Tree-View", "%R/tree?ci=%T&mtime=1&type=tree", zName);
style_header("File Ages");
zGlob = P("glob");
compute_fileage(rid,zGlob);
db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);");
| > > > | > | > > > > | < | | < < | < > > | | | | 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 |
*/
void fileage_page(void){
int rid;
const char *zName;
const char *zGlob;
const char *zUuid;
const char *zNow; /* Time of check-in */
int isBranchCI; /* name= is a branch name */
int showId = PB("showid");
Stmt q1, q2;
double baseTime;
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
if( exclude_spiders() ) return;
zName = P("name");
if( zName==0 ) zName = "tip";
rid = symbolic_name_to_rid(zName, "ci");
if( rid==0 ){
fossil_fatal("not a valid check-in: %s", zName);
}
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
isBranchCI = branch_includes_uuid(zName,zUuid);
baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid);
zNow = db_text("", "SELECT datetime(mtime,toLocal()) FROM event"
" WHERE objid=%d", rid);
style_submenu_element("Tree-View", "%R/tree?ci=%T&mtime=1&type=tree", zName);
style_header("File Ages");
zGlob = P("glob");
compute_fileage(rid,zGlob);
db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);");
if( fossil_strcmp(zName,"tip")==0 ){
@ <h1>Files in the %z(href("%R/info?name=tip"))latest check-in</a>
}else if( isBranchCI ){
@ <h1>Files in the %z(href("%R/info?name=%T",zName))latest check-in</a>
@ of branch %z(href("%R/timeline?r=%T",zName))%h(zName)</a>
}else{
@ <h1>Files in check-in %z(href("%R/info?name=%T",zName))%h(zName)</a>
}
if( zGlob && zGlob[0] ){
@ that match "%h(zGlob)"
}
@ ordered by age</h1>
@
@ <p>File ages are expressed relative to the check-in time of
@ %z(href("%R/timeline?c=%t",zNow))%s(zNow)</a>.</p>
@
@ <div class='fileage'><table>
@ <tr><th>Age</th><th>Files</th><th>Check-in</th></tr>
db_prepare(&q1,
"SELECT event.mtime, event.objid, blob.uuid,\n"
" coalesce(event.ecomment,event.comment),\n"
" coalesce(event.euser,event.user),\n"
" coalesce((SELECT value FROM tagxref\n"
" WHERE tagtype>0 AND tagid=%d\n"
" AND rid=event.objid),'trunk')\n"
" FROM event, blob\n"
" WHERE event.objid IN (SELECT mid FROM fileage)\n"
" AND blob.rid=event.objid\n"
" ORDER BY event.mtime DESC;",
TAG_BRANCH
);
db_prepare(&q2,
"SELECT filename.name, fileage.fid\n"
" FROM fileage, filename\n"
" WHERE fileage.mid=:mid AND filename.fnid=fileage.fnid"
);
while( db_step(&q1)==SQLITE_ROW ){
double age = baseTime - db_column_double(&q1, 0);
int mid = db_column_int(&q1, 1);
const char *zUuid = db_column_text(&q1, 2);
const char *zComment = db_column_text(&q1, 3);
const char *zUser = db_column_text(&q1, 4);
const char *zBranch = db_column_text(&q1, 5);
char *zAge = human_readable_age(age);
@ <tr><td>%s(zAge)</td>
@ <td>
db_bind_int(&q2, ":mid", mid);
while( db_step(&q2)==SQLITE_ROW ){
const char *zFile = db_column_text(&q2,0);
@ %z(href("%R/file?name=%T&ci=%!S",zFile,zUuid))%h(zFile)</a> \
if( showId ){
int fid = db_column_int(&q2,1);
@ (%d(fid))<br />
}else{
@ </a><br />
}
}
db_reset(&q2);
@ </td>
@ <td>
@ %W(zComment)
@ (check-in: %z(href("%R/info/%!S",zUuid))%S(zUuid)</a>,
if( showId ){
@ id: %d(mid)
}
@ user: %z(href("%R/timeline?u=%t&c=%!S&nd",zUser,zUuid))%h(zUser)</a>,
@ branch: \
@ %z(href("%R/timeline?r=%t&c=%!S&nd",zBranch,zUuid))%h(zBranch)</a>)
@ </td></tr>
|
| ︙ | ︙ |
| ︙ | ︙ | |||
53 54 55 56 57 58 59 60 61 62 63 |
}
const char *builtin_text(const char *zFilename){
return (char*)builtin_file(zFilename, 0);
}
/*
** COMMAND: test-builtin-list
**
** List the names and sizes of all built-in resources.
*/
void test_builtin_list(void){
| > > > | > | > > > > | 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 |
}
const char *builtin_text(const char *zFilename){
return (char*)builtin_file(zFilename, 0);
}
/*
** COMMAND: test-builtin-list
**
** If -verbose is used, it outputs a line at the end
** with the total item count and size.
**
** List the names and sizes of all built-in resources.
*/
void test_builtin_list(void){
int i, size = 0;;
for(i=0; i<count(aBuiltinFiles); i++){
const int n = aBuiltinFiles[i].nByte;
fossil_print("%-30s %6d\n", aBuiltinFiles[i].zName,n);
size += n;
}
if(find_option("verbose","v",0)!=0){
fossil_print("%d entries totaling %d bytes\n", i, size);
}
}
/*
** WEBPAGE: test-builtin-files
**
** Show all built-in text files.
|
| ︙ | ︙ |
| ︙ | ︙ | |||
23 24 25 26 27 28 29 | /* ** SQL code used to initialize the schema of a bundle. ** ** The bblob.delta field can be an integer, a text string, or NULL. ** If an integer, then the corresponding blobid is the delta basis. ** If a text string, then that string is a SHA1 hash for the delta | | | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | /* ** SQL code used to initialize the schema of a bundle. ** ** The bblob.delta field can be an integer, a text string, or NULL. ** If an integer, then the corresponding blobid is the delta basis. ** If a text string, then that string is a SHA1 hash for the delta ** basis, which is presumably in the main repository. If NULL, then ** data contains content without delta compression. */ static const char zBundleInit[] = @ CREATE TABLE IF NOT EXISTS "%w".bconfig( @ bcname TEXT, @ bcvalue ANY @ ); |
| ︙ | ︙ | |||
528 529 530 531 532 533 534 |
fossil_fatal("incorrect hash for artifact %b", &h1);
}
blob_reset(&h1);
bag_remove(&busy, blobid);
db_finalize(&q);
}
| | | | 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 |
fossil_fatal("incorrect hash for artifact %b", &h1);
}
blob_reset(&h1);
bag_remove(&busy, blobid);
db_finalize(&q);
}
/* fossil bundle cat BUNDLE HASH...
**
** Write elements of a bundle on standard output
*/
static void bundle_cat_cmd(void){
int i;
Blob x;
verify_all_options();
if( g.argc<5 ) usage("cat BUNDLE HASH...");
bundle_attach_file(g.argv[3], "b1", 1);
blob_zero(&x);
for(i=4; i<g.argc; i++){
int blobid = db_int(0,"SELECT blobid FROM bblob WHERE uuid LIKE '%q%%'",
g.argv[i]);
if( blobid==0 ){
fossil_fatal("no such artifact in bundle: %s", g.argv[i]);
|
| ︙ | ︙ | |||
718 719 720 721 722 723 724 | } /* ** COMMAND: bundle ** ** Usage: %fossil bundle SUBCOMMAND ARGS... ** | | | | | | | | | | | 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 | } /* ** COMMAND: bundle ** ** Usage: %fossil bundle SUBCOMMAND ARGS... ** ** > fossil bundle append BUNDLE FILE... ** ** Add files named on the command line to BUNDLE. This subcommand has ** little practical use and is mostly intended for testing. ** ** > fossil bundle cat BUNDLE HASH... ** ** Extract one or more artifacts from the bundle and write them ** consecutively on standard output. This subcommand was designed ** for testing and introspection of bundles and is not something ** commonly used. ** ** > fossil bundle export BUNDLE ?OPTIONS? ** ** Generate a new bundle, in the file named BUNDLE, that contains a ** subset of the check-ins in the repository (usually a single branch) ** described by the --branch, --from, --to, and/or --checkin options, ** at least one of which is required. If BUNDLE already exists, the ** specified content is added to the bundle. ** ** --branch BRANCH Package all check-ins on BRANCH. ** --from TAG1 --to TAG2 Package check-ins between TAG1 and TAG2. ** --checkin TAG Package the single check-in TAG ** --standalone Do no use delta-encoding against ** artifacts not in the bundle ** ** > fossil bundle extend BUNDLE ** ** The BUNDLE must already exist. This subcommand adds to the bundle ** any check-ins that are descendants of check-ins already in the bundle, ** and any tags that apply to artifacts in the bundle. ** ** > fossil bundle import BUNDLE ?--publish? ** ** Import all content from BUNDLE into the repository. By default, the ** imported files are private and will not sync. Use the --publish ** option to make the import public. ** ** > fossil bundle ls BUNDLE ** ** List the contents of BUNDLE on standard output ** ** > fossil bundle purge BUNDLE ** ** Remove from the repository all files that are used exclusively ** by check-ins in BUNDLE. This has the effect of undoing a ** "fossil bundle import". ** ** SUMMARY: ** fossil bundle append BUNDLE FILE... Add files to BUNDLE ** fossil bundle cat BUNDLE HASH... Extract file from BUNDLE ** fossil bundle export BUNDLE ?OPTIONS? Create a new BUNDLE ** --branch BRANCH --from TAG1 --to TAG2 Check-ins to include ** --checkin TAG Use only check-in TAG ** --standalone Omit dependencies ** fossil bundle extend BUNDLE Update with newer content ** fossil bundle import BUNDLE ?OPTIONS? Import a bundle ** --publish Publish the import |
| ︙ | ︙ |
| ︙ | ︙ | |||
98 99 100 101 102 103 104 |
** if "u" is present and "developer" if "v" is present.
*/
void capability_expand(CapabilityString *pIn){
static char *zNobody = 0;
static char *zAnon = 0;
static char *zReader = 0;
static char *zDev = 0;
| | | 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
** if "u" is present and "developer" if "v" is present.
*/
void capability_expand(CapabilityString *pIn){
static char *zNobody = 0;
static char *zAnon = 0;
static char *zReader = 0;
static char *zDev = 0;
static char *zAdmin = "bcdefghijklmnopqrtwz234567AD";
int doneV = 0;
if( pIn==0 ){
fossil_free(zNobody); zNobody = 0;
fossil_free(zAnon); zAnon = 0;
fossil_free(zReader); zReader = 0;
fossil_free(zDev); zDev = 0;
|
| ︙ | ︙ | |||
237 238 239 240 241 242 243 |
unsigned nUser; /* Number of users with this capability */
char *zAbbrev; /* Abbreviated mnemonic name */
char *zOneLiner; /* One-line summary */
} aCap[] = {
{ 'a', CAPCLASS_SUPER, 0,
"Admin", "Create and delete users" },
{ 'b', CAPCLASS_WIKI|CAPCLASS_TKT, 0,
| | < > | > > | 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
unsigned nUser; /* Number of users with this capability */
char *zAbbrev; /* Abbreviated mnemonic name */
char *zOneLiner; /* One-line summary */
} aCap[] = {
{ 'a', CAPCLASS_SUPER, 0,
"Admin", "Create and delete users" },
{ 'b', CAPCLASS_WIKI|CAPCLASS_TKT, 0,
"Attach", "Add attachments to wiki or tickets" },
{ 'c', CAPCLASS_TKT, 0,
"Append-Tkt", "Append to existing tickets" },
/*
** d unused since fork from CVSTrac;
** see https://fossil-scm.org/forum/forumpost/43c78f4bef
*/
{ 'e', CAPCLASS_DATA, 0,
"View-PII", "View sensitive info such as email addresses" },
{ 'f', CAPCLASS_WIKI, 0,
"New-Wiki", "Create new wiki pages" },
{ 'g', CAPCLASS_DATA, 0,
"Clone", "Clone the repository" },
{ 'h', CAPCLASS_OTHER, 0,
|
| ︙ | ︙ | |||
295 296 297 298 299 300 301 |
{ '3', CAPCLASS_FORUM, 0,
"Forum-Write", "Create new forum messages" },
{ '4', CAPCLASS_FORUM, 0,
"Forum-Trusted", "Create forum messages that bypass moderation" },
{ '5', CAPCLASS_FORUM|CAPCLASS_SUPER, 0,
"Forum-Mod", "Moderator for forum messages" },
{ '6', CAPCLASS_FORUM|CAPCLASS_SUPER, 0,
| | | 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
{ '3', CAPCLASS_FORUM, 0,
"Forum-Write", "Create new forum messages" },
{ '4', CAPCLASS_FORUM, 0,
"Forum-Trusted", "Create forum messages that bypass moderation" },
{ '5', CAPCLASS_FORUM|CAPCLASS_SUPER, 0,
"Forum-Mod", "Moderator for forum messages" },
{ '6', CAPCLASS_FORUM|CAPCLASS_SUPER, 0,
"Forum-Admin", "Grant capability '4' to other users" },
{ '7', CAPCLASS_ALERT, 0,
"Alerts", "Sign up for email alerts" },
{ 'A', CAPCLASS_ALERT|CAPCLASS_SUPER, 0,
"Announce", "Send announcements to all subscribers" },
{ 'D', CAPCLASS_OTHER, 0,
"Debug", "Enable debugging features" },
};
|
| ︙ | ︙ | |||
357 358 359 360 361 362 363 364 365 366 |
/*
** Generate a "capability summary table" that shows the major capabilities
** against the various user categories.
*/
void capability_summary(void){
Stmt q;
db_prepare(&q,
"WITH t(id,seq) AS (VALUES('nobody',1),('anonymous',2),('reader',3),"
"('developer',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 |
/*
** Generate a "capability summary table" that shows the major capabilities
** against the various user categories.
*/
void capability_summary(void){
Stmt q;
CapabilityString *pCap;
char *zSelfCap;
char *zPubPages = db_get("public-pages",0);
int hasPubPages = zPubPages && zPubPages[0];
pCap = capability_add(0, db_get("default-perms","u"));
capability_expand(pCap);
zSelfCap = capability_string(pCap);
capability_free(pCap);
db_prepare(&q,
"WITH t(id,seq) AS (VALUES('nobody',1),('anonymous',2),('reader',3),"
"('developer',4))"
" SELECT id, CASE WHEN user.login='nobody' THEN user.cap"
" ELSE fullcap(user.cap) END,seq,1"
" FROM t LEFT JOIN user ON t.id=user.login"
" UNION ALL"
" SELECT 'Public Pages', %Q, 100, %d"
" UNION ALL"
" SELECT 'New User Default', %Q, 110, 1"
" UNION ALL"
" SELECT 'Regular User', fullcap(capunion(cap)), 200, count(*) FROM user"
" WHERE cap NOT GLOB '*[as]*' AND login NOT IN (SELECT id FROM t)"
" UNION ALL"
" SELECT 'Adminstrator', fullcap(capunion(cap)), 300, count(*) FROM user"
" WHERE cap GLOB '*[as]*'"
" ORDER BY 3 ASC",
zSelfCap, hasPubPages, zSelfCap
);
@ <table id='capabilitySummary' cellpadding="0" cellspacing="0" border="1">
@ <tr><th> <th>Code<th>Forum<th>Tickets<th>Wiki\
@ <th>Unversioned Content</th></tr>
while( db_step(&q)==SQLITE_ROW ){
const char *zId = db_column_text(&q, 0);
const char *zCap = db_column_text(&q, 1);
int n = db_column_int(&q, 3);
int eType;
static const char *const azType[] = { "off", "read", "write" };
static const char *const azClass[] =
{ "capsumOff", "capsumRead", "capsumWrite" };
if( n==0 ) continue;
/* Code */
if( db_column_int(&q,2)<10 ){
@ <tr><th align="right"><tt>"%h(zId)"</tt></th>
}else if( n>1 ){
|
| ︙ | ︙ | |||
414 415 416 417 418 419 420 |
eType = 1;
}else{
eType = 0;
}
@ <td class="%s(azClass[eType])">%s(azType[eType])</td>
/* Ticket */
| | | 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 |
eType = 1;
}else{
eType = 0;
}
@ <td class="%s(azClass[eType])">%s(azType[eType])</td>
/* Ticket */
if( sqlite3_strglob("*[ascnqtw]*",zCap)==0 ){
eType = 2;
}else if( sqlite3_strglob("*r*",zCap)==0 ){
eType = 1;
}else{
eType = 0;
}
@ <td class="%s(azClass[eType])">%s(azType[eType])</td>
|
| ︙ | ︙ |
| ︙ | ︙ | |||
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 |
@ </pre>
@ Enter security code shown above:
@ <input type="hidden" name="captchaseed" value="%u(uSeed)" />
@ <input type="text" name="captcha" size=8 />
if( showButton ){
@ <input type="submit" value="Submit">
}
@ </td></tr></table></div>
}
/*
** WEBPAGE: test-captcha
** Test the captcha-generator by rendering the value of the name= query
** parameter using ascii-art. If name= is omitted, show a random 16-digit
** hexadecimal number.
*/
| > > > > > > > > > > > > > > > > > > > > | 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 |
@ </pre>
@ Enter security code shown above:
@ <input type="hidden" name="captchaseed" value="%u(uSeed)" />
@ <input type="text" name="captcha" size=8 />
if( showButton ){
@ <input type="submit" value="Submit">
}
@ <br/>\
captcha_speakit_button(uSeed, 0);
@ </td></tr></table></div>
}
/*
** Add a "Speak the captcha" button.
*/
void captcha_speakit_button(unsigned int uSeed, const char *zMsg){
if( zMsg==0 ) zMsg = "Speak the text";
@ <input aria-label="%h(zMsg)" type="button" value="%h(zMsg)" \
@ id="speakthetext">
@ <script nonce="%h(style_nonce())">
@ document.getElementById("speakthetext").onclick = function(){
@ var audio = window.fossilAudioCaptcha \
@ || new Audio("%R/captcha-audio/%u(uSeed)");
@ window.fossilAudioCaptcha = audio;
@ audio.currentTime = 0;
@ audio.play();
@ }
@ </script>
}
/*
** WEBPAGE: test-captcha
** Test the captcha-generator by rendering the value of the name= query
** parameter using ascii-art. If name= is omitted, show a random 16-digit
** hexadecimal number.
*/
|
| ︙ | ︙ | |||
606 607 608 609 610 611 612 | cgi_query_parameters_to_hidden(); @ <p>Please demonstrate that you are human, not a spider or robot</p> captcha_generate(1); @ </form> style_footer(); return 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 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 |
cgi_query_parameters_to_hidden();
@ <p>Please demonstrate that you are human, not a spider or robot</p>
captcha_generate(1);
@ </form>
style_footer();
return 1;
}
/*
** Generate a WAV file that reads aloud the hex digits given by
** zHex.
*/
static void captcha_wav(const char *zHex, Blob *pOut){
int i;
const int szWavHdr = 44;
blob_init(pOut, 0, 0);
blob_resize(pOut, szWavHdr); /* Space for the WAV header */
pOut->nUsed = szWavHdr;
memset(pOut->aData, 0, szWavHdr);
if( zHex==0 || zHex[0]==0 ) zHex = "0";
for(i=0; zHex[i]; i++){
int v = hex_digit_value(zHex[i]);
int sz;
int nData;
const unsigned char *pData;
char zSoundName[50];
sqlite3_snprintf(sizeof(zSoundName),zSoundName,"sounds/%c.wav",
"0123456789abcdef"[v]);
/* Extra silence in between letters */
if( i>0 ){
int nQuiet = 3000;
blob_resize(pOut, pOut->nUsed+nQuiet);
memset(pOut->aData+pOut->nUsed-nQuiet, 0x80, nQuiet);
}
pData = builtin_file(zSoundName, &sz);
nData = sz - szWavHdr;
blob_resize(pOut, pOut->nUsed+nData);
memcpy(pOut->aData+pOut->nUsed-nData, pData+szWavHdr, nData);
if( zHex[i+1]==0 ){
int len = pOut->nUsed + 36;
memcpy(pOut->aData, pData, szWavHdr);
pOut->aData[4] = (char)(len&0xff);
pOut->aData[5] = (char)((len>>8)&0xff);
pOut->aData[6] = (char)((len>>16)&0xff);
pOut->aData[7] = (char)((len>>24)&0xff);
len = pOut->nUsed;
pOut->aData[40] = (char)(len&0xff);
pOut->aData[41] = (char)((len>>8)&0xff);
pOut->aData[42] = (char)((len>>16)&0xff);
pOut->aData[43] = (char)((len>>24)&0xff);
}
}
}
/*
** WEBPAGE: /captcha-audio
**
** Return a WAV file that pronounces the digits of the captcha that
** is determined by the seed given in the name= query parameter.
*/
void captcha_wav_page(void){
const char *zSeed = P("name");
const char *zDecode = captcha_decode((unsigned int)atoi(zSeed));
Blob audio;
captcha_wav(zDecode, &audio);
cgi_set_content_type("audio/wav");
cgi_set_content(&audio);
}
/*
** WEBPAGE: /test-captcha-audio
**
** Return a WAV file that pronounces the hex digits of the name=
** query parameter.
*/
void captcha_test_wav_page(void){
const char *zSeed = P("name");
Blob audio;
captcha_wav(zSeed, &audio);
cgi_set_content_type("audio/wav");
cgi_set_content(&audio);
}
|
| ︙ | ︙ | |||
62 63 64 65 66 67 68 69 70 71 72 73 74 75 | #define PD(x,y) cgi_parameter((x),(y)) #define PT(x) cgi_parameter_trimmed((x),0) #define PDT(x,y) cgi_parameter_trimmed((x),(y)) #define PB(x) cgi_parameter_boolean(x) #define PCK(x) cgi_parameter_checked(x,1) #define PIF(x,y) cgi_parameter_checked(x,y) /* ** Destinations for output text. */ #define CGI_HEADER 0 #define CGI_BODY 1 | > > > > > > > > > > > | 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 | #define PD(x,y) cgi_parameter((x),(y)) #define PT(x) cgi_parameter_trimmed((x),0) #define PDT(x,y) cgi_parameter_trimmed((x),(y)) #define PB(x) cgi_parameter_boolean(x) #define PCK(x) cgi_parameter_checked(x,1) #define PIF(x,y) cgi_parameter_checked(x,y) /* ** Shortcut for the cgi_printf() routine. Instead of using the ** ** @ ... ** ** notation provided by the translate.c utility, you can also ** optionally use: ** ** CX(...) */ #define CX cgi_printf /* ** Destinations for output text. */ #define CGI_HEADER 0 #define CGI_BODY 1 |
| ︙ | ︙ | |||
142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
/*
** Return a pointer to the CGI output blob.
*/
Blob *cgi_output_blob(void){
return pContent;
}
/*
** Combine the header and body of the CGI into a single string.
*/
static void cgi_combine_header_and_body(void){
int size = blob_size(&cgiContent[1]);
if( size>0 ){
| > > > > > > > | 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
/*
** Return a pointer to the CGI output blob.
*/
Blob *cgi_output_blob(void){
return pContent;
}
/*
** Return complete text of the output header
*/
const char *cgi_header(void){
return blob_str(&cgiContent[0]);
}
/*
** Combine the header and body of the CGI into a single string.
*/
static void cgi_combine_header_and_body(void){
int size = blob_size(&cgiContent[1]);
if( size>0 ){
|
| ︙ | ︙ | |||
165 166 167 168 169 170 171 | cgi_combine_header_and_body(); return blob_buffer(&cgiContent[0]); } /* ** Additional information used to form the HTTP reply */ | | | > > | 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
cgi_combine_header_and_body();
return blob_buffer(&cgiContent[0]);
}
/*
** Additional information used to form the HTTP reply
*/
static const char *zContentType = "text/html"; /* Content type of the reply */
static const char *zReplyStatus = "OK"; /* Reply status description */
static int iReplyStatus = 200; /* Reply status code */
static Blob extraHeader = BLOB_INITIALIZER; /* Extra header text */
static int rangeStart = 0; /* Start of Range: */
static int rangeEnd = 0; /* End of Range: plus 1 */
/*
** Set the reply content type
*/
void cgi_set_content_type(const char *zType){
zContentType = mprintf("%s", zType);
}
|
| ︙ | ︙ | |||
200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
}
/*
** Append text to the header of an HTTP reply
*/
void cgi_append_header(const char *zLine){
blob_append(&extraHeader, zLine, -1);
}
/*
** Set a cookie by queuing up the appropriate HTTP header output. If
** !g.isHTTP, this is a no-op.
**
** Zero lifetime implies a session cookie.
| > > > > > > | 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
}
/*
** Append text to the header of an HTTP reply
*/
void cgi_append_header(const char *zLine){
blob_append(&extraHeader, zLine, -1);
}
void cgi_printf_header(const char *zLine, ...){
va_list ap;
va_start(ap, zLine);
blob_vappendf(&extraHeader, zLine, ap);
va_end(ap);
}
/*
** Set a cookie by queuing up the appropriate HTTP header output. If
** !g.isHTTP, this is a no-op.
**
** Zero lifetime implies a session cookie.
|
| ︙ | ︙ | |||
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
int total_size;
if( iReplyStatus<=0 ){
iReplyStatus = 200;
zReplyStatus = "OK";
}
if( g.fullHttpReply ){
fprintf(g.httpOut, "HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus);
fprintf(g.httpOut, "Date: %s\r\n", cgi_rfc822_datestamp(time(0)));
fprintf(g.httpOut, "Connection: close\r\n");
fprintf(g.httpOut, "X-UA-Compatible: IE=edge\r\n");
}else{
fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus);
}
if( g.isConst ){
/* isConst means that the reply is guaranteed to be invariant, even
** after configuration changes and/or Fossil binary recompiles. */
fprintf(g.httpOut, "Cache-Control: max-age=31536000\r\n");
| > > > > > > > > | | 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 |
int total_size;
if( iReplyStatus<=0 ){
iReplyStatus = 200;
zReplyStatus = "OK";
}
if( g.fullHttpReply ){
if( rangeEnd>0
&& iReplyStatus==200
&& fossil_strcmp(P("REQUEST_METHOD"),"GET")==0
){
iReplyStatus = 206;
zReplyStatus = "Partial Content";
}
fprintf(g.httpOut, "HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus);
fprintf(g.httpOut, "Date: %s\r\n", cgi_rfc822_datestamp(time(0)));
fprintf(g.httpOut, "Connection: close\r\n");
fprintf(g.httpOut, "X-UA-Compatible: IE=edge\r\n");
}else{
assert( rangeEnd==0 );
fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus);
}
if( g.isConst ){
/* isConst means that the reply is guaranteed to be invariant, even
** after configuration changes and/or Fossil binary recompiles. */
fprintf(g.httpOut, "Cache-Control: max-age=31536000\r\n");
}else if( etag_tag()[0]!=0 ){
fprintf(g.httpOut, "ETag: %s\r\n", etag_tag());
fprintf(g.httpOut, "Cache-Control: max-age=%d\r\n", etag_maxage());
}else{
fprintf(g.httpOut, "Cache-control: no-cache\r\n");
}
if( etag_mtime()>0 ){
fprintf(g.httpOut, "Last-Modified: %s\r\n",
|
| ︙ | ︙ | |||
304 305 306 307 308 309 310 | ** These headers are probably best added by the web server hosting fossil as ** a CGI script. */ /* Content intended for logged in users should only be cached in ** the browser, not some shared location. */ | > | | | | | < | > > > > > | > | > > > > > > | > > | | 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 |
** These headers are probably best added by the web server hosting fossil as
** a CGI script.
*/
/* Content intended for logged in users should only be cached in
** the browser, not some shared location.
*/
if( iReplyStatus!=304 ) {
fprintf(g.httpOut, "Content-Type: %s; charset=utf-8\r\n", zContentType);
if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){
cgi_combine_header_and_body();
blob_compress(&cgiContent[0], &cgiContent[0]);
}
if( is_gzippable() && iReplyStatus!=206 ){
int i;
gzip_begin(0);
for( i=0; i<2; i++ ){
int size = blob_size(&cgiContent[i]);
if( size>0 ) gzip_step(blob_buffer(&cgiContent[i]), size);
blob_reset(&cgiContent[i]);
}
gzip_finish(&cgiContent[0]);
fprintf(g.httpOut, "Content-Encoding: gzip\r\n");
fprintf(g.httpOut, "Vary: Accept-Encoding\r\n");
}
total_size = blob_size(&cgiContent[0]) + blob_size(&cgiContent[1]);
if( iReplyStatus==206 ){
fprintf(g.httpOut, "Content-Range: bytes %d-%d/%d\r\n",
rangeStart, rangeEnd-1, total_size);
total_size = rangeEnd - rangeStart;
}
fprintf(g.httpOut, "Content-Length: %d\r\n", total_size);
}else{
total_size = 0;
}
fprintf(g.httpOut, "\r\n");
if( total_size>0
&& iReplyStatus!=304
&& fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0
){
int i, size;
for(i=0; i<2; i++){
size = blob_size(&cgiContent[i]);
if( size<=rangeStart ){
rangeStart -= size;
}else{
int n = size - rangeStart;
if( n>total_size ){
n = total_size;
}
fwrite(blob_buffer(&cgiContent[i])+rangeStart, 1, n, g.httpOut);
rangeStart = 0;
total_size -= n;
}
}
}
fflush(g.httpOut);
CGIDEBUG(("-------- END cgi ---------\n"));
/* After the webpage has been sent, do any useful background
** processing.
*/
g.cgiOutput = 2;
if( g.db!=0 && iReplyStatus==200 ){
backoffice_check_if_needed();
|
| ︙ | ︙ | |||
394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
}
NORETURN void cgi_redirectf(const char *zFormat, ...){
va_list ap;
va_start(ap, zFormat);
cgi_redirect(vmprintf(zFormat, ap));
va_end(ap);
}
/*
** Return the URL for the caller. This is obtained from either the
** referer CGI parameter, if it exists, or the HTTP_REFERER HTTP parameter.
** If neither exist, return zDefault.
*/
const char *cgi_referer(const char *zDefault){
| > > > > > > > > > > > > > > > > > > > > > | 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 |
}
NORETURN void cgi_redirectf(const char *zFormat, ...){
va_list ap;
va_start(ap, zFormat);
cgi_redirect(vmprintf(zFormat, ap));
va_end(ap);
}
/*
** Add a "Content-disposition: attachment; filename=%s" header to the reply.
*/
void cgi_content_disposition_filename(const char *zFilename){
char *z;
int i, n;
/* 0123456789 123456789 123456789 123456789 123456*/
z = mprintf("Content-Disposition: attachment; filename=\"%s\";\r\n",
file_tail(zFilename));
n = (int)strlen(z);
for(i=43; i<n-4; i++){
char c = z[i];
if( fossil_isalnum(c) ) continue;
if( c=='.' || c=='-' || c=='/' ) continue;
z[i] = '_';
}
cgi_append_header(z);
fossil_free(z);
}
/*
** Return the URL for the caller. This is obtained from either the
** referer CGI parameter, if it exists, or the HTTP_REFERER HTTP parameter.
** If neither exist, return zDefault.
*/
const char *cgi_referer(const char *zDefault){
|
| ︙ | ︙ | |||
480 481 482 483 484 485 486 487 488 489 490 491 492 493 |
nUsedQP++;
sortQP = 1;
}
/*
** 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.
**
** 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), 0);
}
| > > > > > > > > > > > > > > > > > > > > > | 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 |
nUsedQP++;
sortQP = 1;
}
/*
** 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 will be modified to be an
** all lowercase string.
**
** zName and zValue are not copied and must not change or be
** deallocated after this routine returns. This routine changes
** all ASCII alphabetic characters in zName to lower case. The
** caller must not change them back.
*/
void cgi_set_parameter_nocopy_tolower(
char *zName,
const char *zValue,
int isQP
){
int i;
for(i=0; zName[i]; i++){ zName[i] = fossil_tolower(zName[i]); }
cgi_set_parameter_nocopy(zName, zValue, isQP);
}
/*
** 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.
**
** 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), 0);
}
|
| ︙ | ︙ | |||
514 515 516 517 518 519 520 521 522 523 524 525 526 527 |
if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){
aParamQP[i].zValue = zValue;
assert( aParamQP[i].isQP );
return;
}
}
cgi_set_parameter_nocopy(zName, zValue, 1);
}
/*
** Delete a parameter.
*/
void cgi_delete_parameter(const char *zName){
int i;
| > > > > > | 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 |
if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){
aParamQP[i].zValue = zValue;
assert( aParamQP[i].isQP );
return;
}
}
cgi_set_parameter_nocopy(zName, zValue, 1);
}
void cgi_replace_query_parameter_tolower(char *zName, const char *zValue){
int i;
for(i=0; zName[i]; i++){ zName[i] = fossil_tolower(zName[i]); }
cgi_replace_query_parameter(zName, zValue);
}
/*
** Delete a parameter.
*/
void cgi_delete_parameter(const char *zName){
int i;
|
| ︙ | ︙ | |||
552 553 554 555 556 557 558 |
/*
** 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), 0);
}
| < | 647 648 649 650 651 652 653 654 655 656 657 658 659 660 |
/*
** 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), 0);
}
/*
** Add a list of query parameters or cookies to the parameter set.
**
** Each parameter is of the form NAME=VALUE. Both the NAME and the
** VALUE may be url-encoded ("+" for space, "%HH" for other special
** characters). But this routine assumes that NAME contains no
|
| ︙ | ︙ | |||
606 607 608 609 610 611 612 |
z++;
}
dehttpize(zValue);
}else{
if( *z ){ *z++ = 0; }
zValue = "";
}
| > | | > > > | 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 |
z++;
}
dehttpize(zValue);
}else{
if( *z ){ *z++ = 0; }
zValue = "";
}
if( zName[0] && fossil_no_strange_characters(zName+1) ){
if( fossil_islower(zName[0]) ){
cgi_set_parameter_nocopy(zName, zValue, isQP);
}else if( fossil_isupper(zName[0]) ){
cgi_set_parameter_nocopy_tolower(zName, zValue, isQP);
}
}
#ifdef FOSSIL_ENABLE_JSON
json_setenv( zName, cson_value_new_string(zValue,strlen(zValue)) );
#endif /* FOSSIL_ENABLE_JSON */
}
}
|
| ︙ | ︙ | |||
644 645 646 647 648 649 650 | *pz = &z[i]; *pLen -= i; return z; } /* ** The input *pz points to content that is terminated by a "\r\n" | | | | | | | | | 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 |
*pz = &z[i];
*pLen -= i;
return z;
}
/*
** The input *pz points to content that is terminated by a "\r\n"
** followed by the boundary marker zBoundary. An extra "--" may or
** may not be appended to the boundary marker. There are *pLen characters
** in *pz.
**
** This routine adds a "\000" to the end of the content (overwriting
** the "\r\n") and returns a pointer to the content. The *pz input
** is adjusted to point to the first line following the boundary.
** The length of the content is stored in *pnContent.
*/
static char *get_bounded_content(
char **pz, /* Content taken from here */
int *pLen, /* Number of bytes of data in (*pz)[] */
char *zBoundary, /* Boundary text marking the end of content */
int *pnContent /* Write the size of the content here */
){
char *z = *pz;
int len = *pLen;
int i;
int nBoundary = strlen(zBoundary);
*pnContent = len;
for(i=0; i<len; i++){
if( z[i]=='\n' && strncmp(zBoundary, &z[i+1], nBoundary)==0 ){
if( i>0 && z[i-1]=='\r' ) i--;
z[i] = 0;
*pnContent = i;
i += nBoundary;
break;
}
}
*pz = &z[i];
get_line_from_string(pz, pLen);
return z;
}
|
| ︙ | ︙ | |||
738 739 740 741 742 743 744 |
** not copied. The calling function must not deallocate or modify
** "z" after this routine finishes or it could corrupt the parameter
** table.
*/
static void process_multipart_form_data(char *z, int len){
char *zLine;
int nArg, i;
| | | | | | > | | | | > > > > > > > > | | > > > > > | | > > > > | 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 |
** not copied. The calling function must not deallocate or modify
** "z" after this routine finishes or it could corrupt the parameter
** table.
*/
static void process_multipart_form_data(char *z, int len){
char *zLine;
int nArg, i;
char *zBoundary;
char *zValue;
char *zName = 0;
int showBytes = 0;
char *azArg[50];
zBoundary = get_line_from_string(&z, &len);
if( zBoundary==0 ) return;
while( (zLine = get_line_from_string(&z, &len))!=0 ){
if( zLine[0]==0 ){
int nContent = 0;
zValue = get_bounded_content(&z, &len, zBoundary, &nContent);
if( zName && zValue ){
if( fossil_islower(zName[0]) ){
cgi_set_parameter_nocopy(zName, zValue, 1);
if( showBytes ){
cgi_set_parameter_nocopy(mprintf("%s:bytes", zName),
mprintf("%d",nContent), 1);
}
}else if( fossil_isupper(zName[0]) ){
cgi_set_parameter_nocopy_tolower(zName, zValue, 1);
if( showBytes ){
cgi_set_parameter_nocopy_tolower(mprintf("%s:bytes", zName),
mprintf("%d",nContent), 1);
}
}
}
zName = 0;
showBytes = 0;
}else{
nArg = tokenize_line(zLine, count(azArg), azArg);
for(i=0; i<nArg; i++){
int c = fossil_tolower(azArg[i][0]);
int n = strlen(azArg[i]);
if( c=='c' && sqlite3_strnicmp(azArg[i],"content-disposition:",n)==0 ){
i++;
}else if( c=='n' && sqlite3_strnicmp(azArg[i],"name=",n)==0 ){
zName = azArg[++i];
}else if( c=='f' && sqlite3_strnicmp(azArg[i],"filename=",n)==0 ){
char *z = azArg[++i];
if( zName && z ){
if( fossil_islower(zName[0]) ){
cgi_set_parameter_nocopy(mprintf("%s:filename",zName), z, 1);
}else if( fossil_isupper(zName[0]) ){
cgi_set_parameter_nocopy_tolower(mprintf("%s:filename",zName),
z, 1);
}
}
showBytes = 1;
}else if( c=='c' && sqlite3_strnicmp(azArg[i],"content-type:",n)==0 ){
char *z = azArg[++i];
if( zName && z ){
if( fossil_islower(zName[0]) ){
cgi_set_parameter_nocopy(mprintf("%s:mimetype",zName), z, 1);
}else if( fossil_isupper(zName[0]) ){
cgi_set_parameter_nocopy_tolower(mprintf("%s:mimetype",zName),
z, 1);
}
}
}
}
}
}
}
|
| ︙ | ︙ | |||
936 937 938 939 940 941 942 943 944 945 946 947 948 |
**
** SCGI typically omits PATH_INFO. CGI sometimes omits REQUEST_URI and
** PATH_INFO when it is empty.
*/
void cgi_init(void){
char *z;
const char *zType;
int len;
const char *zRequestUri = cgi_parameter("REQUEST_URI",0);
const char *zScriptName = cgi_parameter("SCRIPT_NAME",0);
const char *zPathInfo = cgi_parameter("PATH_INFO",0);
#ifdef FOSSIL_ENABLE_JSON
| > > > > > | > > > > > > > > > > > > > > > | > > > > > > > > > > > > | > > > > > > > | > < < < | > | | < < < | < < | < < < < | 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 1166 |
**
** SCGI typically omits PATH_INFO. CGI sometimes omits REQUEST_URI and
** PATH_INFO when it is empty.
*/
void cgi_init(void){
char *z;
const char *zType;
char *zSemi;
int len;
const char *zRequestUri = cgi_parameter("REQUEST_URI",0);
const char *zScriptName = cgi_parameter("SCRIPT_NAME",0);
const char *zPathInfo = cgi_parameter("PATH_INFO",0);
#ifdef _WIN32
const char *zServerSoftware = cgi_parameter("SERVER_SOFTWARE",0);
#endif
#ifdef FOSSIL_ENABLE_JSON
int noJson = P("no_json")!=0;
if( noJson==0 ){ json_main_bootstrap(); }
#endif
g.isHTTP = 1;
cgi_destination(CGI_BODY);
if( zScriptName==0 ) malformed_request("missing SCRIPT_NAME");
#ifdef _WIN32
/* The Microsoft IIS web server does not define REQUEST_URI, instead it uses
** PATH_INFO for virtually the same purpose. Define REQUEST_URI the same as
** PATH_INFO and redefine PATH_INFO with SCRIPT_NAME removed from the
** beginning. */
if( zServerSoftware && strstr(zServerSoftware, "Microsoft-IIS") ){
int i, j;
cgi_set_parameter("REQUEST_URI", zPathInfo);
for(i=0; zPathInfo[i]==zScriptName[i] && zPathInfo[i]; i++){}
for(j=i; zPathInfo[j] && zPathInfo[j]!='?'; j++){}
zPathInfo = mprintf("%.*s", j-i, zPathInfo+i);
cgi_replace_parameter("PATH_INFO", zPathInfo);
}
#endif
if( zRequestUri==0 ){
const char *z = zPathInfo;
if( zPathInfo==0 ){
malformed_request("missing PATH_INFO and/or REQUEST_URI");
}
if( z[0]=='/' ) z++;
zRequestUri = mprintf("%s/%s", zScriptName, z);
cgi_set_parameter("REQUEST_URI", zRequestUri);
}
if( zPathInfo==0 ){
int i, j;
for(i=0; zRequestUri[i]==zScriptName[i] && zRequestUri[i]; i++){}
for(j=i; zRequestUri[j] && zRequestUri[j]!='?'; j++){}
zPathInfo = mprintf("%.*s", j-i, zRequestUri+i);
cgi_set_parameter("PATH_INFO", zPathInfo);
}
#ifdef FOSSIL_ENABLE_JSON
if(strncmp("/json",zPathInfo,5)==0
&& (zPathInfo[5]==0 || zPathInfo[5]=='/')){
/* We need to change some following behaviour depending on whether
** we are operating in JSON mode or not. We cannot, however, be
** certain whether we should/need to be in JSON mode until the
** PATH_INFO is set up.
*/
g.json.isJsonMode = 1;
}else{
assert(!g.json.isJsonMode &&
"Internal misconfiguration of g.json.isJsonMode");
}
#endif
z = (char*)P("HTTP_COOKIE");
if( z ){
z = mprintf("%s",z);
add_param_list(z, ';');
}
z = (char*)P("QUERY_STRING");
if( z ){
z = mprintf("%s",z);
add_param_list(z, '&');
}
z = (char*)P("REMOTE_ADDR");
if( z ){
g.zIpAddr = mprintf("%s", z);
}
len = atoi(PD("CONTENT_LENGTH", "0"));
zType = P("CONTENT_TYPE");
zSemi = zType ? strchr(zType, ';') : 0;
if( zSemi ){
g.zContentType = mprintf("%.*s", (int)(zSemi-zType), zType);
zType = g.zContentType;
}else{
g.zContentType = zType;
}
blob_zero(&g.cgiIn);
if( len>0 && zType ){
if( fossil_strcmp(zType, "application/x-fossil")==0 ){
blob_read_from_channel(&g.cgiIn, g.httpIn, len);
blob_uncompress(&g.cgiIn, &g.cgiIn);
}
#ifdef FOSSIL_ENABLE_JSON
else if( noJson==0 && g.json.isJsonMode!=0
&& json_can_consume_content_type(zType)!=0 ){
cgi_parse_POST_JSON(g.httpIn, (unsigned int)len);
/*
Potential TODOs:
1) If parsing fails, immediately return an error response
without dispatching the ostensibly-upcoming JSON API.
*/
cgi_set_content_type(json_guess_content_type());
}
#endif /* FOSSIL_ENABLE_JSON */
else{
blob_read_from_channel(&g.cgiIn, g.httpIn, len);
}
|
| ︙ | ︙ | |||
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 |
if( j<i ){
memcpy(&aParamQP[j], &aParamQP[i], sizeof(aParamQP[j]));
}
j++;
}
nUsedQP = j;
}
/* Do a binary search for a matching query parameter */
lo = 0;
hi = nUsedQP-1;
while( lo<=hi ){
mid = (lo+hi)/2;
c = fossil_strcmp(aParamQP[mid].zName, zName);
if( c==0 ){
CGIDEBUG(("mem-match [%s] = [%s]\n", zName, aParamQP[mid].zValue));
return aParamQP[mid].zValue;
}else if( c>0 ){
hi = mid-1;
}else{
lo = mid+1;
}
}
/* If no match is found and the name begins with an upper-case
** letter, then check to see if there is an environment variable
| > > > > | > | | | 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 |
if( j<i ){
memcpy(&aParamQP[j], &aParamQP[i], sizeof(aParamQP[j]));
}
j++;
}
nUsedQP = j;
}
/* Invoking with a NULL zName is just a way to cause the parameters
** to be sorted. So go ahead and bail out in that case */
if( zName==0 || zName[0]==0 ) return 0;
/* Do a binary search for a matching query parameter */
lo = 0;
hi = nUsedQP-1;
while( lo<=hi ){
mid = (lo+hi)/2;
c = fossil_strcmp(aParamQP[mid].zName, zName);
if( c==0 ){
CGIDEBUG(("mem-match [%s] = [%s]\n", zName, aParamQP[mid].zValue));
return aParamQP[mid].zValue;
}else if( c>0 ){
hi = mid-1;
}else{
lo = mid+1;
}
}
/* If no match is found and the name begins with an upper-case
** letter, then check to see if there is an environment variable
** with the given name. Handle environment variables with empty values
** the same as non-existent environment variables.
*/
if( fossil_isupper(zName[0]) ){
const char *zValue = fossil_getenv(zName);
if( zValue && zValue[0] ){
cgi_set_parameter_nocopy(zName, zValue, 0);
CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
return zValue;
}
}
CGIDEBUG(("no-match [%s]\n", zName));
return zDefault;
|
| ︙ | ︙ | |||
1231 1232 1233 1234 1235 1236 1237 |
if( cgi_parameter(z2,0)==0 ) return 0;
}
va_end(ap);
return 1;
}
/*
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > > > > > > | > | > > > > | > > | | > > | 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 |
if( cgi_parameter(z2,0)==0 ) return 0;
}
va_end(ap);
return 1;
}
/*
** Load all relevant environment variables into the parameter buffer.
** Invoke this routine prior to calling cgi_print_all() in order to see
** the full CGI environment. This routine intended for debugging purposes
** only.
*/
void cgi_load_environment(void){
/* The following is a list of environment variables that Fossil considers
** to be "relevant". */
static const char *const azCgiVars[] = {
"COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE", "SCGI",
"HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
"HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENICATION",
"HTTP_CONNECTION", "HTTP_HOST",
"HTTP_IF_NONE_MATCH", "HTTP_IF_MODIFIED_SINCE",
"HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED",
"QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT",
"REMOTE_USER", "REQUEST_METHOD",
"REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
"HOME", "FOSSIL_HOME", "USERNAME", "USER", "FOSSIL_USER",
"SQLITE_TMPDIR", "TMPDIR",
"TEMP", "TMP", "FOSSIL_VFS",
"FOSSIL_FORCE_TICKET_MODERATION", "FOSSIL_FORCE_WIKI_MODERATION",
"FOSSIL_TCL_PATH", "TH1_DELETE_INTERP", "TH1_ENABLE_DOCS",
"TH1_ENABLE_HOOKS", "TH1_ENABLE_TCL", "REMOTE_HOST",
};
int i;
for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
}
/*
** Print all query parameters on standard output.
** This is used for testing and debugging.
**
** Omit the values of the cookies unless showAll is true.
**
** The eDest parameter determines where the output is shown:
**
** eDest==0: Rendering as HTML into the CGI reply
** eDest==1: Written to stderr
** eDest==2: Written to cgi_debug
*/
void cgi_print_all(int showAll, unsigned int eDest){
int i;
cgi_parameter("",""); /* Force the parameters into sorted order */
for(i=0; i<nUsedQP; i++){
const char *zName = aParamQP[i].zName;
if( !showAll ){
if( fossil_stricmp("HTTP_COOKIE",zName)==0 ) continue;
if( fossil_strnicmp("fossil-",zName,7)==0 ) continue;
}
switch( eDest ){
case 0: {
cgi_printf("%h = %h <br />\n", zName, aParamQP[i].zValue);
break;
}
case 1: {
fossil_trace("%s = %s\n", zName, aParamQP[i].zValue);
break;
}
case 2: {
cgi_debug("%s = %s\n", zName, aParamQP[i].zValue);
break;
}
}
}
}
/*
** Export all untagged query parameters (but not cookies or environment
** variables) as hidden values of a form.
|
| ︙ | ︙ | |||
1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 |
if( zIpAddr==0 ){
zIpAddr = cgi_remote_ip(fileno(g.httpIn));
}
if( zIpAddr ){
cgi_setenv("REMOTE_ADDR", zIpAddr);
g.zIpAddr = mprintf("%s", zIpAddr);
}
/* Get all the optional fields that follow the first line.
*/
while( fgets(zLine,sizeof(zLine),g.httpIn) ){
char *zFieldName;
char *zVal;
| > | 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 |
if( zIpAddr==0 ){
zIpAddr = cgi_remote_ip(fileno(g.httpIn));
}
if( zIpAddr ){
cgi_setenv("REMOTE_ADDR", zIpAddr);
g.zIpAddr = mprintf("%s", zIpAddr);
}
/* Get all the optional fields that follow the first line.
*/
while( fgets(zLine,sizeof(zLine),g.httpIn) ){
char *zFieldName;
char *zVal;
|
| ︙ | ︙ | |||
1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 |
cgi_setenv("HTTP_AUTHORIZATION", zVal);
}else if( fossil_strcmp(zFieldName,"x-forwarded-for:")==0 ){
const char *zIpAddr = cgi_accept_forwarded_for(zVal);
if( zIpAddr!=0 ){
g.zIpAddr = mprintf("%s", zIpAddr);
cgi_replace_parameter("REMOTE_ADDR", g.zIpAddr);
}
}
}
cgi_init();
cgi_trace(0);
}
/*
| > > > > > > > | 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 |
cgi_setenv("HTTP_AUTHORIZATION", zVal);
}else if( fossil_strcmp(zFieldName,"x-forwarded-for:")==0 ){
const char *zIpAddr = cgi_accept_forwarded_for(zVal);
if( zIpAddr!=0 ){
g.zIpAddr = mprintf("%s", zIpAddr);
cgi_replace_parameter("REMOTE_ADDR", g.zIpAddr);
}
}else if( fossil_strcmp(zFieldName,"range:")==0 ){
int x1 = 0;
int x2 = 0;
if( sscanf(zVal,"bytes=%d-%d",&x1,&x2)==2 && x1>=0 && x1<=x2 ){
rangeStart = x1;
rangeEnd = x2+1;
}
}
}
cgi_init();
cgi_trace(0);
}
/*
|
| ︙ | ︙ | |||
1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 |
static int nCycles = 0;
static char *zCmd = 0;
char *z, *zToken;
const char *zType = 0;
int i, content_length = 0;
char zLine[2000]; /* A single line of input. */
if( zIpAddr ){
if( nCycles==0 ){
cgi_setenv("REMOTE_ADDR", zIpAddr);
g.zIpAddr = mprintf("%s", zIpAddr);
}
}else{
fossil_panic("missing SSH IP address");
| > > > | 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 |
static int nCycles = 0;
static char *zCmd = 0;
char *z, *zToken;
const char *zType = 0;
int i, content_length = 0;
char zLine[2000]; /* A single line of input. */
#ifdef FOSSIL_ENABLE_JSON
if( nCycles==0 ){ json_main_bootstrap(); }
#endif
if( zIpAddr ){
if( nCycles==0 ){
cgi_setenv("REMOTE_ADDR", zIpAddr);
g.zIpAddr = mprintf("%s", zIpAddr);
}
}else{
fossil_panic("missing SSH IP address");
|
| ︙ | ︙ | |||
1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 |
return mprintf("");
}else{
return mprintf("%s, %d %s %02d %02d:%02d:%02d +0000",
azDays[pTm->tm_wday], pTm->tm_mday, azMonths[pTm->tm_mon],
pTm->tm_year+1900, pTm->tm_hour, pTm->tm_min, pTm->tm_sec);
}
}
/*
** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return
** a Unix epoch time. <= zero is returned on failure.
**
** Note that this won't handle all the _allowed_ HTTP formats, just the
** most popular one (the one generated by cgi_rfc822_datestamp(), actually).
| > > > > > > > > > > > > > > > > > > > > | 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 |
return mprintf("");
}else{
return mprintf("%s, %d %s %02d %02d:%02d:%02d +0000",
azDays[pTm->tm_wday], pTm->tm_mday, azMonths[pTm->tm_mon],
pTm->tm_year+1900, pTm->tm_hour, pTm->tm_min, pTm->tm_sec);
}
}
/*
** Returns an ISO8601-formatted time string suitable for debugging
** purposes.
**
** The value returned is always a string obtained from mprintf() and must
** be freed using fossil_free() to avoid a memory leak.
*/
char *cgi_iso8601_datestamp(void){
struct tm *pTm;
time_t now = time(0);
pTm = gmtime(&now);
if( pTm==0 ){
return mprintf("");
}else{
return mprintf("%04d-%02d-%02d %02d:%02d:%02d",
pTm->tm_year+1900, pTm->tm_mon, pTm->tm_mday,
pTm->tm_hour, pTm->tm_min, pTm->tm_sec);
}
}
/*
** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return
** a Unix epoch time. <= zero is returned on failure.
**
** Note that this won't handle all the _allowed_ HTTP formats, just the
** most popular one (the one generated by cgi_rfc822_datestamp(), actually).
|
| ︙ | ︙ |
| ︙ | ︙ | |||
820 821 822 823 824 825 826 | ** setting are ignored. This setting can be overridden by the --ignore ** option, whose CSG argument is a comma-separated list of glob patterns. ** ** Pathnames are displayed according to the "relative-paths" setting, ** unless overridden by the --abs-paths or --rel-paths options. ** ** Options: | | | | | | | | | 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 |
** setting are ignored. This setting can be overridden by the --ignore
** option, whose CSG argument is a comma-separated list of glob patterns.
**
** Pathnames are displayed according to the "relative-paths" setting,
** unless overridden by the --abs-paths or --rel-paths options.
**
** Options:
** --abs-paths Display absolute pathnames.
** --case-sensitive BOOL Override case-sensitive setting
** --dotfiles Include files beginning with a dot (".")
** --header Identify the repository if there are extras
** --ignore CSG Ignore files matching patterns from the argument
** --rel-paths Display pathnames relative to the current working
** directory.
**
** See also: changes, clean, status
*/
void extras_cmd(void){
Blob report = BLOB_INITIALIZER;
const char *zIgnoreFlag = find_option("ignore",0,1);
unsigned scanFlags = find_option("dotfiles",0,0)!=0 ? SCAN_ALL : 0;
|
| ︙ | ︙ | |||
906 907 908 909 910 911 912 | ** ** The --verily option ignores the keep-glob and ignore-glob settings and ** turns on --emptydirs, --dotfiles, and --disable-undo. Use the ** --verily option when you really want to clean up everything. Extreme ** care should be exercised when using the --verily option. ** ** Options: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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 |
**
** The --verily option ignores the keep-glob and ignore-glob settings and
** turns on --emptydirs, --dotfiles, and --disable-undo. Use the
** --verily option when you really want to clean up everything. Extreme
** care should be exercised when using the --verily option.
**
** Options:
** --allckouts Check for empty directories within any checkouts
** that may be nested within the current one. This
** option should be used with great care because the
** empty-dirs setting (and other applicable settings)
** belonging to the other repositories, if any, will
** not be checked.
** --case-sensitive BOOL Override case-sensitive setting
** --dirsonly Only remove empty directories. No files will
** be removed. Using this option will automatically
** enable the --emptydirs option as well.
** --disable-undo WARNING: This option disables use of the undo
** mechanism for this clean operation and should be
** used with extreme caution.
** --dotfiles Include files beginning with a dot (".").
** --emptydirs Remove any empty directories that are not
** explicitly exempted via the empty-dirs setting
** or another applicable setting or command line
** argument. Matching files, if any, are removed
** prior to checking for any empty directories;
** therefore, directories that contain only files
** that were removed will be removed as well.
** -f|--force Remove files without prompting.
** -i|--prompt Prompt before removing each file. This option
** implies the --disable-undo option.
** -x|--verily WARNING: Removes everything that is not a managed
** file or the repository itself. This option
** implies the --dotfiles and --emptydirs options.
** Furthermore, it completely disregards the keep-glob
** and ignore-glob settings. However, it does honor
** the --ignore and --keep options.
** --clean CSG WARNING: Never prompt to delete any files matching
** this comma separated list of glob patterns. Also,
** deletions of any files matching this pattern list
** cannot be undone.
** --ignore CSG Ignore files matching patterns from the
** comma separated list of glob patterns.
** --keep <CSG> Keep files matching this comma separated
** list of glob patterns.
** -n|--dry-run Delete nothing, but display what would have been
** deleted.
** --no-prompt This option disables prompting the user for input
** and assumes an answer of 'No' for every question.
** --temp Remove only Fossil-generated temporary files.
** -v|--verbose Show all files as they are removed.
**
** See also: addremove, extras, status
*/
void clean_cmd(void){
int allFileFlag, allDirFlag, dryRunFlag, verboseFlag;
int emptyDirsFlag, dirsOnlyFlag;
int disableUndo, noPrompt;
|
| ︙ | ︙ | |||
1174 1175 1176 1177 1178 1179 1180 | const char *zEditor; char *zCmd; char *zFile; Blob reply, line; char *zComment; int i; | | < < < < < < < < < < < < < < < | 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 |
const char *zEditor;
char *zCmd;
char *zFile;
Blob reply, line;
char *zComment;
int i;
zEditor = fossil_text_editor();db_get("editor", 0);
if( zEditor==0 ){
if( blob_size(pPrompt)>0 ){
blob_append(pPrompt,
"#\n"
"# Since no default text editor is set using EDITOR or VISUAL\n"
"# environment variables or the \"fossil set editor\" command,\n"
"# and because no comment was specified using the \"-m\" or \"-M\"\n"
|
| ︙ | ︙ | |||
1219 1220 1221 1222 1223 1224 1225 |
blob_reset(&fname);
}
#if defined(_WIN32)
blob_add_cr(pPrompt);
#endif
if( blob_size(pPrompt)>0 ) blob_write_to_file(pPrompt, zFile);
if( zEditor ){
| | | 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 |
blob_reset(&fname);
}
#if defined(_WIN32)
blob_add_cr(pPrompt);
#endif
if( blob_size(pPrompt)>0 ) blob_write_to_file(pPrompt, zFile);
if( zEditor ){
zCmd = mprintf("%s %$", zEditor, zFile);
fossil_print("%s\n", zCmd);
if( fossil_system(zCmd) ){
fossil_fatal("editor aborted: \"%s\"", zCmd);
}
blob_read_from_file(&reply, zFile, ExtFILE);
}else{
|
| ︙ | ︙ | |||
1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 |
g.aCommitFile[jj++] = ii;
}
g.aCommitFile[jj] = 0;
bag_clear(&toCommit);
}
return result;
}
/*
** Make sure the current check-in with timestamp zDate is younger than its
** ancestor identified rid and zUuid. Throw a fatal error if not.
*/
static void checkin_verify_younger(
int rid, /* The record ID of the ancestor */
| > > > > > > > > > > > > > > > > > > | < < < < < | < < > > | 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 |
g.aCommitFile[jj++] = ii;
}
g.aCommitFile[jj] = 0;
bag_clear(&toCommit);
}
return result;
}
/*
** Returns true if the checkin identified by the first parameter is
** older than the given (valid) date/time string, else returns false.
** Also returns true if rid does not refer to a checkin, but it is not
** intended to be used for that case.
*/
int checkin_is_younger(
int rid, /* The record ID of the ancestor */
const char *zDate /* Date & time of the current check-in */
){
return db_exists(
"SELECT 1 FROM event"
" WHERE datetime(mtime)>=%Q"
" AND type='ci' AND objid=%d",
zDate, rid
) ? 0 : 1;
}
/*
** Make sure the current check-in with timestamp zDate is younger than its
** ancestor identified rid and zUuid. Throw a fatal error if not.
*/
static void checkin_verify_younger(
int rid, /* The record ID of the ancestor */
const char *zUuid, /* The artifact hash of the ancestor */
const char *zDate /* Date & time of the current check-in */
){
#ifndef FOSSIL_ALLOW_OUT_OF_ORDER_DATES
if(checkin_is_younger(rid,zDate)==0){
fossil_fatal("ancestor check-in [%S] (%s) is not older (clock skew?)"
" Use --allow-older to override.", zUuid, zDate);
}
#endif
}
/*
** zDate should be a valid date string. Convert this string into the
** format YYYY-MM-DDTHH:MM:SS. If the string is not a valid date,
** print a fatal error and quit.
*/
char *date_in_standard_format(const char *zInputDate){
|
| ︙ | ︙ | |||
1487 1488 1489 1490 1491 1492 1493 | #endif /* INTERFACE */ /* ** Create a manifest. */ static void create_manifest( Blob *pOut, /* Write the manifest here */ | | | | 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 |
#endif /* INTERFACE */
/*
** Create a manifest.
*/
static void create_manifest(
Blob *pOut, /* Write the manifest here */
const char *zBaselineUuid, /* Hash of baseline, or zero */
Manifest *pBaseline, /* Make it a delta manifest if not zero */
int vid, /* BLOB.id for the parent check-in */
CheckinInfo *p, /* Information about the check-in */
int *pnFBcard /* OUT: Number of generated B- and F-cards */
){
char *zDate; /* Date of the check-in */
char *zParentUuid = 0; /* Hash of parent check-in */
Blob filename; /* A single filename */
int nBasename; /* Size of base filename */
Stmt q; /* Various queries */
Blob mcksum; /* Manifest checksum */
ManifestFile *pFile; /* File from the baseline */
int nFBcard = 0; /* Number of B-cards and F-cards */
int i; /* Loop counter */
|
| ︙ | ︙ | |||
1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 |
" WHERE id %s ORDER BY 1",
p->integrateFlag ? "IN(0,-4)" : "=(-4)");
while( db_step(&q)==SQLITE_ROW ){
const char *zIntegrateUuid = db_column_text(&q, 0);
int rid = db_column_int(&q, 1);
if( is_a_leaf(rid) && !db_exists("SELECT 1 FROM tagxref "
" WHERE tagid=%d AND rid=%d AND tagtype>0", TAG_CLOSED, rid)){
blob_appendf(pOut, "T +closed %s\n", zIntegrateUuid);
}
}
db_finalize(&q);
if( p->azTag ){
for(i=0; p->azTag[i]; i++){
| > > > > > > > > | 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 |
" WHERE id %s ORDER BY 1",
p->integrateFlag ? "IN(0,-4)" : "=(-4)");
while( db_step(&q)==SQLITE_ROW ){
const char *zIntegrateUuid = db_column_text(&q, 0);
int rid = db_column_int(&q, 1);
if( is_a_leaf(rid) && !db_exists("SELECT 1 FROM tagxref "
" WHERE tagid=%d AND rid=%d AND tagtype>0", TAG_CLOSED, rid)){
#if 0
/* Make sure the check-in manifest of the resulting merge child does not
** include a +close tag referring to the leaf check-in on a private
** branch, so as not to generate a missing artifact reference on
** repository clones without that private branch. The merge command
** should have dropped the --integrate option, at this point. */
assert( !content_is_private(rid) );
#endif
blob_appendf(pOut, "T +closed %s\n", zIntegrateUuid);
}
}
db_finalize(&q);
if( p->azTag ){
for(i=0; p->azTag[i]; i++){
|
| ︙ | ︙ | |||
1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 | } /* ** COMMAND: ci* ** COMMAND: commit ** ** Usage: %fossil commit ?OPTIONS? ?FILE...? ** ** Create a new version containing all of the changes in the current ** checkout. You will be prompted to enter a check-in comment unless ** the comment has been specified on the command-line using "-m" or a ** file containing the comment using -M. The editor defined in the ** "editor" fossil option (see %fossil help set) will be used, or from ** the "VISUAL" or "EDITOR" environment variables (in that order) if | > | 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 | } /* ** COMMAND: ci* ** COMMAND: commit ** ** Usage: %fossil commit ?OPTIONS? ?FILE...? ** or: %fossil ci ?OPTIONS? ?FILE...? ** ** Create a new version containing all of the changes in the current ** checkout. You will be prompted to enter a check-in comment unless ** the comment has been specified on the command-line using "-m" or a ** file containing the comment using -M. The editor defined in the ** "editor" fossil option (see %fossil help set) will be used, or from ** the "VISUAL" or "EDITOR" environment variables (in that order) if |
| ︙ | ︙ | |||
2036 2037 2038 2039 2040 2041 2042 | int hasChanges; /* True if unsaved changes exist */ int vid; /* blob-id of parent version */ int nrid; /* blob-id of a modified file */ int nvid; /* Blob-id of the new check-in */ Blob comment; /* Check-in comment */ const char *zComment; /* Check-in comment */ Stmt q; /* Various queries */ | | > > | 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 | int hasChanges; /* True if unsaved changes exist */ int vid; /* blob-id of parent version */ int nrid; /* blob-id of a modified file */ int nvid; /* Blob-id of the new check-in */ Blob comment; /* Check-in comment */ const char *zComment; /* Check-in comment */ Stmt q; /* Various queries */ char *zUuid; /* Hash of the new check-in */ int useHash = 0; /* True to verify file status using hashing */ int noSign = 0; /* True to omit signing the manifest using GPG */ int privateFlag = 0; /* True if the --private option is present */ int privateParent = 0; /* True if the parent check-in is private */ int isAMerge = 0; /* True if checking in a merge */ int noWarningFlag = 0; /* True if skipping all warnings */ int noPrompt = 0; /* True if skipping all prompts */ int forceFlag = 0; /* Undocumented: Disables all checks */ int forceDelta = 0; /* Force a delta-manifest */ int forceBaseline = 0; /* Force a baseline-manifest */ int allowConflict = 0; /* Allow unresolve merge conflicts */ |
| ︙ | ︙ | |||
2066 2067 2068 2069 2070 2071 2072 | Blob manifest; /* Manifest in baseline form */ Blob muuid; /* Manifest uuid */ Blob cksum1, cksum2; /* Before and after commit checksums */ Blob cksum1b; /* Checksum recorded in the manifest */ int szD; /* Size of the delta manifest */ int szB; /* Size of the baseline manifest */ int nConflict = 0; /* Number of unresolved merge conflicts */ | | | | > > | > | > > > > | 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 |
Blob manifest; /* Manifest in baseline form */
Blob muuid; /* Manifest uuid */
Blob cksum1, cksum2; /* Before and after commit checksums */
Blob cksum1b; /* Checksum recorded in the manifest */
int szD; /* Size of the delta manifest */
int szB; /* Size of the baseline manifest */
int nConflict = 0; /* Number of unresolved merge conflicts */
int abortCommit = 0; /* Abort the commit due to text format conversions */
Blob ans; /* Answer to continuation prompts */
char cReply; /* First character of ans */
int bRecheck = 0; /* Repeat fork and closed-branch checks*/
memset(&sCiInfo, 0, sizeof(sCiInfo));
url_proxy_options();
/* --sha1sum is an undocumented alias for --hash for backwards compatiblity */
useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0;
noSign = find_option("nosign",0,0)!=0;
privateFlag = find_option("private",0,0)!=0;
forceDelta = find_option("delta",0,0)!=0;
forceBaseline = find_option("baseline",0,0)!=0;
if( forceDelta ){
if( forceBaseline ){
fossil_fatal("cannot use --delta and --baseline together");
}
if( db_get_boolean("forbid-delta-manifests",0) ){
fossil_fatal("delta manifests are prohibited in this repository");
}
}
dryRunFlag = find_option("dry-run","n",0)!=0;
if( !dryRunFlag ){
dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
}
zComment = find_option("comment","m",1);
forceFlag = find_option("force", "f", 0)!=0;
|
| ︙ | ︙ | |||
2107 2108 2109 2110 2111 2112 2113 |
if( zTag[0]==0 ) continue;
sCiInfo.azTag = fossil_realloc((void*)sCiInfo.azTag,
sizeof(char*)*(nTag+2));
sCiInfo.azTag[nTag++] = zTag;
sCiInfo.azTag[nTag] = 0;
}
zComFile = find_option("message-file", "M", 1);
| < < < < < < < | | > | > > > | > > > > > > | 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 |
if( zTag[0]==0 ) continue;
sCiInfo.azTag = fossil_realloc((void*)sCiInfo.azTag,
sizeof(char*)*(nTag+2));
sCiInfo.azTag[nTag++] = zTag;
sCiInfo.azTag[nTag] = 0;
}
zComFile = find_option("message-file", "M", 1);
sCiInfo.zDateOvrd = find_option("date-override",0,1);
sCiInfo.zUserOvrd = find_option("user-override",0,1);
db_must_be_within_tree();
noSign = db_get_boolean("omitsign", 0)|noSign;
if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; }
useCksum = db_get_boolean("repo-cksum", 1);
outputManifest = db_get_manifest_setting();
verify_all_options();
/* Get the ID of the parent manifest artifact */
vid = db_lget_int("checkout", 0);
if( vid==0 ){
useCksum = 1;
if( privateFlag==0 && sCiInfo.zBranch==0 ) {
sCiInfo.zBranch=db_get("main-branch", 0);
}
}else{
privateParent = content_is_private(vid);
}
/* Track the "private" status */
g.markPrivate = privateFlag || privateParent;
if( privateFlag && !privateParent ){
/* Apply default branch name ("private") and color ("orange") if not
** specified otherwise on the command-line, and if the parent is not
** already private. */
if( sCiInfo.zBranch==0 ) sCiInfo.zBranch = "private";
if( sCiInfo.zBrClr==0 && sCiInfo.zColor==0 ) sCiInfo.zBrClr = "#fec084";
}
/* Do not allow the creation of a new branch using an existing open
** branch name unless the --force flag is used */
if( sCiInfo.zBranch!=0
&& !forceFlag
&& fossil_strcmp(sCiInfo.zBranch,"private")!=0
|
| ︙ | ︙ | |||
2158 2159 2160 2161 2162 2163 2164 | /* So that older versions of Fossil (that do not understand delta- ** manifest) can continue to use this repository, do not create a new ** delta-manifest unless this repository already contains one or more ** delta-manifests, or unless the delta-manifest is explicitly requested ** by the --delta option. */ | > | > > | 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 |
/* So that older versions of Fossil (that do not understand delta-
** manifest) can continue to use this repository, do not create a new
** delta-manifest unless this repository already contains one or more
** delta-manifests, or unless the delta-manifest is explicitly requested
** by the --delta option.
*/
if( !forceDelta
&& !db_get_boolean("seen-delta-manifest",0)
&& !db_get_boolean("forbid-delta-manifests",0)
){
forceBaseline = 1;
}
/*
** Autosync if autosync is enabled and this is not a private check-in.
*/
if( !g.markPrivate ){
|
| ︙ | ︙ | |||
2272 2273 2274 2275 2276 2277 2278 |
" WHERE is_selected(id)"
" AND (chnged OR deleted OR rid=0 OR pathname!=origname)")
){
fossil_fatal("none of the selected files have changed; use "
"--allow-empty to override.");
}
| > > > > > | | | | | | | | | | | | | | > | | | | | | | | | | | | | < < | | | | | | | | | | > > > > > > > | | | | | | | | | | | | | | | | | | | | | > > > > > > > > > > | > > > > > < < < < | 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 |
" WHERE is_selected(id)"
" AND (chnged OR deleted OR rid=0 OR pathname!=origname)")
){
fossil_fatal("none of the selected files have changed; use "
"--allow-empty to override.");
}
/* This loop checks for potential forks and for check-ins against a
** closed branch. The checks are repeated once after interactive
** check-in comment editing.
*/
do{
/*
** Do not allow a commit that will cause a fork unless the --allow-fork
** or --force flags is used, or unless this is a private check-in.
** The initial commit MUST have tags "trunk" and "sym-trunk".
*/
if( sCiInfo.zBranch==0
&& allowFork==0
&& forceFlag==0
&& g.markPrivate==0
&& (vid==0 || !is_a_leaf(vid) || g.ckinLockFail)
){
if( g.ckinLockFail ){
fossil_fatal("Might fork due to a check-in race with user \"%s\"\n"
"Try \"update\" first, or --branch, or "
"use --override-lock",
g.ckinLockFail);
}else{
fossil_fatal("Would fork. \"update\" first or use --branch or "
"--allow-fork.");
}
}
/*
** Do not allow a commit against a closed leaf unless the commit
** ends up on a different branch.
*/
if(
/* parent check-in has the "closed" tag... */
leaf_is_closed(vid)
/* ... and the new check-in has no --branch option or the --branch
** option does not actually change the branch */
&& (sCiInfo.zBranch==0
|| db_exists("SELECT 1 FROM tagxref"
" WHERE tagid=%d AND rid=%d AND tagtype>0"
" AND value=%Q", TAG_BRANCH, vid, sCiInfo.zBranch))
){
fossil_fatal("cannot commit against a closed leaf");
}
/* Always exit the loop on the second pass */
if( bRecheck ) break;
/* Get the check-in comment. This might involve prompting the
** user for the check-in comment, in which case we should resync
** to renew the check-in lock and repeat the checks for conflicts.
*/
if( zComment ){
blob_zero(&comment);
blob_append(&comment, zComment, -1);
}else if( zComFile ){
blob_zero(&comment);
blob_read_from_file(&comment, zComFile, ExtFILE);
blob_to_utf8_no_bom(&comment, 1);
}else if( dryRunFlag ){
blob_zero(&comment);
}else if( !noPrompt ){
char *zInit = db_text(0,"SELECT value FROM vvar WHERE name='ci-comment'");
prepare_commit_comment(&comment, zInit, &sCiInfo, vid);
if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){
prompt_user("unchanged check-in comment. continue (y/N)? ", &ans);
cReply = blob_str(&ans)[0];
blob_reset(&ans);
if( cReply!='y' && cReply!='Y' ){
fossil_exit(1);
}
}
free(zInit);
db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment);
db_end_transaction(0);
db_begin_transaction();
if( !g.markPrivate && vid!=0 && !allowFork && !forceFlag ){
/* Do another auto-pull, renewing the check-in lock. Then set
** bRecheck so that we loop back above to verify that the check-in
** is still not against a closed branch and still won't fork. */
int syncFlags = SYNC_PULL|SYNC_CKIN_LOCK;
if( autosync_loop(syncFlags, db_get_int("autosync-tries", 1), 1) ){
fossil_exit(1);
}
bRecheck = 1;
}
}
}while( bRecheck );
if( blob_size(&comment)==0 ){
if( !dryRunFlag ){
if( !noPrompt ){
prompt_user("empty check-in comment. continue (y/N)? ", &ans);
cReply = blob_str(&ans)[0];
blob_reset(&ans);
}else{
fossil_print("Abandoning commit due to empty check-in comment\n");
cReply = 'N';
}
if( cReply!='y' && cReply!='Y' ){
fossil_exit(1);
}
}
}
/*
** Step 1: Compute an aggregate MD5 checksum over the disk image
** of every file in vid. The file names are part of the checksum.
** The resulting checksum is the same as is expected on the R-card
** of a manifest.
|
| ︙ | ︙ | |||
2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 |
undo_reset();
/* Commit */
db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'");
db_multi_exec("PRAGMA repository.application_id=252006673;");
db_multi_exec("PRAGMA localdb.application_id=252006674;");
if( dryRunFlag ){
db_end_transaction(1);
exit(1);
}
db_end_transaction(0);
if( outputManifest & MFESTFLG_TAGS ){
Blob tagslist;
| > | 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 |
undo_reset();
/* Commit */
db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'");
db_multi_exec("PRAGMA repository.application_id=252006673;");
db_multi_exec("PRAGMA localdb.application_id=252006674;");
if( dryRunFlag ){
leaf_ambiguity_warning(nvid,nvid);
db_end_transaction(1);
exit(1);
}
db_end_transaction(0);
if( outputManifest & MFESTFLG_TAGS ){
Blob tagslist;
|
| ︙ | ︙ | |||
2630 2631 2632 2633 2634 2635 2636 2637 2638 |
if( !g.markPrivate ){
int syncFlags = SYNC_PUSH | SYNC_PULL | SYNC_IFABLE;
int nTries = db_get_int("autosync-tries",1);
autosync_loop(syncFlags, nTries, 0);
}
if( count_nonbranch_children(vid)>1 ){
fossil_print("**** warning: a fork has occurred *****\n");
}
}
| > > | 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 |
if( !g.markPrivate ){
int syncFlags = SYNC_PUSH | SYNC_PULL | SYNC_IFABLE;
int nTries = db_get_int("autosync-tries",1);
autosync_loop(syncFlags, nTries, 0);
}
if( count_nonbranch_children(vid)>1 ){
fossil_print("**** warning: a fork has occurred *****\n");
}else{
leaf_ambiguity_warning(nvid,nvid);
}
}
|
| ︙ | ︙ | |||
85 86 87 88 89 90 91 |
);
fossil_free(zPwd);
db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid);
}
/*
| | | 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
);
fossil_free(zPwd);
db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid);
}
/*
** Given the abbreviated hash of a version, load the content of that
** version in the VFILE table. Return the VID for the version.
**
** If anything goes wrong, panic.
*/
int load_vfile(const char *zName, int forceMissingFlag){
Blob uuid;
int vid;
|
| ︙ | ︙ |
| ︙ | ︙ | |||
83 84 85 86 87 88 89 | ** COMMAND: clone ** ** Usage: %fossil clone ?OPTIONS? URI FILENAME ** ** Make a clone of a repository specified by URI in the local ** file named FILENAME. ** | | > > | > | | > | < | > | | | | | 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 | ** COMMAND: clone ** ** Usage: %fossil clone ?OPTIONS? URI FILENAME ** ** Make a clone of a repository specified by URI in the local ** file named FILENAME. ** ** URI may be one of the following forms: ** ([...] denotes optional elements): ** ** * HTTP/HTTPS protocol: ** ** http[s]://[userid[:password]@]host[:port][/path] ** ** * SSH protocol: ** ** ssh://[userid@]host[:port]/path/to/repo.fossil[?fossil=path/fossil.exe] ** ** * Filesystem: ** ** [file://]path/to/repo.fossil ** ** Note 1: For ssh and filesystem, path must have an extra leading ** '/' to use an absolute path. ** ** Note 2: Use %HH escapes for special characters in the userid and ** password. For example "%40" in place of "@", "%2f" in place ** of "/", and "%3a" in place of ":". ** ** By default, your current login name is used to create the default ** admin user. This can be overridden using the -A|--admin-user ** parameter. ** ** Options: ** --admin-user|-A USERNAME Make USERNAME the administrator |
| ︙ | ︙ | |||
202 203 204 205 206 207 208 |
" VALUES('server-code', lower(hex(randomblob(20))), now());"
"DELETE FROM config WHERE name='project-code';"
);
url_enable_proxy(0);
clone_ssh_db_set_options();
url_get_password_if_needed();
g.xlinkClusterOnly = 1;
| | | 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
" VALUES('server-code', lower(hex(randomblob(20))), now());"
"DELETE FROM config WHERE name='project-code';"
);
url_enable_proxy(0);
clone_ssh_db_set_options();
url_get_password_if_needed();
g.xlinkClusterOnly = 1;
nErr = client_sync(syncFlags,CONFIGSET_ALL,0,0);
g.xlinkClusterOnly = 0;
verify_cancel();
db_end_transaction(0);
db_close(1);
if( nErr ){
file_delete(g.argv[3]);
fossil_fatal("server returned an error - clone aborted");
|
| ︙ | ︙ |
| ︙ | ︙ | |||
341 342 343 344 345 346 347 | if( strncmp(z,"cgi_param",9)==0 ) return 1; return 0; } /* ** Processing flags */ | | | | | | | > > | > > | | | | > > | | | | > > > | | > > > > > > > > > | 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 |
if( strncmp(z,"cgi_param",9)==0 ) return 1;
return 0;
}
/*
** Processing flags
*/
#define FMT_SQL 0x00001 /* Generator for SQL text */
#define FMT_HTML 0x00002 /* Generator for HTML text */
#define FMT_URL 0x00004 /* Generator for URLs */
#define FMT_SAFE 0x00008 /* Generator for human-readable text */
/*
** A list of internal Fossil interfaces that take a printf-style format
** string.
*/
struct FmtFunc {
const char *zFName; /* Name of the function */
int iFmtArg; /* Index of format argument. Leftmost is 1. */
unsigned fmtFlags; /* Processing flags */
} aFmtFunc[] = {
{ "admin_log", 1, FMT_SAFE },
{ "audit_append", 3, FMT_SAFE },
{ "backofficeTrace", 1, FMT_SAFE },
{ "blob_append_sql", 2, FMT_SQL },
{ "blob_appendf", 2, FMT_SAFE },
{ "cgi_debug", 1, FMT_SAFE },
{ "cgi_panic", 1, FMT_SAFE },
{ "cgi_printf", 1, FMT_HTML },
{ "cgi_printf_header", 1, FMT_HTML },
{ "cgi_redirectf", 1, FMT_URL },
{ "chref", 2, FMT_URL },
{ "CX", 1, FMT_HTML },
{ "db_blob", 2, FMT_SQL },
{ "db_debug", 1, FMT_SQL },
{ "db_double", 2, FMT_SQL },
{ "db_err", 1, FMT_SAFE },
{ "db_exists", 1, FMT_SQL },
{ "db_get_mprintf", 2, FMT_SAFE },
{ "db_int", 2, FMT_SQL },
{ "db_int64", 2, FMT_SQL },
{ "db_multi_exec", 1, FMT_SQL },
{ "db_optional_sql", 2, FMT_SQL },
{ "db_prepare", 2, FMT_SQL },
{ "db_prepare_ignore_error", 2, FMT_SQL },
{ "db_set_mprintf", 3, FMT_SAFE },
{ "db_static_prepare", 2, FMT_SQL },
{ "db_text", 2, FMT_SQL },
{ "db_unset_mprintf", 2, FMT_SAFE },
{ "emailerError", 2, FMT_SAFE },
{ "fileedit_ajax_error", 2, FMT_SAFE },
{ "form_begin", 2, FMT_URL },
{ "fossil_error", 2, FMT_SAFE },
{ "fossil_errorlog", 1, FMT_SAFE },
{ "fossil_fatal", 1, FMT_SAFE },
{ "fossil_fatal_recursive", 1, FMT_SAFE },
{ "fossil_panic", 1, FMT_SAFE },
{ "fossil_print", 1, FMT_SAFE },
{ "fossil_trace", 1, FMT_SAFE },
{ "fossil_warning", 1, FMT_SAFE },
{ "href", 1, FMT_URL },
{ "json_new_string_f", 1, FMT_SAFE },
{ "json_set_err", 2, FMT_SAFE },
{ "json_warn", 2, FMT_SAFE },
{ "mprintf", 1, FMT_SAFE },
{ "pop3_print", 2, FMT_SAFE },
{ "smtp_send_line", 2, FMT_SAFE },
{ "smtp_server_send", 2, FMT_SAFE },
{ "socket_set_errmsg", 1, FMT_SAFE },
{ "ssl_set_errmsg", 1, FMT_SAFE },
{ "style_header", 1, FMT_HTML },
{ "style_js_onload", 1, FMT_HTML },
{ "style_set_current_page", 1, FMT_URL },
{ "style_submenu_element", 2, FMT_URL },
{ "style_submenu_sql", 3, FMT_SQL },
{ "webpage_error", 1, FMT_SAFE },
{ "xhref", 2, FMT_URL },
};
/*
** Comparison function for two FmtFunc entries
*/
static int fmtfunc_cmp(const void *pAA, const void *pBB){
const struct FmtFunc *pA = (const struct FmtFunc*)pAA;
const struct FmtFunc *pB = (const struct FmtFunc*)pBB;
return strcmp(pA->zFName, pB->zFName);
}
/*
** Determine if the indentifier zIdent of length nIndent is a Fossil
** internal interface that uses a printf-style argument. Return zero if not.
** Return the index of the format string if true with the left-most
** argument having an index of 1.
*/
|
| ︙ | ︙ | |||
623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 |
** on the command-line.
**
** The eVerbose global variable is incremented with each "-v" argument.
*/
int main(int argc, char **argv){
int i;
int nErr = 0;
for(i=1; i<argc; i++){
char *zFile;
if( strcmp(argv[i],"-v")==0 ){
eVerbose++;
continue;
}
if( eVerbose>0 ) printf("Processing %s...\n", argv[i]);
zFile = read_file(argv[i]);
nErr += scan_file(argv[i], zFile);
free(zFile);
}
return nErr;
}
| > > | 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 |
** on the command-line.
**
** The eVerbose global variable is incremented with each "-v" argument.
*/
int main(int argc, char **argv){
int i;
int nErr = 0;
qsort(aFmtFunc, sizeof(aFmtFunc)/sizeof(aFmtFunc[0]),
sizeof(aFmtFunc[0]), fmtfunc_cmp);
for(i=1; i<argc; i++){
char *zFile;
if( strcmp(argv[i],"-v")==0 ){
eVerbose++;
continue;
}
if( eVerbose>0 ) printf("Processing %s...\n", argv[i]);
zFile = read_file(argv[i]);
nErr += scan_file(argv[i], zFile);
free(zFile);
}
return nErr;
}
|
| ︙ | ︙ | |||
17 18 19 20 21 22 23 | ** ** This file contains code used to format and print comments or other ** text on a TTY. */ #include "config.h" #include "comformat.h" #include <assert.h> | < < < < < < | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | ** ** This file contains code used to format and print comments or other ** text on a TTY. */ #include "config.h" #include "comformat.h" #include <assert.h> #if INTERFACE #define COMMENT_PRINT_NONE ((u32)0x00000000) /* No flags = non-legacy. */ #define COMMENT_PRINT_LEGACY ((u32)0x00000001) /* Use legacy algorithm. */ #define COMMENT_PRINT_TRIM_CRLF ((u32)0x00000002) /* Trim leading CR/LF. */ #define COMMENT_PRINT_TRIM_SPACE ((u32)0x00000004) /* Trim leading/trailing. */ #define COMMENT_PRINT_WORD_BREAK ((u32)0x00000008) /* Break lines on words. */ |
| ︙ | ︙ | |||
65 66 67 68 69 70 71 |
** returned to indicate the terminal line width is using the hard-coded
** legacy default value.
*/
static int comment_set_maxchars(
int indent,
int *pMaxChars
){
| | < | < < | | < < < | | < < | | | | | | | | < > | 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 |
** returned to indicate the terminal line width is using the hard-coded
** legacy default value.
*/
static int comment_set_maxchars(
int indent,
int *pMaxChars
){
struct TerminalSize ts;
if ( !terminal_get_size(&ts) ){
return 0;
}
if( ts.nColumns ){
*pMaxChars = ts.nColumns - indent;
return 1;
}else{
/*
** Fallback to using more-or-less the "legacy semantics" of hard-coding
** the maximum line length to a value reasonable for the vast majority
** of supported systems.
*/
*pMaxChars = COMMENT_LEGACY_LINE_LENGTH - indent;
return -1;
}
}
/*
** This function checks the current line being printed against the original
** comment text. Upon matching, it updates the provided character and line
** counts, if applicable. The caller needs to emit a new line, if desired.
*/
|
| ︙ | ︙ |
| ︙ | ︙ | |||
175 176 177 178 179 180 181 182 183 184 185 186 187 188 | #if !defined(_RC_COMPILE_) && !defined(SQLITE_AMALGAMATION) /* ** MSVC does not include the "stdint.h" header file until 2010. */ #if defined(_MSC_VER) && _MSC_VER<1600 typedef __int32 int32_t; typedef unsigned __int32 uint32_t; typedef __int64 int64_t; typedef unsigned __int64 uint64_t; #else # include <stdint.h> #endif | > > | 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | #if !defined(_RC_COMPILE_) && !defined(SQLITE_AMALGAMATION) /* ** MSVC does not include the "stdint.h" header file until 2010. */ #if defined(_MSC_VER) && _MSC_VER<1600 typedef __int8 int8_t; typedef unsigned __int8 uint8_t; typedef __int32 int32_t; typedef unsigned __int32 uint32_t; typedef __int64 int64_t; typedef unsigned __int64 uint64_t; #else # include <stdint.h> #endif |
| ︙ | ︙ | |||
250 251 252 253 254 255 256 | #else # define NORETURN #endif /* ** Number of elements in an array */ | | > | 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 | #else # define NORETURN #endif /* ** Number of elements in an array */ #define count(X) (int)(sizeof(X)/sizeof(X[0])) #define ArraySize(X) (int)(sizeof(X)/sizeof(X[0])) /* ** The pledge() interface is currently only available on OpenBSD 5.9 ** and later. Make calls to fossil_pledge() no-ops on all platforms ** that omit the HAVE_PLEDGE configuration parameter. */ #if !defined(HAVE_PLEDGE) # define fossil_pledge(A) #endif #endif /* _RC_COMPILE_ */ |
| ︙ | ︙ | |||
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 |
const char *zName; /* Name of the configuration parameter */
int groupMask; /* Which config groups is it part of */
} aConfig[] = {
{ "css", CONFIGSET_CSS },
{ "header", CONFIGSET_SKIN },
{ "footer", CONFIGSET_SKIN },
{ "details", CONFIGSET_SKIN },
{ "logo-mimetype", CONFIGSET_SKIN },
{ "logo-image", CONFIGSET_SKIN },
{ "background-mimetype", CONFIGSET_SKIN },
{ "background-image", CONFIGSET_SKIN },
{ "timeline-block-markup", CONFIGSET_SKIN },
{ "timeline-date-format", CONFIGSET_SKIN },
{ "timeline-dwelltime", CONFIGSET_SKIN },
{ "timeline-closetime", CONFIGSET_SKIN },
{ "timeline-max-comment", CONFIGSET_SKIN },
{ "timeline-plaintext", CONFIGSET_SKIN },
{ "timeline-truncate-at-blank", CONFIGSET_SKIN },
{ "timeline-utc", CONFIGSET_SKIN },
{ "adunit", CONFIGSET_SKIN },
{ "adunit-omit-if-admin", CONFIGSET_SKIN },
{ "adunit-omit-if-user", CONFIGSET_SKIN },
{ "sitemap-docidx", CONFIGSET_SKIN },
{ "sitemap-download", CONFIGSET_SKIN },
{ "sitemap-license", CONFIGSET_SKIN },
{ "sitemap-contact", CONFIGSET_SKIN },
#ifdef FOSSIL_ENABLE_TH1_DOCS
{ "th1-docs", CONFIGSET_TH1 },
| > > > > | 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 |
const char *zName; /* Name of the configuration parameter */
int groupMask; /* Which config groups is it part of */
} aConfig[] = {
{ "css", CONFIGSET_CSS },
{ "header", CONFIGSET_SKIN },
{ "footer", CONFIGSET_SKIN },
{ "details", CONFIGSET_SKIN },
{ "js", CONFIGSET_SKIN },
{ "logo-mimetype", CONFIGSET_SKIN },
{ "logo-image", CONFIGSET_SKIN },
{ "background-mimetype", CONFIGSET_SKIN },
{ "background-image", CONFIGSET_SKIN },
{ "timeline-block-markup", CONFIGSET_SKIN },
{ "timeline-date-format", CONFIGSET_SKIN },
{ "timeline-default-style", CONFIGSET_SKIN },
{ "timeline-dwelltime", CONFIGSET_SKIN },
{ "timeline-closetime", CONFIGSET_SKIN },
{ "timeline-max-comment", CONFIGSET_SKIN },
{ "timeline-plaintext", CONFIGSET_SKIN },
{ "timeline-truncate-at-blank", CONFIGSET_SKIN },
{ "timeline-tslink-info", CONFIGSET_SKIN },
{ "timeline-utc", CONFIGSET_SKIN },
{ "adunit", CONFIGSET_SKIN },
{ "adunit-omit-if-admin", CONFIGSET_SKIN },
{ "adunit-omit-if-user", CONFIGSET_SKIN },
{ "default-csp", CONFIGSET_SKIN },
{ "sitemap-docidx", CONFIGSET_SKIN },
{ "sitemap-download", CONFIGSET_SKIN },
{ "sitemap-license", CONFIGSET_SKIN },
{ "sitemap-contact", CONFIGSET_SKIN },
#ifdef FOSSIL_ENABLE_TH1_DOCS
{ "th1-docs", CONFIGSET_TH1 },
|
| ︙ | ︙ | |||
140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
{ "empty-dirs", CONFIGSET_PROJ },
{ "allow-symlinks", CONFIGSET_PROJ },
{ "dotfiles", CONFIGSET_PROJ },
{ "parent-project-code", CONFIGSET_PROJ },
{ "parent-project-name", CONFIGSET_PROJ },
{ "hash-policy", CONFIGSET_PROJ },
{ "comment-format", CONFIGSET_PROJ },
#ifdef FOSSIL_ENABLE_LEGACY_MV_RM
{ "mv-rm-files", CONFIGSET_PROJ },
#endif
{ "ticket-table", CONFIGSET_TKT },
{ "ticket-common", CONFIGSET_TKT },
| > > | 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
{ "empty-dirs", CONFIGSET_PROJ },
{ "allow-symlinks", CONFIGSET_PROJ },
{ "dotfiles", CONFIGSET_PROJ },
{ "parent-project-code", CONFIGSET_PROJ },
{ "parent-project-name", CONFIGSET_PROJ },
{ "hash-policy", CONFIGSET_PROJ },
{ "comment-format", CONFIGSET_PROJ },
{ "mimetypes", CONFIGSET_PROJ },
{ "forbid-delta-manifests", CONFIGSET_PROJ },
#ifdef FOSSIL_ENABLE_LEGACY_MV_RM
{ "mv-rm-files", CONFIGSET_PROJ },
#endif
{ "ticket-table", CONFIGSET_TKT },
{ "ticket-common", CONFIGSET_TKT },
|
| ︙ | ︙ | |||
696 697 698 699 700 701 702 | ** COMMAND: configuration* ** ** Usage: %fossil configuration METHOD ... ?OPTIONS? ** ** Where METHOD is one of: export import merge pull push reset. All methods ** accept the -R or --repository option to specify a repository. ** | | | | | | | | | 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 | ** COMMAND: configuration* ** ** Usage: %fossil configuration METHOD ... ?OPTIONS? ** ** Where METHOD is one of: export import merge pull push reset. All methods ** accept the -R or --repository option to specify a repository. ** ** > fossil configuration export AREA FILENAME ** ** Write to FILENAME exported configuration information for AREA. ** AREA can be one of: ** ** all email project shun skin ticket user alias subscriber ** ** > fossil configuration import FILENAME ** ** Read a configuration from FILENAME, overwriting the current ** configuration. ** ** > fossil configuration merge FILENAME ** ** Read a configuration from FILENAME and merge its values into ** the current configuration. Existing values take priority over ** values read from FILENAME. ** ** > fossil configuration pull AREA ?URL? ** ** Pull and install the configuration from a different server ** identified by URL. If no URL is specified, then the default ** server is used. Use the --overwrite flag to completely ** replace local settings with content received from URL. ** ** > fossil configuration push AREA ?URL? ** ** Push the local configuration into the remote server identified ** by URL. Admin privilege is required on the remote server for ** this to work. When the same record exists both locally and on ** the remote end, the one that was most recently changed wins. ** ** > fossil configuration reset AREA ** ** Restore the configuration to the default. AREA as above. ** ** > fossil configuration sync AREA ?URL? ** ** Synchronize configuration changes in the local repository with ** the remote repository at URL. ** ** Options: ** -R|--repository FILE Extract info from repository FILE ** |
| ︙ | ︙ | |||
810 811 812 813 814 815 816 |
}
url_parse(zServer, URL_PROMPT_PW);
if( g.url.protocol==0 ) fossil_fatal("no server URL specified");
user_select();
url_enable_proxy("via proxy: ");
if( overwriteFlag ) mask |= CONFIGSET_OVERWRITE;
if( strncmp(zMethod, "push", n)==0 ){
| | | | | 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 |
}
url_parse(zServer, URL_PROMPT_PW);
if( g.url.protocol==0 ) fossil_fatal("no server URL specified");
user_select();
url_enable_proxy("via proxy: ");
if( overwriteFlag ) mask |= CONFIGSET_OVERWRITE;
if( strncmp(zMethod, "push", n)==0 ){
client_sync(0,0,(unsigned)mask,0);
}else if( strncmp(zMethod, "pull", n)==0 ){
client_sync(0,(unsigned)mask,0,0);
}else{
client_sync(0,(unsigned)mask,(unsigned)mask,0);
}
}else
if( strncmp(zMethod, "reset", n)==0 ){
int mask, i;
char *zBackup;
if( g.argc!=4 ) usage("reset AREA");
mask = configure_name_to_mask(g.argv[3], 1);
|
| ︙ | ︙ |
| ︙ | ︙ | |||
97 98 99 100 101 102 103 | contentCache.szTotal += blob_size(pBlob); p->content = *pBlob; blob_zero(pBlob); bag_insert(&contentCache.inCache, rid); } /* | | > > | > > > > > | 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 |
contentCache.szTotal += blob_size(pBlob);
p->content = *pBlob;
blob_zero(pBlob);
bag_insert(&contentCache.inCache, rid);
}
/*
** Clear the content cache. If it is passed true, it
** also frees all associated memory, otherwise it may
** retain parts for future uses of the cache.
*/
void content_clear_cache(int bFreeIt){
int i;
for(i=0; i<contentCache.n; i++){
blob_reset(&contentCache.a[i].content);
}
bag_clear(&contentCache.missing);
bag_clear(&contentCache.available);
bag_clear(&contentCache.inCache);
contentCache.n = 0;
contentCache.szTotal = 0;
if(bFreeIt){
fossil_free(contentCache.a);
contentCache.a = 0;
contentCache.nAlloc = 0;
}
}
/*
** Return the srcid associated with rid. Or return 0 if rid is
** original content and not a delta.
*/
int delta_source_rid(int rid){
|
| ︙ | ︙ | |||
555 556 557 558 559 560 561 |
** have no data with which to dephantomize it. In either case,
** there is nothing for us to do other than return the RID. */
db_finalize(&s1);
db_end_transaction(0);
return rid;
}
}else{
| | | 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 |
** have no data with which to dephantomize it. In either case,
** there is nothing for us to do other than return the RID. */
db_finalize(&s1);
db_end_transaction(0);
return rid;
}
}else{
rid = 0; /* No entry with the same hash currently exists */
markAsUnclustered = 1;
}
db_finalize(&s1);
/* Construct a received-from ID if we do not already have one */
content_rcvid_init(0);
|
| ︙ | ︙ | |||
650 651 652 653 654 655 656 |
*/
int content_put(Blob *pBlob){
return content_put_ex(pBlob, 0, 0, 0, 0);
}
/*
| | | 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 |
*/
int content_put(Blob *pBlob){
return content_put_ex(pBlob, 0, 0, 0, 0);
}
/*
** Create a new phantom with the given hash and return its artifact ID.
*/
int content_new(const char *zUuid, int isPrivate){
int rid;
static Stmt s1, s2, s3;
assert( g.repositoryOpen );
db_begin_transaction();
|
| ︙ | ︙ | |||
768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 |
static Stmt s1;
db_static_prepare(&s1,
"DELETE FROM private WHERE rid=:rid"
);
db_bind_int(&s1, ":rid", rid);
db_exec(&s1);
}
/*
** Try to change the storage of rid so that it is a delta from one
** of the artifacts given in aSrc[0]..aSrc[nSrc-1]. The aSrc[*] that
** gives the smallest delta is choosen.
**
** If rid is already a delta from some other place then no
** conversion occurs and this is a no-op unless force==1. If force==1,
** then nSrc must also be 1.
**
** Never generate a delta that carries a private artifact into a public
** artifact. Otherwise, when we go to send the public artifact on a
** sync operation, the other end of the sync will never be able to receive
** the source of the delta. It is OK to delta private->private and
** public->private and public->public. Just no private->public delta.
**
| > > > > > > > > > > > > | | 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 |
static Stmt s1;
db_static_prepare(&s1,
"DELETE FROM private WHERE rid=:rid"
);
db_bind_int(&s1, ":rid", rid);
db_exec(&s1);
}
/*
** Make sure an artifact is private
*/
void content_make_private(int rid){
static Stmt s1;
db_static_prepare(&s1,
"INSERT OR IGNORE INTO private(rid) VALUES(:rid)"
);
db_bind_int(&s1, ":rid", rid);
db_exec(&s1);
}
/*
** Try to change the storage of rid so that it is a delta from one
** of the artifacts given in aSrc[0]..aSrc[nSrc-1]. The aSrc[*] that
** gives the smallest delta is choosen.
**
** If rid is already a delta from some other place then no
** conversion occurs and this is a no-op unless force==1. If force==1,
** then nSrc must also be 1.
**
** Never generate a delta that carries a private artifact into a public
** artifact. Otherwise, when we go to send the public artifact on a
** sync operation, the other end of the sync will never be able to receive
** the source of the delta. It is OK to delta private->private and
** public->private and public->public. Just no private->public delta.
**
** If aSrc[bestSrc] is already a delta that depends on rid, then it is
** converted to undeltaed text before the aSrc[bestSrc]->rid delta is
** created, in order to prevent a delta loop.
**
** If either rid or aSrc[i] contain less than 50 bytes, or if the
** resulting delta does not achieve a compression of at least 25%
** the rid is left untouched.
**
|
| ︙ | ︙ | |||
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 |
** COMMAND: test-integrity
**
** Verify that all content can be extracted from the BLOB table correctly.
** If the BLOB table is correct, then the repository can always be
** successfully reconstructed using "fossil rebuild".
**
** Options:
**
** --parse Parse all manifests, wikis, tickets, events, and
** so forth, reporting any errors found.
*/
void test_integrity(void){
Stmt q;
Blob content;
int n1 = 0;
int n2 = 0;
int nErr = 0;
int total;
int nCA = 0;
int anCA[10];
int bParse = find_option("parse",0,0)!=0;
db_find_and_open_repository(OPEN_ANY_SCHEMA, 2);
memset(anCA, 0, sizeof(anCA));
/* Make sure no public artifact is a delta from a private artifact */
db_prepare(&q,
"SELECT "
" rid, (SELECT uuid FROM blob WHERE rid=delta.rid),"
" srcid, (SELECT uuid FROM blob WHERE rid=delta.srcid)"
| > > > > > > > > > > > > > > > > > > > > | 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 |
** COMMAND: test-integrity
**
** Verify that all content can be extracted from the BLOB table correctly.
** If the BLOB table is correct, then the repository can always be
** successfully reconstructed using "fossil rebuild".
**
** Options:
**
** -d|--db-only Run "PRAGMA integrity_check" on the database only.
** No other validation is performed.
**
** --parse Parse all manifests, wikis, tickets, events, and
** so forth, reporting any errors found.
**
** -q|--quick Run "PRAGMA quick_check" on the database only.
** No other validation is performed.
*/
void test_integrity(void){
Stmt q;
Blob content;
int n1 = 0;
int n2 = 0;
int nErr = 0;
int total;
int nCA = 0;
int anCA[10];
int bParse = find_option("parse",0,0)!=0;
int bDbOnly = find_option("db-only","d",0)!=0;
int bQuick = find_option("quick","q",0)!=0;
db_find_and_open_repository(OPEN_ANY_SCHEMA, 2);
if( bDbOnly || bQuick ){
const char *zType = bQuick ? "quick" : "integrity";
char *zRes;
zRes = db_text(0,"PRAGMA repository.%s_check", zType/*safe-for-%s*/);
if( fossil_strcmp(zRes,"ok")!=0 ){
fossil_print("%s_check failed!\n", zType);
exit(1);
}else{
fossil_print("ok\n");
}
return;
}
memset(anCA, 0, sizeof(anCA));
/* Make sure no public artifact is a delta from a private artifact */
db_prepare(&q,
"SELECT "
" rid, (SELECT uuid FROM blob WHERE rid=delta.rid),"
" srcid, (SELECT uuid FROM blob WHERE rid=delta.srcid)"
|
| ︙ | ︙ | |||
1069 1070 1071 1072 1073 1074 1075 | } /* Allowed flags for check_exists */ #define MISSING_SHUNNED 0x0001 /* Do not report shunned artifacts */ /* This is a helper routine for test-artifacts. ** | | | > | | 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 |
}
/* Allowed flags for check_exists */
#define MISSING_SHUNNED 0x0001 /* Do not report shunned artifacts */
/* This is a helper routine for test-artifacts.
**
** Check to see that the artifact hash referenced by zUuid exists in the
** repository. If it does, return 0. If it does not, generate an error
** message and return 1.
*/
static int check_exists(
const char *zUuid, /* Hash of the artifact we are checking for */
unsigned flags, /* Flags */
Manifest *p, /* The control artifact that references zUuid */
const char *zRole, /* Role of zUuid in p */
const char *zDetail /* Additional information, such as a filename */
){
static Stmt q;
int rc = 0;
|
| ︙ | ︙ |
| ︙ | ︙ | |||
124 125 126 127 128 129 130 |
int i;
cookie_parse();
for(i=0; i<cookies.nParam && strcmp(zPName,cookies.aParam[i].zPName); i++){}
if( zQVal==0 && (flags & COOKIE_READ)!=0 && i<cookies.nParam ){
cgi_set_parameter_nocopy(zQP, cookies.aParam[i].zPValue, 1);
return;
}
| | > > > | 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
int i;
cookie_parse();
for(i=0; i<cookies.nParam && strcmp(zPName,cookies.aParam[i].zPName); i++){}
if( zQVal==0 && (flags & COOKIE_READ)!=0 && i<cookies.nParam ){
cgi_set_parameter_nocopy(zQP, cookies.aParam[i].zPValue, 1);
return;
}
if( zQVal==0 ){
zQVal = zDflt;
if( flags & COOKIE_WRITE ) cgi_set_parameter_nocopy(zQP, zQVal, 1);
}
if( (flags & COOKIE_WRITE)!=0
&& i<COOKIE_NPARAM
&& (i==cookies.nParam || strcmp(zQVal, cookies.aParam[i].zPValue))
){
if( i==cookies.nParam ){
cookies.aParam[i].zPName = zPName;
cookies.nParam++;
|
| ︙ | ︙ |
| ︙ | ︙ | |||
16 17 18 19 20 21 22 | ******************************************************************************* ** ** Code for interfacing to the various databases. ** ** There are three separate database files that fossil interacts ** with: ** | | > | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | ******************************************************************************* ** ** Code for interfacing to the various databases. ** ** There are three separate database files that fossil interacts ** with: ** ** (1) The "configdb" database in ~/.fossil or ~/.config/fossil.db ** or in %LOCALAPPDATA%/_fossil ** ** (2) The "repository" database ** ** (3) A local checkout database named "_FOSSIL_" or ".fslckout" ** and located at the root of the local copy of the source tree. ** */ |
| ︙ | ︙ | |||
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 |
va_list ap;
char *z;
va_start(ap, zFormat);
z = vmprintf(zFormat, ap);
va_end(ap);
#ifdef FOSSIL_ENABLE_JSON
if( g.json.isJsonMode ){
json_err( 0, z, 1 );
}
else
#endif /* FOSSIL_ENABLE_JSON */
if( g.xferPanic && g.cgiOutput==1 ){
cgi_reset_content();
@ error Database\serror:\s%F(z)
cgi_reply();
}
fossil_fatal("Database error: %s", z);
}
/*
** All static variable that a used by only this file are gathered into
** the following structure.
*/
static struct DbLocalData {
int nBegin; /* Nesting depth of BEGIN */
| > > > > > > > > > > > > > > > > > > | 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 |
va_list ap;
char *z;
va_start(ap, zFormat);
z = vmprintf(zFormat, ap);
va_end(ap);
#ifdef FOSSIL_ENABLE_JSON
if( g.json.isJsonMode ){
/*
** Avoid calling into the JSON support subsystem if it
** has not yet been initialized, e.g. early SQLite log
** messages, etc.
*/
if( !json_is_main_boostrapped() ) json_main_bootstrap();
json_err( 0, z, 1 );
}
else
#endif /* FOSSIL_ENABLE_JSON */
if( g.xferPanic && g.cgiOutput==1 ){
cgi_reset_content();
@ error Database\serror:\s%F(z)
cgi_reply();
}
fossil_fatal("Database error: %s", z);
}
/*
** Check a result code. If it is not SQLITE_OK, print the
** corresponding error message and exit.
*/
static void db_check_result(int rc, Stmt *pStmt){
if( rc!=SQLITE_OK ){
db_err("SQL error (%d,%d: %s) while running [%s]",
rc, sqlite3_extended_errcode(g.db),
sqlite3_errmsg(g.db), blob_str(&pStmt->sql));
}
}
/*
** All static variable that a used by only this file are gathered into
** the following structure.
*/
static struct DbLocalData {
int nBegin; /* Nesting depth of BEGIN */
|
| ︙ | ︙ | |||
115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
} db = {0, 0, 0, 0, 0, 0, };
/*
** Arrange for the given file to be deleted on a failure.
*/
void db_delete_on_failure(const char *zFilename){
assert( db.nDeleteOnFail<count(db.azDeleteOnFail) );
db.azDeleteOnFail[db.nDeleteOnFail++] = fossil_strdup(zFilename);
}
/*
** Return the transaction nesting depth. 0 means we are currently
** not in a transaction.
*/
| > | 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
} db = {0, 0, 0, 0, 0, 0, };
/*
** Arrange for the given file to be deleted on a failure.
*/
void db_delete_on_failure(const char *zFilename){
assert( db.nDeleteOnFail<count(db.azDeleteOnFail) );
if( zFilename==0 ) return;
db.azDeleteOnFail[db.nDeleteOnFail++] = fossil_strdup(zFilename);
}
/*
** Return the transaction nesting depth. 0 means we are currently
** not in a transaction.
*/
|
| ︙ | ︙ | |||
473 474 475 476 477 478 479 |
}
/*
** Reset or finalize a statement.
*/
int db_reset(Stmt *pStmt){
int rc;
| | | | | | 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 |
}
/*
** Reset or finalize a statement.
*/
int db_reset(Stmt *pStmt){
int rc;
if( g.fSqlStats ){ db_stats(pStmt); }
rc = sqlite3_reset(pStmt->pStmt);
db_check_result(rc, pStmt);
return rc;
}
int db_finalize(Stmt *pStmt){
int rc;
if( pStmt->pNext ){
pStmt->pNext->pPrev = pStmt->pPrev;
}
if( pStmt->pPrev ){
pStmt->pPrev->pNext = pStmt->pNext;
}else if( db.pAllStmt==pStmt ){
db.pAllStmt = pStmt->pNext;
}
pStmt->pNext = 0;
pStmt->pPrev = 0;
if( g.fSqlStats ){ db_stats(pStmt); }
blob_reset(&pStmt->sql);
rc = sqlite3_finalize(pStmt->pStmt);
db_check_result(rc, pStmt);
pStmt->pStmt = 0;
return rc;
}
/*
** Return the rowid of the most recent insert
*/
|
| ︙ | ︙ | |||
573 574 575 576 577 578 579 |
** invalid when the statement is stepped or reset.
*/
void db_ephemeral_blob(Stmt *pStmt, int N, Blob *pBlob){
blob_init(pBlob, sqlite3_column_blob(pStmt->pStmt, N),
sqlite3_column_bytes(pStmt->pStmt, N));
}
| < < < < < < < < < < | > > > > > > > > > > > > > | 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 |
** invalid when the statement is stepped or reset.
*/
void db_ephemeral_blob(Stmt *pStmt, int N, Blob *pBlob){
blob_init(pBlob, sqlite3_column_blob(pStmt->pStmt, N),
sqlite3_column_bytes(pStmt->pStmt, N));
}
/*
** Execute a single prepared statement until it finishes.
*/
int db_exec(Stmt *pStmt){
int rc;
while( (rc = db_step(pStmt))==SQLITE_ROW ){}
rc = db_reset(pStmt);
db_check_result(rc, pStmt);
return rc;
}
/*
** COMMAND: test-db-exec-error
**
** Invoke the db_exec() interface with an erroneous SQL statement
** in order to verify the error handling logic.
*/
void db_test_db_exec_cmd(void){
Stmt err;
db_find_and_open_repository(0,0);
db_prepare(&err, "INSERT INTO repository.config(name) VALUES(NULL);");
db_exec(&err);
}
/*
** Print the output of one or more SQL queries on standard output.
** This routine is used for debugging purposes only.
*/
int db_debug(const char *zSql, ...){
Blob sql;
|
| ︙ | ︙ | |||
635 636 637 638 639 640 641 |
z = zEnd;
}
blob_reset(&sql);
return rc;
}
/*
| | > | < < < < < < | < > > > > > > > > > > > > > > > > | 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 |
z = zEnd;
}
blob_reset(&sql);
return rc;
}
/*
** Execute multiple SQL statements. The input text is executed
** directly without any formatting.
*/
int db_exec_sql(const char *z){
int rc = SQLITE_OK;
sqlite3_stmt *pStmt;
const char *zEnd;
while( rc==SQLITE_OK && z[0] ){
pStmt = 0;
rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
if( rc ){
db_err("%s: {%s}", sqlite3_errmsg(g.db), z);
}else if( pStmt ){
db.nPrepare++;
while( sqlite3_step(pStmt)==SQLITE_ROW ){}
rc = sqlite3_finalize(pStmt);
if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z);
}
z = zEnd;
}
return rc;
}
/*
** Execute multiple SQL statements using printf-style formatting.
*/
int db_multi_exec(const char *zSql, ...){
Blob sql;
int rc;
va_list ap;
blob_init(&sql, 0, 0);
va_start(ap, zSql);
blob_vappendf(&sql, zSql, ap);
va_end(ap);
rc = db_exec_sql(blob_str(&sql));
blob_reset(&sql);
return rc;
}
/*
** Optionally make the following changes to the database if feasible and
** convenient. Do not start a transaction for these changes, but only
|
| ︙ | ︙ | |||
797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 |
db_finalize(&s);
return z;
}
/*
** Initialize a new database file with the given schema. If anything
** goes wrong, call db_err() to exit.
*/
void db_init_database(
const char *zFileName, /* Name of database file to create */
const char *zSchema, /* First part of schema */
... /* Additional SQL to run. Terminate with NULL. */
){
sqlite3 *db;
int rc;
const char *zSql;
va_list ap;
| > > > | > | > > > | 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 |
db_finalize(&s);
return z;
}
/*
** Initialize a new database file with the given schema. If anything
** goes wrong, call db_err() to exit.
**
** If zFilename is NULL, then create an empty repository in an in-memory
** database.
*/
void db_init_database(
const char *zFileName, /* Name of database file to create */
const char *zSchema, /* First part of schema */
... /* Additional SQL to run. Terminate with NULL. */
){
sqlite3 *db;
int rc;
const char *zSql;
va_list ap;
db = db_open(zFileName ? zFileName : ":memory:");
sqlite3_exec(db, "BEGIN EXCLUSIVE", 0, 0, 0);
rc = sqlite3_exec(db, zSchema, 0, 0, 0);
if( rc!=SQLITE_OK ){
db_err("%s", sqlite3_errmsg(db));
}
va_start(ap, zSchema);
while( (zSql = va_arg(ap, const char*))!=0 ){
rc = sqlite3_exec(db, zSql, 0, 0, 0);
if( rc!=SQLITE_OK ){
db_err("%s", sqlite3_errmsg(db));
}
}
va_end(ap);
sqlite3_exec(db, "COMMIT", 0, 0, 0);
if( zFileName || g.db!=0 ){
sqlite3_close(db);
}else{
g.db = db;
}
}
/*
** Function to return the number of seconds since 1970. This is
** the same as strftime('%s','now') but is more compact.
*/
void db_now_function(
|
| ︙ | ︙ | |||
1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 |
db_hextoblob, 0, 0);
sqlite3_create_function(db, "capunion", 1, SQLITE_UTF8, 0,
0, capability_union_step, capability_union_finalize);
sqlite3_create_function(db, "fullcap", 1, SQLITE_UTF8, 0,
capability_fullcap, 0, 0);
sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0,
alert_find_emailaddr_func, 0, 0);
}
#if USE_SEE
/*
** This is a pointer to the saved database encryption key string.
*/
static char *zSavedKey = 0;
| > > | 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 |
db_hextoblob, 0, 0);
sqlite3_create_function(db, "capunion", 1, SQLITE_UTF8, 0,
0, capability_union_step, capability_union_finalize);
sqlite3_create_function(db, "fullcap", 1, SQLITE_UTF8, 0,
capability_fullcap, 0, 0);
sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0,
alert_find_emailaddr_func, 0, 0);
sqlite3_create_function(db, "display_name", 1, SQLITE_UTF8, 0,
alert_display_name_func, 0, 0);
}
#if USE_SEE
/*
** This is a pointer to the saved database encryption key string.
*/
static char *zSavedKey = 0;
|
| ︙ | ︙ | |||
1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 |
fossil_panic("failed read, %u bytes at %p from pid %lu: %lu", nSize,
pAddress, processId, GetLastError());
}
}else{
fossil_panic("failed to open pid %lu: %lu", processId, GetLastError());
}
}
#endif /* defined(_WIN32) */
#endif /* USE_SEE */
/*
** If the database file zDbFile has a name that suggests that it is
** encrypted, then prompt for the database encryption key and return it
** in the blob *pKey. Or, if the encryption key has previously been
| > > > > > > > > > > > > > > > > > > > > > > > > > > | 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 |
fossil_panic("failed read, %u bytes at %p from pid %lu: %lu", nSize,
pAddress, processId, GetLastError());
}
}else{
fossil_panic("failed to open pid %lu: %lu", processId, GetLastError());
}
}
/*
** This function evaluates the specified TH1 script and attempts to parse
** its result as a colon-delimited triplet containing a process identifier,
** address, and size (in bytes) of the database encryption key. This is
** only necessary (or functional) on Windows.
*/
void db_read_saved_encryption_key_from_process_via_th1(
const char *zConfig /* The TH1 script to evaluate. */
){
int rc;
char *zResult;
Th_FossilInit(TH_INIT_DEFAULT | TH_INIT_NEED_CONFIG | TH_INIT_NO_REPO);
rc = Th_Eval(g.interp, 0, zConfig, -1);
zResult = (char*)Th_GetResult(g.interp, 0);
if( rc!=TH_OK ){
fossil_fatal("script for pid key failed: %s", zResult);
}
if( zResult ){
DWORD processId = 0;
LPVOID pAddress = NULL;
SIZE_T nSize = 0;
parse_pid_key_value(zResult, &processId, &pAddress, &nSize);
db_read_saved_encryption_key_from_process(processId, pAddress, nSize);
}
}
#endif /* defined(_WIN32) */
#endif /* USE_SEE */
/*
** If the database file zDbFile has a name that suggests that it is
** encrypted, then prompt for the database encryption key and return it
** in the blob *pKey. Or, if the encryption key has previously been
|
| ︙ | ︙ | |||
1233 1234 1235 1236 1237 1238 1239 |
sqlite3_create_function(db, "print", -1, SQLITE_UTF8, 0,db_sql_print,0,0);
sqlite3_create_function(
db, "is_selected", 1, SQLITE_UTF8, 0, file_is_selected,0,0
);
sqlite3_create_function(
db, "if_selected", 3, SQLITE_UTF8, 0, file_is_selected,0,0
);
| | | 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 |
sqlite3_create_function(db, "print", -1, SQLITE_UTF8, 0,db_sql_print,0,0);
sqlite3_create_function(
db, "is_selected", 1, SQLITE_UTF8, 0, file_is_selected,0,0
);
sqlite3_create_function(
db, "if_selected", 3, SQLITE_UTF8, 0, file_is_selected,0,0
);
if( g.fSqlTrace ) sqlite3_trace_v2(db, SQLITE_TRACE_PROFILE, db_sql_trace, 0);
db_add_aux_functions(db);
re_add_sql_func(db); /* The REGEXP operator */
foci_register(db); /* The "files_of_checkin" virtual table */
sqlite3_exec(db, "PRAGMA foreign_keys=OFF;", 0, 0, 0);
return db;
}
|
| ︙ | ︙ | |||
1255 1256 1257 1258 1259 1260 1261 |
/*
** zDbName is the name of a database file. Attach zDbName using
** the name zLabel.
*/
void db_attach(const char *zDbName, const char *zLabel){
Blob key;
| | | | | 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 |
/*
** zDbName is the name of a database file. Attach zDbName using
** the name zLabel.
*/
void db_attach(const char *zDbName, const char *zLabel){
Blob key;
if( db_table_exists(zLabel,"sqlite_schema") ) return;
blob_init(&key, 0, 0);
db_maybe_obtain_encryption_key(zDbName, &key);
if( fossil_getenv("FOSSIL_USE_SEE_TEXTKEY")==0 ){
char *zCmd = sqlite3_mprintf("ATTACH DATABASE %Q AS %Q KEY %Q",
zDbName, zLabel, blob_str(&key));
db_exec_sql(zCmd);
fossil_secure_zero(zCmd, strlen(zCmd));
sqlite3_free(zCmd);
}else{
char *zCmd = sqlite3_mprintf("ATTACH DATABASE %Q AS %Q KEY ''",
zDbName, zLabel);
db_exec_sql(zCmd);
sqlite3_free(zCmd);
#if USE_SEE
if( blob_size(&key)>0 ){
sqlite3_key_v2(g.db, zLabel, blob_str(&key), -1);
}
#endif
}
|
| ︙ | ︙ | |||
1332 1333 1334 1335 1336 1337 1338 |
db_set_main_schemaname(g.db, zLabel);
}else{
db_attach(zDbName, zLabel);
}
}
/*
| | < < > | > > > > | | | < < < < < < | < | | | < < | > > | > > > > > > > > | | | | | | > > | | | > > | > > > > > | > | < > > > | > > > > > > > > > | > > > > > | | > > > < > > | < | > | < | < > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > | | 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 |
db_set_main_schemaname(g.db, zLabel);
}else{
db_attach(zDbName, zLabel);
}
}
/*
** Close the per-user configuration database file
*/
void db_close_config(){
int iSlot = db_database_slot("configdb");
if( iSlot>0 ){
db_detach("configdb");
}else if( g.dbConfig ){
sqlite3_wal_checkpoint(g.dbConfig, 0);
sqlite3_close(g.dbConfig);
g.dbConfig = 0;
}else if( g.db && 0==iSlot ){
int rc;
sqlite3_wal_checkpoint(g.db, 0);
rc = sqlite3_close(g.db);
if( g.fSqlTrace ) fossil_trace("-- db_close_config(%d)\n", rc);
g.db = 0;
g.repositoryOpen = 0;
g.localOpen = 0;
}else{
return;
}
fossil_free(g.zConfigDbName);
g.zConfigDbName = 0;
}
/*
** Compute the name of the configuration database. If unable to find the
** database, return 0 if isOptional is true, or panic if isOptional is false.
**
** Space to hold the result comes from fossil_malloc().
*/
static char *db_configdb_name(int isOptional){
char *zHome; /* Home directory */
char *zDbName; /* Name of the database file */
/* On Windows, look for these directories, in order:
**
** FOSSIL_HOME
** LOCALAPPDATA
** APPDATA
** USERPROFILE
** HOMEDRIVE HOMEPATH
*/
#if defined(_WIN32) || defined(__CYGWIN__)
zHome = fossil_getenv("FOSSIL_HOME");
if( zHome==0 ){
zHome = fossil_getenv("LOCALAPPDATA");
if( zHome==0 ){
zHome = fossil_getenv("APPDATA");
if( zHome==0 ){
zHome = fossil_getenv("USERPROFILE");
if( zHome==0 ){
char *zDrive = fossil_getenv("HOMEDRIVE");
char *zPath = fossil_getenv("HOMEPATH");
if( zDrive && zPath ) zHome = mprintf("%s%s", zDrive, zPath);
}
}
}
}
zDbName = mprintf("%//_fossil", zHome);
fossil_free(zHome);
return zDbName;
#else /* if unix */
char *zXdgHome;
/* For unix. a 5-step algorithm is used.
** See ../www/tech_overview.wiki for discussion.
**
** Step 1: If FOSSIL_HOME exists -> $FOSSIL_HOME/.fossil
*/
zHome = fossil_getenv("FOSSIL_HOME");
if( zHome!=0 ) return mprintf("%s/.fossil", zHome);
/* Step 2: If HOME exists and file $HOME/.fossil exists -> $HOME/.fossil
*/
zHome = fossil_getenv("HOME");
if( zHome ){
zDbName = mprintf("%s/.fossil", zHome);
if( file_size(zDbName, ExtFILE)>1024*3 ){
return zDbName;
}
fossil_free(zDbName);
}
/* Step 3: if XDG_CONFIG_HOME exists -> $XDG_CONFIG_HOME/fossil.db
*/
zXdgHome = fossil_getenv("XDG_CONFIG_HOME");
if( zXdgHome!=0 ){
return mprintf("%s/fossil.db", zXdgHome);
}
/* The HOME variable is required in order to continue.
*/
if( zHome==0 ){
if( isOptional ) return 0;
fossil_panic("cannot locate home directory - please set one of the "
"FOSSIL_HOME, XDG_CONFIG_HOME, or HOME environment "
"variables");
}
/* Step 4: If $HOME/.config is a directory -> $HOME/.config/fossil.db
*/
zXdgHome = mprintf("%s/.config", zHome);
if( file_isdir(zXdgHome, ExtFILE)==1 ){
fossil_free(zXdgHome);
return mprintf("%s/.config/fossil.db", zHome);
}
/* Step 5: Otherwise -> $HOME/.fossil
*/
return mprintf("%s/.fossil", zHome);
#endif /* unix */
}
/*
** Open the configuration database. Create the database anew if
** it does not already exist.
**
** If the useAttach flag is 0 (the usual case) then the configuration
** database is opened on a separate database connection g.dbConfig.
** This prevents the database from becoming locked on long check-in or sync
** operations which hold an exclusive transaction. In a few cases, though,
** it is convenient for the database to be attached to the main database
** connection so that we can join between the various databases. In that
** case, invoke this routine with useAttach as 1.
*/
int db_open_config(int useAttach, int isOptional){
char *zDbName;
if( g.zConfigDbName ){
int alreadyAttached = db_database_slot("configdb")>0;
if( useAttach==alreadyAttached ) return 1; /* Already open. */
db_close_config();
}
zDbName = db_configdb_name(isOptional);
if( zDbName==0 ) return 0;
if( file_size(zDbName, ExtFILE)<1024*3 ){
char *zHome = file_dirname(zDbName);
int rc;
if( file_isdir(zHome, ExtFILE)==0 ){
file_mkdir(zHome, ExtFILE, 0);
}
rc = file_access(zHome, W_OK);
fossil_free(zHome);
if( rc ){
if( isOptional ) return 0;
fossil_panic("home directory \"%s\" must be writeable", zHome);
}
db_init_database(zDbName, zConfigSchema, (char*)0);
}
if( file_access(zDbName, W_OK) ){
if( isOptional ) return 0;
fossil_panic("configuration file %s must be writeable", zDbName);
}
|
| ︙ | ︙ | |||
1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 |
const char *db_repository_filename(void){
static char *zRepo = 0;
assert( g.localOpen );
assert( g.zLocalRoot );
if( zRepo==0 ){
zRepo = db_lget("repository", 0);
if( zRepo && !file_is_absolute_path(zRepo) ){
zRepo = mprintf("%s%s", g.zLocalRoot, zRepo);
}
}
return zRepo;
}
/*
** Returns non-zero if the default value for the "allow-symlinks" setting
| > > | 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 |
const char *db_repository_filename(void){
static char *zRepo = 0;
assert( g.localOpen );
assert( g.zLocalRoot );
if( zRepo==0 ){
zRepo = db_lget("repository", 0);
if( zRepo && !file_is_absolute_path(zRepo) ){
char * zFree = zRepo;
zRepo = mprintf("%s%s", g.zLocalRoot, zRepo);
fossil_free(zFree);
}
}
return zRepo;
}
/*
** Returns non-zero if the default value for the "allow-symlinks" setting
|
| ︙ | ︙ | |||
1706 1707 1708 1709 1710 1711 1712 |
exit(0);
}else{
char *z;
stash_rid_renumbering_event();
vfile_rid_renumbering_event(0);
undo_reset();
bisect_reset();
| | | 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 |
exit(0);
}else{
char *z;
stash_rid_renumbering_event();
vfile_rid_renumbering_event(0);
undo_reset();
bisect_reset();
z = db_fingerprint(0, 1);
db_lset("fingerprint", z);
fossil_free(z);
fossil_print(
"WARNING: The repository database has been replaced by a clone.\n"
"Bisect history and undo have been lost.\n"
);
}
|
| ︙ | ︙ | |||
1731 1732 1733 1734 1735 1736 1737 |
db_multi_exec("ALTER TABLE vfile ADD COLUMN mhash;");
db_multi_exec(
"UPDATE vfile"
" SET mhash=(SELECT uuid FROM blob WHERE blob.rid=vfile.mrid)"
" WHERE mrid!=rid;"
);
if( !db_table_has_column("localdb", "vmerge", "mhash") ){
| | | | | 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 |
db_multi_exec("ALTER TABLE vfile ADD COLUMN mhash;");
db_multi_exec(
"UPDATE vfile"
" SET mhash=(SELECT uuid FROM blob WHERE blob.rid=vfile.mrid)"
" WHERE mrid!=rid;"
);
if( !db_table_has_column("localdb", "vmerge", "mhash") ){
db_exec_sql("ALTER TABLE vmerge RENAME TO old_vmerge;");
db_exec_sql(zLocalSchemaVmerge);
db_exec_sql(
"INSERT OR IGNORE INTO vmerge(id,merge,mhash)"
" SELECT id, merge, blob.uuid FROM old_vmerge, blob"
" WHERE old_vmerge.merge=blob.rid;"
"DROP TABLE old_vmerge;"
);
}
}
|
| ︙ | ︙ | |||
1763 1764 1765 1766 1767 1768 1769 | return g.iRepoDataVers != v; } /* ** Flags for the db_find_and_open_repository() function. */ #if INTERFACE | | | > | 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 | return g.iRepoDataVers != v; } /* ** Flags for the db_find_and_open_repository() function. */ #if INTERFACE #define OPEN_OK_NOT_FOUND 0x001 /* Do not error out if not found */ #define OPEN_ANY_SCHEMA 0x002 /* Do not error if schema is wrong */ #define OPEN_SUBSTITUTE 0x004 /* Fake in-memory repo if not found */ #endif /* ** Try to find the repository and open it. Use the -R or --repository ** option to locate the repository. If no such option is available, then ** use the repository of the open checkout if there is one. ** |
| ︙ | ︙ | |||
1797 1798 1799 1800 1801 1802 1803 |
}
db_open_repository(zRep);
if( g.repositoryOpen ){
if( (bFlags & OPEN_ANY_SCHEMA)==0 ) db_verify_schema();
return;
}
rep_not_found:
| | > > > > > | 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 |
}
db_open_repository(zRep);
if( g.repositoryOpen ){
if( (bFlags & OPEN_ANY_SCHEMA)==0 ) db_verify_schema();
return;
}
rep_not_found:
if( bFlags & OPEN_OK_NOT_FOUND ){
/* No errors if the database is not found */
if( bFlags & OPEN_SUBSTITUTE ){
db_create_repository(0);
}
}else{
#ifdef FOSSIL_ENABLE_JSON
g.json.resultCode = FSL_JSON_E_DB_NOT_FOUND;
#endif
if( nArgUsed==0 ){
fossil_fatal("use --repository or -R to specify the repository database");
}else{
fossil_fatal("specify the repository name as a command-line argument");
|
| ︙ | ︙ | |||
2027 2028 2029 2030 2031 2032 2033 |
if( zUser==0 ){
zUser = "root";
}
db_multi_exec(
"INSERT OR IGNORE INTO user(login, info) VALUES(%Q,'')", zUser
);
db_multi_exec(
| | | | | 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 |
if( zUser==0 ){
zUser = "root";
}
db_multi_exec(
"INSERT OR IGNORE INTO user(login, info) VALUES(%Q,'')", zUser
);
db_multi_exec(
"UPDATE user SET cap='s', pw=%Q"
" WHERE login=%Q", fossil_random_password(10), zUser
);
if( !setupUserOnly ){
db_multi_exec(
"INSERT OR IGNORE INTO user(login,pw,cap,info)"
" VALUES('anonymous',hex(randomblob(8)),'hmnc','Anon');"
"INSERT OR IGNORE INTO user(login,pw,cap,info)"
" VALUES('nobody','','gjorz','Nobody');"
"INSERT OR IGNORE INTO user(login,pw,cap,info)"
" VALUES('developer','','ei','Dev');"
"INSERT OR IGNORE INTO user(login,pw,cap,info)"
" VALUES('reader','','kptw','Reader');"
);
}
}
/*
|
| ︙ | ︙ | |||
2261 2262 2263 2264 2265 2266 2267 | /* ** SQL functions for debugging. ** ** The print() function writes its arguments on stdout, but only ** if the -sqlprint command-line option is turned on. */ | | > > > > | > > > > > > > > > > > > > > > > > | | 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 |
/*
** SQL functions for debugging.
**
** The print() function writes its arguments on stdout, but only
** if the -sqlprint command-line option is turned on.
*/
void db_sql_print(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
int i;
if( g.fSqlPrint ){
for(i=0; i<argc; i++){
char c = i==argc-1 ? '\n' : ' ';
fossil_print("%s%c", sqlite3_value_text(argv[i]), c);
}
}
}
/*
** Callback for sqlite3_trace_v2();
*/
int db_sql_trace(unsigned m, void *notUsed, void *pP, void *pX){
sqlite3_stmt *pStmt = (sqlite3_stmt*)pP;
char *zSql;
int n;
const char *zArg = (const char*)pX;
char zEnd[40];
if( m & SQLITE_TRACE_CLOSE ){
/* If we are tracking closes, that means we want to clean up static
** prepared statements. */
while( db.pAllStmt ){
db_finalize(db.pAllStmt);
}
return 0;
}
if( zArg[0]=='-' ) return 0;
if( m & SQLITE_TRACE_PROFILE ){
sqlite3_int64 nNano = *(sqlite3_int64*)pX;
double rMillisec = 0.000001 * nNano;
sqlite3_snprintf(sizeof(zEnd),zEnd," /* %.3fms */\n", rMillisec);
}else{
zEnd[0] = '\n';
zEnd[1] = 0;
}
zSql = sqlite3_expanded_sql(pStmt);
n = (int)strlen(zSql);
fossil_trace("%s%s%s", zSql, (n>0 && zSql[n-1]==';') ? "" : ";", zEnd);
sqlite3_free(zSql);
return 0;
}
/*
** Implement the user() SQL function. user() takes no arguments and
** returns the user ID of the current user.
|
| ︙ | ︙ | |||
2448 2449 2450 2451 2452 2453 2454 |
if( fossil_stricmp(zVal,azOff[i])==0 ) return 1;
}
return 0;
}
/*
** Swap the g.db and g.dbConfig connections so that the various db_* routines
| | | | | | 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 |
if( fossil_stricmp(zVal,azOff[i])==0 ) return 1;
}
return 0;
}
/*
** Swap the g.db and g.dbConfig connections so that the various db_* routines
** work on the configuration database instead of on the repository database.
** Be sure to swap them back after doing the operation.
**
** If the configuration database has already been opened as the main database
** or is attached to the main database, no connection swaps are required so
** this routine is a no-op.
*/
void db_swap_connections(void){
/*
** When swapping the main database connection with the config database
** connection, the config database connection must be open (not simply
** attached); otherwise, the swap would end up leaving the main database
** connection invalid, defeating the very purpose of this routine. This
|
| ︙ | ︙ | |||
2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 |
db_swap_connections();
z = db_text(0, "SELECT value FROM global_config WHERE name=%Q", zName);
db_swap_connections();
}
if( pSetting!=0 && pSetting->versionable ){
/* This is a versionable setting, try and get the info from a
** checked out file */
z = db_get_versioned(zName, z);
}
if( z==0 ){
if( zDefault==0 && pSetting && pSetting->def[0] ){
z = fossil_strdup(pSetting->def);
}else{
z = fossil_strdup(zDefault);
}
| > > > > | 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 |
db_swap_connections();
z = db_text(0, "SELECT value FROM global_config WHERE name=%Q", zName);
db_swap_connections();
}
if( pSetting!=0 && pSetting->versionable ){
/* This is a versionable setting, try and get the info from a
** checked out file */
char * zZ = z;
z = db_get_versioned(zName, z);
if(zZ != z){
fossil_free(zZ);
}
}
if( z==0 ){
if( zDefault==0 && pSetting && pSetting->def[0] ){
z = fossil_strdup(pSetting->def);
}else{
z = fossil_strdup(zDefault);
}
|
| ︙ | ︙ | |||
2697 2698 2699 2700 2701 2702 2703 |
}
if( globalFlag && g.repositoryOpen ){
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
}
}
int db_get_boolean(const char *zName, int dflt){
char *zVal = db_get(zName, dflt ? "on" : "off");
| | > | > > > | 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 |
}
if( globalFlag && g.repositoryOpen ){
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
}
}
int db_get_boolean(const char *zName, int dflt){
char *zVal = db_get(zName, dflt ? "on" : "off");
if( is_truth(zVal) ){
dflt = 1;
}else if( is_false(zVal) ){
dflt = 0;
}
fossil_free(zVal);
return dflt;
}
int db_get_versioned_boolean(const char *zName, int dflt){
char *zVal = db_get_versioned(zName, 0);
if( zVal==0 ) return dflt;
if( is_truth(zVal) ) return 1;
if( is_false(zVal) ) return 0;
|
| ︙ | ︙ | |||
2916 2917 2918 2919 2920 2921 2922 |
db_open_repository(g.argv[2]);
/* Figure out which revision to open. */
if( !emptyFlag ){
if( g.argc==4 ){
g.zOpenRevision = g.argv[3];
}else if( db_exists("SELECT 1 FROM event WHERE type='ci'") ){
| | | 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 |
db_open_repository(g.argv[2]);
/* Figure out which revision to open. */
if( !emptyFlag ){
if( g.argc==4 ){
g.zOpenRevision = g.argv[3];
}else if( db_exists("SELECT 1 FROM event WHERE type='ci'") ){
g.zOpenRevision = db_get("main-branch", 0);
}
}
if( g.zOpenRevision ){
/* Since the repository is open and we know the revision now,
** refresh the allow-symlinks flag. Since neither the local
** checkout nor the configuration database are open at this
|
| ︙ | ︙ | |||
3039 3040 3041 3042 3043 3044 3045 | /* ** Define all settings, which can be controlled via the set/unset ** command. ** ** var is the name of the internal configuration name for db_(un)set. ** If var is 0, the settings name is used. ** | | | > > > | > > | 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 |
/*
** Define all settings, which can be controlled via the set/unset
** command.
**
** var is the name of the internal configuration name for db_(un)set.
** If var is 0, the settings name is used.
**
** width is the length for the edit field on the behavior page, 0 is
** used for on/off checkboxes. A negative value indicates that that
** page should not render this setting. Such values may be rendered
** separately/manually on another page, e.g., /setup_access, and are
** exposed via the CLI settings command.
**
** The behaviour page doesn't use a special layout. It lists all
** set-commands and displays the 'set'-help as info.
*/
struct Setting {
const char *name; /* Name of the setting */
const char *var; /* Internal variable name used by db_set() */
int width; /* Width of display. 0 for boolean values and
** negative for values which should not appear
** on the /setup_settings page. */
int versionable; /* Is this setting versionable? */
int forceTextArea; /* Force using a text area for display? */
const char *def; /* Default value */
};
#endif /* INTERFACE */
/*
|
| ︙ | ︙ | |||
3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 | #if !defined(FOSSIL_ENABLE_EXEC_REL_PATHS) /* ** SETTING: exec-rel-paths boolean default=off ** When executing certain external commands (e.g. diff and ** gdiff), use relative paths. */ #endif /* ** SETTING: gdiff-command width=40 default=gdiff ** The value is an external command to run when performing a graphical ** diff. If undefined, text diff will be used. */ /* ** SETTING: gmerge-command width=40 | > > > > > > > > > | 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 | #if !defined(FOSSIL_ENABLE_EXEC_REL_PATHS) /* ** SETTING: exec-rel-paths boolean default=off ** When executing certain external commands (e.g. diff and ** gdiff), use relative paths. */ #endif /* ** SETTING: fileedit-glob width=40 block-text ** A comma- or newline-separated list of globs of filenames ** which are allowed to be edited using the /fileedit page. ** An empty list prohibits editing via that page. Note that ** it cannot edit binary files, so the list should not ** contain any globs for, e.g., images or PDFs. */ /* ** SETTING: gdiff-command width=40 default=gdiff ** The value is an external command to run when performing a graphical ** diff. If undefined, text diff will be used. */ /* ** SETTING: gmerge-command width=40 |
| ︙ | ︙ | |||
3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 | ** For maximum security, set "localauth" to 1. However, because ** of the other restrictions (2) through (4), it should be safe ** to leave "localauth" set to 0 in most installations, and ** especially on cloned repositories on workstations. Leaving ** "localauth" at 0 makes the "fossil ui" command more convenient ** to use. */ /* ** SETTING: main-branch width=40 default=trunk ** The value is the primary branch for the project. */ /* ** SETTING: manifest width=5 versionable ** If enabled, automatically create files "manifest" and "manifest.uuid" | > > > > > > > > > > > > > > > > > > | 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 | ** For maximum security, set "localauth" to 1. However, because ** of the other restrictions (2) through (4), it should be safe ** to leave "localauth" set to 0 in most installations, and ** especially on cloned repositories on workstations. Leaving ** "localauth" at 0 makes the "fossil ui" command more convenient ** to use. */ /* ** SETTING: lock-timeout width=25 default=60 ** This is the number of seconds that a check-in lock will be held on ** the server before the lock expires. The default is a 60-second delay. ** Set this value to zero to disable the check-in lock mechanism. ** ** This value should be set on the server to which users auto-sync ** their work. This setting has no affect on client repositories. The ** check-in lock mechanism is only effective if all users are auto-syncing ** to the same server. ** ** Check-in locks are an advisory mechanism designed to help prevent ** accidental forks due to a check-in race in installations where many ** user are committing to the same branch and auto-sync is enabled. ** As forks are harmless, there is no danger in disabling this mechanism. ** However, keeping check-in locks turned on can help prevent unnecessary ** confusion. */ /* ** SETTING: main-branch width=40 default=trunk ** The value is the primary branch for the project. */ /* ** SETTING: manifest width=5 versionable ** If enabled, automatically create files "manifest" and "manifest.uuid" |
| ︙ | ︙ | |||
3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 | ** when running as a web-server, Fossil does not open the ** global configuration database. */ /* ** SETTING: max-upload width=25 default=250000 ** A limit on the size of uplink HTTP requests. */ /* ** SETTING: mtime-changes boolean default=on ** Use file modification times (mtimes) to detect when ** files have been modified. If disabled, all managed files ** are hashed to detect changes, which can be slow for large ** projects. */ | > > > > > > > | 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 | ** when running as a web-server, Fossil does not open the ** global configuration database. */ /* ** SETTING: max-upload width=25 default=250000 ** A limit on the size of uplink HTTP requests. */ /* ** SETTING: mimetypes width=40 versionable block-text ** A list of file extension-to-mimetype mappings, one per line. e.g. ** "foo application/x-foo". File extensions are compared ** case-insensitively in the order listed in this setting. A leading ** '.' on file extensions is permitted but not required. */ /* ** SETTING: mtime-changes boolean default=on ** Use file modification times (mtimes) to detect when ** files have been modified. If disabled, all managed files ** are hashed to detect changes, which can be slow for large ** projects. */ |
| ︙ | ︙ | |||
3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 | */ #endif /* ** SETTING: pgp-command width=40 ** Command used to clear-sign manifests at check-in. ** Default value is "gpg --clearsign -o" */ /* ** SETTING: proxy width=32 default=off ** URL of the HTTP proxy. If undefined or "off" then ** the "http_proxy" environment variable is consulted. ** If the http_proxy environment variable is undefined ** then a direct HTTP connection is used. */ /* ** SETTING: relative-paths boolean default=on ** When showing changes and extras, report paths relative ** to the current working directory. */ /* ** SETTING: repo-cksum boolean default=on | > > > > > > > > > > > | 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 | */ #endif /* ** SETTING: pgp-command width=40 ** Command used to clear-sign manifests at check-in. ** Default value is "gpg --clearsign -o" */ /* ** SETTING: forbid-delta-manifests boolean default=off ** If enabled, new delta manifests are prohibited. */ /* ** SETTING: proxy width=32 default=off ** URL of the HTTP proxy. If undefined or "off" then ** the "http_proxy" environment variable is consulted. ** If the http_proxy environment variable is undefined ** then a direct HTTP connection is used. */ /* ** SETTING: redirect-to-https default=0 width=-1 ** Specifies whether or not to redirect http:// requests to ** https:// URIs. A value of 0 (the default) means not to ** redirect, 1 means to redirect only the /login page, and 2 ** means to always redirect. */ /* ** SETTING: relative-paths boolean default=on ** When showing changes and extras, report paths relative ** to the current working directory. */ /* ** SETTING: repo-cksum boolean default=on |
| ︙ | ︙ | |||
3486 3487 3488 3489 3490 3491 3492 | ** If enabled Tcl integration commands will be added to the TH1 ** interpreter, allowing arbitrary Tcl expressions and ** scripts to be evaluated from TH1. Additionally, the Tcl ** interpreter will be able to evaluate arbitrary TH1 ** expressions and scripts. */ /* | | | 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 | ** If enabled Tcl integration commands will be added to the TH1 ** interpreter, allowing arbitrary Tcl expressions and ** scripts to be evaluated from TH1. Additionally, the Tcl ** interpreter will be able to evaluate arbitrary TH1 ** expressions and scripts. */ /* ** SETTING: tcl-setup width=40 block-text ** This is the setup script to be evaluated after creating ** and initializing the Tcl interpreter. By default, this ** is empty and no extra setup is performed. */ #endif /* FOSSIL_ENABLE_TCL */ /* ** SETTING: tclsh width=80 default=tclsh |
| ︙ | ︙ | |||
3518 3519 3520 3521 3522 3523 3524 | /* ** SETTING: th1-hooks boolean default=off ** If enabled, special TH1 commands will be called before and ** after any Fossil command or web page. */ #endif /* | | | > > > > > > > > > > > > > > > > > > > > | 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 | /* ** SETTING: th1-hooks boolean default=off ** If enabled, special TH1 commands will be called before and ** after any Fossil command or web page. */ #endif /* ** SETTING: th1-setup width=40 block-text ** This is the setup script to be evaluated after creating ** and initializing the TH1 interpreter. By default, this ** is empty and no extra setup is performed. */ /* ** SETTING: th1-uri-regexp width=40 block-text ** Specify which URI's are allowed in HTTP requests from ** TH1 scripts. If empty, no HTTP requests are allowed ** whatsoever. */ /* ** SETTING: default-csp width=40 block-text ** ** The text of the Content Security Policy that is included ** in the Content-Security-Policy: header field of the HTTP ** reply and in the default HTML <head> section that is added when the ** skin header does not specify a <head> section. The text "$nonce" ** is replaced by the random nonce that is created for each web page. ** ** If this setting is an empty string or is omitted, then ** the following default Content Security Policy is used: ** ** default-src 'self' data:; ** script-src 'self' 'nonce-$nonce'; ** style-src 'self' 'unsafe-inline'; ** ** The default CSP is recommended. The main reason to change ** this setting would be to add CDNs from which it is safe to ** load additional content. */ /* ** SETTING: uv-sync boolean default=off ** If true, automatically send unversioned files as part ** of a "fossil clone" or "fossil sync" command. The ** default is false, in which case the -u option is ** needed to clone or sync unversioned files. */ |
| ︙ | ︙ | |||
3597 3598 3599 3600 3601 3602 3603 | ** file exists. ** ** The "unset" command clears a setting. ** ** Settings can have both a "local" repository-only value and "global" value ** that applies to all repositories. The local values are stored in the ** "config" table of the repository and the global values are stored in the | < | | | | 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 | ** file exists. ** ** The "unset" command clears a setting. ** ** Settings can have both a "local" repository-only value and "global" value ** that applies to all repositories. The local values are stored in the ** "config" table of the repository and the global values are stored in the ** configuration database. If both a local and a global value exists for a ** setting, the local value takes precedence. This command normally operates ** on the local settings. Use the --global option to change global settings. ** ** Options: ** --global set or unset the given property globally instead of ** setting or unsetting it for the open repository only. ** ** --exact only consider exact name matches. ** |
| ︙ | ︙ | |||
3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 |
double rDiff;
if( g.argc!=3 ) usage("TIMESTAMP");
sqlite3_open(":memory:", &g.db);
rDiff = db_double(0.0, "SELECT julianday('now') - julianday(%Q)", g.argv[2]);
fossil_print("Time differences: %s\n", db_timespan_name(rDiff));
sqlite3_close(g.db);
g.db = 0;
}
/*
** COMMAND: test-without-rowid
**
** Usage: %fossil test-without-rowid FILENAME...
**
** Change the Fossil repository FILENAME to make use of the WITHOUT ROWID
| > > | | | | 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 |
double rDiff;
if( g.argc!=3 ) usage("TIMESTAMP");
sqlite3_open(":memory:", &g.db);
rDiff = db_double(0.0, "SELECT julianday('now') - julianday(%Q)", g.argv[2]);
fossil_print("Time differences: %s\n", db_timespan_name(rDiff));
sqlite3_close(g.db);
g.db = 0;
g.repositoryOpen = 0;
g.localOpen = 0;
}
/*
** COMMAND: test-without-rowid
**
** Usage: %fossil test-without-rowid FILENAME...
**
** Change the Fossil repository FILENAME to make use of the WITHOUT ROWID
** optimization. FILENAME can also be the configuration database file
** (~/.fossil or ~/.config/fossil.db) or a local .fslckout or _FOSSIL_ file.
**
** The purpose of this command is for testing the WITHOUT ROWID capabilities
** of SQLite. There is no big advantage to using WITHOUT ROWID in Fossil.
**
** Options:
** --dryrun | -n No changes. Just print what would happen.
*/
void test_without_rowid(void){
int i, j;
Stmt q;
Blob allSql;
int dryRun = find_option("dry-run", "n", 0)!=0;
for(i=2; i<g.argc; i++){
db_open_or_attach(g.argv[i], "main");
blob_init(&allSql, "BEGIN;\n", -1);
db_prepare(&q,
"SELECT name, sql FROM main.sqlite_schema "
" WHERE type='table' AND sql NOT LIKE '%%WITHOUT ROWID%%'"
" AND name IN ('global_config','shun','concealed','config',"
" 'plink','tagxref','backlink','vcache');"
);
while( db_step(&q)==SQLITE_ROW ){
const char *zTName = db_column_text(&q, 0);
const char *zOrigSql = db_column_text(&q, 1);
|
| ︙ | ︙ | |||
3876 3877 3878 3879 3880 3881 3882 | ** ** The fingerprint consists of the rcvid, a "/", and the MD5 checksum of ** the remaining fields of the RCVFROM table entry. MD5 is used for this ** because it is 4x faster than SHA3 and 5x faster than SHA1, and there ** are no security concerns - this is just a checksum, not a security ** token. */ | | > > > > > | | | | > > > > > > > | 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 |
**
** The fingerprint consists of the rcvid, a "/", and the MD5 checksum of
** the remaining fields of the RCVFROM table entry. MD5 is used for this
** because it is 4x faster than SHA3 and 5x faster than SHA1, and there
** are no security concerns - this is just a checksum, not a security
** token.
*/
char *db_fingerprint(int rcvid, int iVersion){
char *z = 0;
Blob sql = BLOB_INITIALIZER;
Stmt q;
if( iVersion==0 ){
/* The original fingerprint algorithm used "quote(mtime)". But this
** could give slightly different answers depending on how the floating-
** point hardware is configured. For example, it gave different
** answers on native Linux versus running under valgrind. */
blob_append_sql(&sql,
"SELECT rcvid, quote(uid), quote(mtime), quote(nonce), quote(ipaddr)"
" FROM rcvfrom"
);
}else{
/* These days, we use "datetime(mtime)" for more consistent answers */
blob_append_sql(&sql,
"SELECT rcvid, quote(uid), datetime(mtime), quote(nonce), quote(ipaddr)"
" FROM rcvfrom"
);
}
if( rcvid<=0 ){
blob_append_sql(&sql, " ORDER BY rcvid DESC LIMIT 1");
}else{
blob_append_sql(&sql, " WHERE rcvid=%d", rcvid);
}
db_prepare_blob(&q, &sql);
blob_reset(&sql);
|
| ︙ | ︙ | |||
3906 3907 3908 3909 3910 3911 3912 | db_finalize(&q); return z; } /* ** COMMAND: test-fingerprint ** | | | | > | < < < < < | > > > > > > > > | | | | | | > > > > > | | > > > > > | > > | 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 |
db_finalize(&q);
return z;
}
/*
** COMMAND: test-fingerprint
**
** Usage: %fossil test-fingerprint ?RCVID?
**
** Display the repository fingerprint using the supplied RCVID or
** using the latest RCVID if not is given on the command line.
** Show both the legacy and the newer version of the fingerprint,
** and the currently stored fingerprint if there is one.
*/
void test_fingerprint(void){
int rcvid = 0;
db_find_and_open_repository(OPEN_ANY_SCHEMA,0);
if( g.argc==3 ){
rcvid = atoi(g.argv[2]);
}else if( g.argc!=2 ){
fossil_fatal("wrong number of arguments");
}
fossil_print("legacy: %z\n", db_fingerprint(rcvid, 0));
fossil_print("version-1: %z\n", db_fingerprint(rcvid, 1));
if( g.localOpen ){
fossil_print("localdb: %z\n", db_lget("fingerprint","(none)"));
fossil_print("db_fingerprint_ok(): %d\n", db_fingerprint_ok());
}
fossil_print("Fossil version: %s - %.10s %.19s\n",
RELEASE_VERSION, MANIFEST_DATE, MANIFEST_UUID);
}
/*
** Set the value of the "checkout" entry in the VVAR table.
**
** Also set "fingerprint" and "checkout-hash".
*/
void db_set_checkout(int rid){
char *z;
db_lset_int("checkout", rid);
if (rid != 0) {
z = db_text(0,"SELECT uuid FROM blob WHERE rid=%d",rid);
db_lset("checkout-hash", z);
fossil_free(z);
z = db_fingerprint(0, 1);
db_lset("fingerprint", z);
fossil_free(z);
}
}
/*
** Verify that the fingerprint recorded in the "fingerprint" entry
** of the VVAR table matches the fingerprint on the currently
** connected repository. Return true if the fingerprint is ok, and
** return false if the fingerprint does not match.
*/
int db_fingerprint_ok(void){
char *zCkout; /* The fingerprint recorded in the checkout database */
char *zRepo; /* The fingerprint of the repository */
int rc; /* Result */
if( !db_lget_int("checkout", 0) ){
/* We have an empty checkout, fingerprint is still NULL. */
return 2;
}
zCkout = db_text(0,"SELECT value FROM localdb.vvar WHERE name='fingerprint'");
if( zCkout==0 ){
/* This is an older checkout that does not record a fingerprint.
** We have to assume everything is ok */
return 2;
}
zRepo = db_fingerprint(atoi(zCkout), 1);
rc = fossil_strcmp(zCkout,zRepo)==0;
fossil_free(zRepo);
/* If the initial test fails, try again using the older fingerprint
** algorithm */
if( !rc ){
zRepo = db_fingerprint(atoi(zCkout), 0);
rc = fossil_strcmp(zCkout,zRepo)==0;
fossil_free(zRepo);
}
fossil_free(zCkout);
return rc;
}
|
|
| | | | < | < < < < > | 1 2 3 4 5 6 7 8 9 10 11 12 |
/* This CSS file holds the default implementations for all of fossil's
CSS classes. When /style.css is requested, the rules in this file
are emitted first, followed by (1) page-specific CSS (if any) and
(2) skin-specific CSS.
*/
div.sidebox {
float: right;
background-color: white;
border-width: medium;
border-style: double;
margin: 10px;
}
|
| ︙ | ︙ | |||
36 37 38 39 40 41 42 |
font-size: small;
}
tr.timelineCurrent {
padding: .1em .2em;
border: 1px dashed #446979;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.5);
}
| | > > > | 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
font-size: small;
}
tr.timelineCurrent {
padding: .1em .2em;
border: 1px dashed #446979;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.5);
}
.timelineSelected {
padding: .1em .2em;
border: 2px solid lightgray;
background-color: #ffc;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.5);
}
.timelineSecondary {
background-color: #cff;
}
tr.timelineSelected td {
border-radius: 0;
border-width: 0;
}
tr.timelineCurrent td {
border-radius: 0;
border-width: 0;
|
| ︙ | ︙ | |||
286 287 288 289 290 291 292 |
}
.filetree a {
position: relative;
z-index: 1;
display: table-cell;
min-height: 16px;
padding-left: 21px;
| > | > > | > > | > > | > | 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 |
}
.filetree a {
position: relative;
z-index: 1;
display: table-cell;
min-height: 16px;
padding-left: 21px;
background-image: url("data:image/gif;base64,R0lGODlhEAAQAJEAAP\/\/\/y\
EhIf\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfmgOUvoaqDSCxrEEfF14GqFX\
ImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==");
background-position: center left;
background-repeat: no-repeat;
}
ul.browser {
list-style-type: none;
padding: 10px;
margin: 0px;
white-space: nowrap;
}
ul.browser li.file {
background-image: url("data:image/gif;base64,R0lGODlhEAAQAJEAAP\/\/\/\
yEhIf\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfmgOUvoaqDSCxrEEfF14Gq\
FXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==");
background-repeat: no-repeat;
background-position: 0px center;
padding-left: 20px;
padding-top: 2px;
}
ul.browser li.dir {
background-image: url("data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIiI\
v\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXpo+\
jUs6b5Z/K4siDu5RPUFADs=");
background-repeat: no-repeat;
background-position: 0px center;
padding-left: 20px;
padding-top: 2px;
}
div.filetreeline {
display: table;
width: 100%;
white-space: nowrap;
}
.filetree .dir > div.filetreeline > a {
background-image: url("data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIiI\
v\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXpo\
+jUs6b5Z/K4siDu5RPUFADs=");
}
div.filetreeage {
display: table-cell;
padding-left: 3em;
text-align: right;
}
div.filetreeline:hover {
|
| ︙ | ︙ | |||
450 451 452 453 454 455 456 457 458 459 460 461 462 463 |
td.tktDspLabel {
text-align: right;
}
td.tktDspValue {
text-align: left;
vertical-align: top;
background-color: #d0d0d0;
}
span.tktError {
color: red;
font-weight: bold;
}
table.rpteditex {
float: right;
| > > > > > > | 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 |
td.tktDspLabel {
text-align: right;
}
td.tktDspValue {
text-align: left;
vertical-align: top;
background-color: #d0d0d0;
}
td.tktTlOpen {
color: #800;
}
td.tktTlClosed {
color: #888;
}
span.tktError {
color: red;
font-weight: bold;
}
table.rpteditex {
float: right;
|
| ︙ | ︙ | |||
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 |
border: 2px solid #ff0;
}
div.forumEdit {
border: 1px solid black;
padding-left: 1ex;
padding-right: 1ex;
}
div.forumHier, div.forumTime {
border: 1px solid black;
padding-left: 1ex;
padding-right: 1ex;
margin-top: 1ex;
}
div.forumSel {
background-color: #cef;
}
div.forumObs {
color: #bbb;
}
| > > > > > > > > > > > > > > > > > | 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 |
border: 2px solid #ff0;
}
div.forumEdit {
border: 1px solid black;
padding-left: 1ex;
padding-right: 1ex;
}
div.forumTimeline {
border: 1px solid black;
padding-left: 1ex;
padding-right: 1ex;
max-width: 50em;
overflow: auto;
}
div.forumTimeline code {
white-space: pre-wrap;
}
div.markdown code {
white-space: pre-wrap;
}
div.forumHier, div.forumTime {
border: 1px solid black;
padding-left: 1ex;
padding-right: 1ex;
margin-top: 1ex;
}
div.forumPostBody {
max-height: 40em;
overflow: auto;
}
div.forumSel {
background-color: #cef;
}
div.forumObs {
color: #bbb;
}
|
| ︙ | ︙ | |||
769 770 771 772 773 774 775 |
label {
white-space: nowrap;
}
.copy-button {
display: inline-block;
width: 14px;
height: 14px;
| | < | > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
label {
white-space: nowrap;
}
.copy-button {
display: inline-block;
width: 14px;
height: 14px;
/*Note: .24em is slightly smaller than the average width of a normal space.*/
margin: -2px .24em 0 0;
padding: 0;
border: 0;
vertical-align: middle;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' \
viewBox='0,0,14,14'%3E%3Cpath style='fill:black;opacity:0' \
d='M14,14H0V0h14v14z'/%3E%3Cpath style='fill:rgb(240,240,240)' \
d='M1,0h6.6l2,2h1l3.4,3.4v8.6h-10v-2h-3z'/%3E%3Cpath style='fill:rgb(64,64,64)' \
d='M2,1h5l3,3v7h-8z'/%3E%3Cpath style='fill:rgb(248,248,248)' \
d='M3,2h3.6l2.4,2.4v5.6h-6z'/%3E%3Cpath style='fill:rgb(80,128,208)' \
d='M4,5h4v1h-4zm0,2h4v1h-4z'/%3E%3Cpath style='fill:rgb(64,64,64)' \
d='M5,3h5l3,3v7h-8z'/%3E%3Cpath style='fill:rgb(248,248,248)' \
d='M10,4.4v1.6h1.6zm-4,-0.6h3v3h-3zm0,3h6v5.4h-6z'/%3E%3Cpath style='fill:rgb(80,128,208)' \
d='M7,8h4v1h-4zm0,2h4v1h-4z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
}
.copy-button-flipped {
/*Note: .16em is suitable for element grouping.*/
margin-left: .16em;
margin-right: 0;
}
.nobr {
white-space: nowrap;
}
.accordion {
cursor: pointer;
}
.accordion_btn {
display: inline-block;
width: 16px;
height: 16px;
margin-right: .5em;
vertical-align: middle;
}
/* Note: the order of the next 3 entries should be
maintained for the hierarchical cascade to work. */
.accordion > .accordion_btn_plus {
display: none;
}
.accordion_closed > .accordion_btn_minus {
display: none;
}
.accordion_closed > .accordion_btn_plus {
display: inline-block;
}
.accordion_panel {
overflow: hidden;
transition: max-height 0.25s ease-out;
}
.error {
color: darkred;
background: yellow;
}
.warning {
color: darkred;
background: yellow;
opacity: 0.7;
}
.hidden {
position: absolute;
opacity: 0;
pointer-events: none;
display: none;
}
input {
max-width: 95%;
}
textarea {
max-width: 95%;
}
img {
max-width: 100%;
height: auto;
}
|
| ︙ | ︙ | |||
157 158 159 160 161 162 163 |
TAG_CLOSED
);
}
}
/*
** Load the record ID rid and up to |N|-1 closest ancestors into
| | > | > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | < | > > > > | | 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 |
TAG_CLOSED
);
}
}
/*
** Load the record ID rid and up to |N|-1 closest ancestors into
** the "ok" table. If N is zero, no limit. If ridBackTo is not zero
** then stop the search upon reaching the ancestor with rid==ridBackTo.
*/
void compute_ancestors(int rid, int N, int directOnly, int ridBackTo){
if( !N ){
N = -1;
}else if( N<0 ){
N = -N;
}
if( directOnly ){
/* Direct mode means to show primary parents only */
db_multi_exec(
"WITH RECURSIVE "
" ancestor(rid, mtime) AS ("
" SELECT %d, mtime FROM event WHERE objid=%d "
" UNION "
" SELECT plink.pid, event.mtime"
" FROM ancestor, plink, event"
" WHERE plink.cid=ancestor.rid"
" AND event.objid=plink.pid"
" AND plink.isPrim"
" ORDER BY mtime DESC LIMIT %d"
" )"
"INSERT INTO ok"
" SELECT rid FROM ancestor;",
rid, rid, N
);
}else{
/* If not in directMode, also include merge parents, including
** cherrypick merges. Except, terminate searches at the cherrypick
** merge parent itself. In other words, include:
** (1) Primary parents
** (2) Merge parents
** (3) Cherrypick merge parents.
** (4) All ancestores of 1 and 2 but not of 3.
*/
double rLimitMtime = 0.0;
if( ridBackTo ){
rLimitMtime = db_double(0.0,
"SELECT mtime FROM event WHERE objid=%d",
ridBackTo);
}
db_multi_exec(
"WITH RECURSIVE "
" parent(pid,cid,isCP) AS ("
" SELECT plink.pid, plink.cid, 0 AS xisCP FROM plink"
" UNION ALL"
" SELECT parentid, childid, 1 FROM cherrypick WHERE NOT isExclude"
" ),"
" ancestor(rid, mtime, isCP) AS ("
" SELECT %d, mtime, 0 FROM event WHERE objid=%d "
" UNION "
" SELECT parent.pid, event.mtime, parent.isCP"
" FROM ancestor, parent, event"
" WHERE parent.cid=ancestor.rid"
" AND event.objid=parent.pid"
" AND NOT ancestor.isCP"
" AND (event.mtime>=%.17g OR parent.pid=%d)"
" ORDER BY mtime DESC LIMIT %d"
" )"
"INSERT OR IGNORE INTO ok"
" SELECT rid FROM ancestor;",
rid, rid, rLimitMtime, ridBackTo, N
);
if( ridBackTo && db_changes()>1 ){
db_multi_exec("INSERT OR IGNORE INTO ok VALUES(%d)", ridBackTo);
}
}
}
/*
** Compute the youngest ancestor of record ID rid that is a member of
** branch zBranch.
*/
int compute_youngest_ancestor_in_branch(int rid, const char *zBranch){
return db_int(0,
"WITH RECURSIVE "
" ancestor(rid, mtime) AS ("
" SELECT %d, mtime FROM event WHERE objid=%d "
" UNION "
" SELECT plink.pid, event.mtime"
" FROM ancestor, plink, event"
" WHERE plink.cid=ancestor.rid"
" AND event.objid=plink.pid"
" ORDER BY mtime DESC"
" )"
" SELECT ancestor.rid FROM ancestor"
" WHERE EXISTS(SELECT 1 FROM tagxref"
" WHERE tagid=%d AND tagxref.rid=ancestor.rid"
" AND value=%Q AND tagtype>0)"
" LIMIT 1",
rid, rid, TAG_BRANCH, zBranch
);
}
/*
** Compute all direct ancestors (merge ancestors do not count)
** for the check-in rid and put them in a table named "ancestor".
** Label each generation with consecutive integers going backwards
|
| ︙ | ︙ | |||
221 222 223 224 225 226 227 |
static int prevVid = -1;
static Stmt q;
if( prevVid!=vid ){
prevVid = vid;
db_multi_exec("CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
"DELETE FROM ok;");
| | | 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
static int prevVid = -1;
static Stmt q;
if( prevVid!=vid ){
prevVid = vid;
db_multi_exec("CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
"DELETE FROM ok;");
compute_ancestors(vid, 100000000, 1, 0);
}
db_static_prepare(&q,
"SELECT (max(event.mtime)-2440587.5)*86400 FROM mlink, event"
" WHERE mlink.mid=event.objid"
" AND +mlink.mid IN ok"
" AND mlink.fid=:fid");
db_bind_int(&q, ":fid", fid);
|
| ︙ | ︙ | |||
538 539 540 541 542 543 544 | blob_reset(&sql); /* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too ** many descenders to (off-screen) parents. */ tmFlags = TIMELINE_LEAFONLY | TIMELINE_DISJOINT | TIMELINE_NOSCROLL; if( fNg==0 ) tmFlags |= TIMELINE_GRAPH; if( fBrBg ) tmFlags |= TIMELINE_BRCOLOR; if( fUBg ) tmFlags |= TIMELINE_UCOLOR; | | | 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 | blob_reset(&sql); /* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too ** many descenders to (off-screen) parents. */ tmFlags = TIMELINE_LEAFONLY | TIMELINE_DISJOINT | TIMELINE_NOSCROLL; if( fNg==0 ) tmFlags |= TIMELINE_GRAPH; if( fBrBg ) tmFlags |= TIMELINE_BRCOLOR; if( fUBg ) tmFlags |= TIMELINE_UCOLOR; www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, 0); db_finalize(&q); @ <br /> style_footer(); } #if INTERFACE /* Flag parameters to compute_uses_file() */ |
| ︙ | ︙ |
1 2 3 4 5 6 | /* ** Copyright (c) 2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /* ** Copyright (c) 2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** ** 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. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ |
| ︙ | ︙ | |||
58 59 60 61 62 63 64 |
#define DIFF_TOO_MANY_CHANGES \
"more than 10,000 changes\n"
#define DIFF_WHITESPACE_ONLY \
"whitespace changes only\n"
/*
| | | | | | | | | 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 |
#define DIFF_TOO_MANY_CHANGES \
"more than 10,000 changes\n"
#define DIFF_WHITESPACE_ONLY \
"whitespace changes only\n"
/*
** Maximum length of a line in a text file, in bytes. (2**15 = 32768 bytes)
*/
#define LENGTH_MASK_SZ 15
#define LENGTH_MASK ((1<<LENGTH_MASK_SZ)-1)
#endif /* INTERFACE */
/*
** Information about each line of a file being diffed.
**
** The lower LENGTH_MASK_SZ bits of the hash (DLine.h) are the length
** of the line. If any line is longer than LENGTH_MASK characters,
** the file is considered binary.
*/
typedef struct DLine DLine;
struct DLine {
const char *z; /* The text of the line */
u64 h; /* Hash of the line */
unsigned short indent; /* Indent of the line. Only !=0 with -w/-Z option */
unsigned short n; /* number of bytes */
unsigned int iNext; /* 1+(Index of next line with same the same hash) */
/* an array of DLine elements serves two purposes. The fields
** above are one per line of input text. But each entry is also
** a bucket in a hash table, as follows: */
unsigned int iHash; /* 1+(first entry in the hash chain) */
};
/*
** Length of a dline
*/
#define LENGTH(X) ((X)->n)
|
| ︙ | ︙ | |||
113 114 115 116 117 118 119 | int *aEdit; /* Array of copy/delete/insert triples */ int nEdit; /* Number of integers (3x num of triples) in aEdit[] */ int nEditAlloc; /* Space allocated for aEdit[] */ DLine *aFrom; /* File on left side of the diff */ int nFrom; /* Number of lines in aFrom[] */ DLine *aTo; /* File on right side of the diff */ int nTo; /* Number of lines in aTo[] */ | | | 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | int *aEdit; /* Array of copy/delete/insert triples */ int nEdit; /* Number of integers (3x num of triples) in aEdit[] */ int nEditAlloc; /* Space allocated for aEdit[] */ DLine *aFrom; /* File on left side of the diff */ int nFrom; /* Number of lines in aFrom[] */ DLine *aTo; /* File on right side of the diff */ int nTo; /* Number of lines in aTo[] */ int (*xDiffer)(const DLine*,const DLine*); /* comparison function */ }; /* ** Count the number of lines in the input string. Include the last line ** in the count even if it lacks the \n terminator. If an empty string ** is specified, the number of lines is zero. For the purposes of this ** function, a string is considered empty if it contains no characters |
| ︙ | ︙ | |||
162 163 164 165 166 167 168 |
static DLine *break_into_lines(
const char *z,
int n,
int *pnLine,
u64 diffFlags
){
int nLine, i, k, nn, s, x;
| | | 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
static DLine *break_into_lines(
const char *z,
int n,
int *pnLine,
u64 diffFlags
){
int nLine, i, k, nn, s, x;
u64 h, h2;
DLine *a;
const char *zNL;
if( count_lines(z, n, &nLine)==0 ){
return 0;
}
assert( nLine>0 || z[0]=='\0' );
|
| ︙ | ︙ | |||
203 204 205 206 207 208 209 |
int numws = 0;
while( s<k && fossil_isspace(z[s]) ){ s++; }
for(h=0, x=s; x<k; x++){
char c = z[x];
if( fossil_isspace(c) ){
++numws;
}else{
| | < > > | | < > > > > | | > | | | | | | 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 |
int numws = 0;
while( s<k && fossil_isspace(z[s]) ){ s++; }
for(h=0, x=s; x<k; x++){
char c = z[x];
if( fossil_isspace(c) ){
++numws;
}else{
h = (h^c)*9000000000000000041LL;
}
}
k -= numws;
}else{
int k2 = k & ~0x7;
u64 m;
for(h=0, x=s; x<k2; x += 8){
memcpy(&m, z+x, 8);
h = (h^m)*9000000000000000041LL;
}
m = 0;
memcpy(&m, z+x, k-k2);
h ^= m;
}
a[i].indent = s;
a[i].h = h = ((h%281474976710597LL)<<LENGTH_MASK_SZ) | (k-s);
h2 = h % nLine;
a[i].iNext = a[h2].iHash;
a[h2].iHash = i+1;
z += nn+1; n -= nn+1;
i++;
}while( zNL[0]!='\0' && zNL[1]!='\0' );
assert( i==nLine );
/* Return results */
*pnLine = nLine;
return a;
}
/*
** Return zero if two DLine elements are identical.
*/
static int same_dline(const DLine *pA, const DLine *pB){
if( pA->h!=pB->h ) return 1;
return memcmp(pA->z,pB->z, pA->h&LENGTH_MASK);
}
/*
** Return zero if two DLine elements are identical, ignoring
** all whitespace. The indent field of pA/pB already points
** to the first non-space character in the string.
*/
static int same_dline_ignore_allws(const DLine *pA, const DLine *pB){
int a = pA->indent, b = pB->indent;
if( pA->h==pB->h ){
while( a<pA->n || b<pB->n ){
if( a<pA->n && b<pB->n && pA->z[a++] != pB->z[b++] ) return 1;
while( a<pA->n && fossil_isspace(pA->z[a])) ++a;
while( b<pB->n && fossil_isspace(pB->z[b])) ++b;
}
return pA->n-a != pB->n-b;
}
return 1;
}
/*
** Return true if the regular expression *pRe matches any of the
** N dlines
*/
static int re_dline_match(
|
| ︙ | ︙ | |||
1442 1443 1444 1445 1446 1447 1448 |
int i, j; /* Loop counters */
int k; /* Length of a candidate subsequence */
int iSXb = iS1; /* Best match so far */
int iSYb = iS2; /* Best match so far */
for(i=iS1; i<iE1-mxLength; i++){
for(j=iS2; j<iE2-mxLength; j++){
| | | | | 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 |
int i, j; /* Loop counters */
int k; /* Length of a candidate subsequence */
int iSXb = iS1; /* Best match so far */
int iSYb = iS2; /* Best match so far */
for(i=iS1; i<iE1-mxLength; i++){
for(j=iS2; j<iE2-mxLength; j++){
if( p->xDiffer(&p->aFrom[i], &p->aTo[j]) ) continue;
if( mxLength && p->xDiffer(&p->aFrom[i+mxLength], &p->aTo[j+mxLength]) ){
continue;
}
k = 1;
while( i+k<iE1 && j+k<iE2 && p->xDiffer(&p->aFrom[i+k],&p->aTo[j+k])==0 ){
k++;
}
if( k>mxLength ){
iSXb = i;
iSYb = j;
mxLength = k;
}
|
| ︙ | ︙ | |||
1513 1514 1515 1516 1517 1518 1519 |
iSYb = iSYp = iS2;
iEYb = iEYp = iS2;
mid = (iE1 + iS1)/2;
for(i=iS1; i<iE1; i++){
int limit = 0;
j = p->aTo[p->aFrom[i].h % p->nTo].iHash;
while( j>0
| | | | | 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 |
iSYb = iSYp = iS2;
iEYb = iEYp = iS2;
mid = (iE1 + iS1)/2;
for(i=iS1; i<iE1; i++){
int limit = 0;
j = p->aTo[p->aFrom[i].h % p->nTo].iHash;
while( j>0
&& (j-1<iS2 || j>=iE2 || p->xDiffer(&p->aFrom[i], &p->aTo[j-1]))
){
if( limit++ > 10 ){
j = 0;
break;
}
j = p->aTo[j-1].iNext;
}
if( j==0 ) continue;
assert( i>=iSXb && i>=iSXp );
if( i<iEXb && j>=iSYb && j<iEYb ) continue;
if( i<iEXp && j>=iSYp && j<iEYp ) continue;
iSX = i;
iSY = j-1;
pA = &p->aFrom[iSX-1];
pB = &p->aTo[iSY-1];
n = minInt(iSX-iS1, iSY-iS2);
for(k=0; k<n && p->xDiffer(pA,pB)==0; k++, pA--, pB--){}
iSX -= k;
iSY -= k;
iEX = i+1;
iEY = j;
pA = &p->aFrom[iEX];
pB = &p->aTo[iEY];
n = minInt(iE1-iEX, iE2-iEY);
for(k=0; k<n && p->xDiffer(pA,pB)==0; k++, pA++, pB++){}
iEX += k;
iEY += k;
skew = (iSX-iS1) - (iSY-iS2);
if( skew<0 ) skew = -skew;
dist = (iSX+iEX)/2 - mid;
if( dist<0 ) dist = -dist;
score = (iEX - iSX)*(sqlite3_int64)span - (skew + dist);
|
| ︙ | ︙ | |||
1679 1680 1681 1682 1683 1684 1685 |
*/
static void diff_all(DContext *p){
int mnE, iS, iE1, iE2;
/* Carve off the common header and footer */
iE1 = p->nFrom;
iE2 = p->nTo;
| | | | 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 |
*/
static void diff_all(DContext *p){
int mnE, iS, iE1, iE2;
/* Carve off the common header and footer */
iE1 = p->nFrom;
iE2 = p->nTo;
while( iE1>0 && iE2>0 && p->xDiffer(&p->aFrom[iE1-1], &p->aTo[iE2-1])==0 ){
iE1--;
iE2--;
}
mnE = iE1<iE2 ? iE1 : iE2;
for(iS=0; iS<mnE && p->xDiffer(&p->aFrom[iS],&p->aTo[iS])==0; iS++){}
/* do the difference */
if( iS>0 ){
appendTriple(p, iS, 0, 0);
}
diff_step(p, iS, iE1, iS, iE2);
if( iE1<p->nFrom ){
|
| ︙ | ︙ | |||
1753 1754 1755 1756 1757 1758 1759 |
lnFrom += cpy;
lnTo += cpy;
/* Shift insertions toward the beginning of the file */
while( cpy>0 && del==0 && ins>0 ){
DLine *pTop = &p->aFrom[lnFrom-1]; /* Line before start of insert */
DLine *pBtm = &p->aTo[lnTo+ins-1]; /* Last line inserted */
| | | | | | 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 |
lnFrom += cpy;
lnTo += cpy;
/* Shift insertions toward the beginning of the file */
while( cpy>0 && del==0 && ins>0 ){
DLine *pTop = &p->aFrom[lnFrom-1]; /* Line before start of insert */
DLine *pBtm = &p->aTo[lnTo+ins-1]; /* Last line inserted */
if( p->xDiffer(pTop, pBtm) ) break;
if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break;
lnFrom--;
lnTo--;
p->aEdit[r]--;
p->aEdit[r+3]++;
cpy--;
}
/* Shift insertions toward the end of the file */
while( r+3<p->nEdit && p->aEdit[r+3]>0 && del==0 && ins>0 ){
DLine *pTop = &p->aTo[lnTo]; /* First line inserted */
DLine *pBtm = &p->aTo[lnTo+ins]; /* First line past end of insert */
if( p->xDiffer(pTop, pBtm) ) break;
if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop+1)+LENGTH(pBtm) ) break;
lnFrom++;
lnTo++;
p->aEdit[r]++;
p->aEdit[r+3]--;
cpy++;
}
/* Shift deletions toward the beginning of the file */
while( cpy>0 && del>0 && ins==0 ){
DLine *pTop = &p->aFrom[lnFrom-1]; /* Line before start of delete */
DLine *pBtm = &p->aFrom[lnFrom+del-1]; /* Last line deleted */
if( p->xDiffer(pTop, pBtm) ) break;
if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break;
lnFrom--;
lnTo--;
p->aEdit[r]--;
p->aEdit[r+3]++;
cpy--;
}
/* Shift deletions toward the end of the file */
while( r+3<p->nEdit && p->aEdit[r+3]>0 && del>0 && ins==0 ){
DLine *pTop = &p->aFrom[lnFrom]; /* First line deleted */
DLine *pBtm = &p->aFrom[lnFrom+del]; /* First line past end of delete */
if( p->xDiffer(pTop, pBtm) ) break;
if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop)+LENGTH(pBtm) ) break;
lnFrom++;
lnTo++;
p->aEdit[r]++;
p->aEdit[r+3]--;
cpy++;
}
|
| ︙ | ︙ | |||
1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 |
if( n==0 && (diffFlags & DIFF_CONTEXT_EX)==0 ) n = 5;
return n;
}
/*
** Extract the width of columns for side-by-side diff. Supply an
** appropriate default if no width is given.
*/
int diff_width(u64 diffFlags){
int w = (diffFlags & DIFF_WIDTH_MASK)/(DIFF_CONTEXT_MASK+1);
| > > > > > > | > > > > > > > > > > > > > > > > > > > | 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 |
if( n==0 && (diffFlags & DIFF_CONTEXT_EX)==0 ) n = 5;
return n;
}
/*
** Extract the width of columns for side-by-side diff. Supply an
** appropriate default if no width is given.
**
** Calculate the default automatically, based on terminal's current width:
** term-width = 2*diff-col + diff-marker + 1
** diff-col = lineno + lmargin + text-width + rmargin
**
** text-width = (term-width - diff-marker - 1)/2 - lineno - lmargin - rmargin
*/
int diff_width(u64 diffFlags){
int w = (diffFlags & DIFF_WIDTH_MASK)/(DIFF_CONTEXT_MASK+1);
if( w==0 ){
static struct {
unsigned int lineno, lmargin, text, rmargin, marker;
} sbsW = { 5, 2, 0, 0, 3 };
const unsigned int wMin = 24, wMax = 132;
unsigned int tw = terminal_get_width(80);
unsigned int twMin =
(wMin + sbsW.lineno + sbsW.lmargin + sbsW.rmargin)*2 + sbsW.marker + 1;
unsigned int twMax =
(wMax + sbsW.lineno + sbsW.lmargin + sbsW.rmargin)*2 + sbsW.marker + 1;
if( tw<twMin ){
tw = twMin;
}else if( tw>twMax ){
tw = twMax;
}
sbsW.text =
(tw - sbsW.marker - 1)/2 - sbsW.lineno - sbsW.lmargin - sbsW.rmargin;
w = sbsW.text;
}
return w;
}
/*
** Append the error message to pOut.
*/
void diff_errmsg(Blob *pOut, const char *msg, int diffFlags){
|
| ︙ | ︙ | |||
1873 1874 1875 1876 1877 1878 1879 |
ignoreWs = (diffFlags & DIFF_IGNORE_ALLWS)!=0;
blob_to_utf8_no_bom(pA_Blob, 0);
blob_to_utf8_no_bom(pB_Blob, 0);
/* Prepare the input files */
memset(&c, 0, sizeof(c));
if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
| | | | 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 |
ignoreWs = (diffFlags & DIFF_IGNORE_ALLWS)!=0;
blob_to_utf8_no_bom(pA_Blob, 0);
blob_to_utf8_no_bom(pB_Blob, 0);
/* Prepare the input files */
memset(&c, 0, sizeof(c));
if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
c.xDiffer = same_dline_ignore_allws;
}else{
c.xDiffer = same_dline;
}
c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
&c.nFrom, diffFlags);
c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
&c.nTo, diffFlags);
if( c.aFrom==0 || c.aTo==0 ){
fossil_free(c.aFrom);
|
| ︙ | ︙ | |||
2105 2106 2107 2108 2109 2110 2111 |
** will release it when it is finished with it.
*/
static int annotation_start(Annotator *p, Blob *pInput, u64 diffFlags){
int i;
memset(p, 0, sizeof(*p));
if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
| | | | 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 |
** will release it when it is finished with it.
*/
static int annotation_start(Annotator *p, Blob *pInput, u64 diffFlags){
int i;
memset(p, 0, sizeof(*p));
if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
p->c.xDiffer = same_dline_ignore_allws;
}else{
p->c.xDiffer = same_dline;
}
p->c.aTo = break_into_lines(blob_str(pInput), blob_size(pInput),&p->c.nTo,
diffFlags);
if( p->c.aTo==0 ){
return 1;
}
p->aOrig = fossil_malloc( sizeof(p->aOrig[0])*p->c.nTo );
|
| ︙ | ︙ | |||
2367 2368 2369 2370 2371 2372 2373 | ** or removed by any subsequent check-in. ** ** Query parameters: ** ** checkin=ID The check-in at which to start the annotation ** filename=FILENAME The filename. ** filevers=BOOLEAN Show file versions rather than check-in versions | | | | | | | | | 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 |
** or removed by any subsequent check-in.
**
** Query parameters:
**
** checkin=ID The check-in at which to start the annotation
** filename=FILENAME The filename.
** filevers=BOOLEAN Show file versions rather than check-in versions
** limit=LIMIT Limit the amount of analysis. LIMIT can be one of:
** none No limit
** Xs As much as can be computed in X seconds
** N N versions
** log=BOOLEAN Show a log of versions analyzed
** origin=ID The origin checkin. If unspecified, the root
** check-in over the entire repository is used.
** Specify "origin=trunk" or similar for a reverse
** annotation
** w=BOOLEAN Ignore whitespace
*/
void annotation_page(void){
int i;
const char *zLimit; /* Depth limit */
u64 annFlags = DIFF_STRIP_EOLCR;
int showLog; /* True to display the log */
|
| ︙ | ︙ | |||
2554 2555 2556 2557 2558 2559 2560 | ** removed by any subsequent check-in. ** ** Options: ** --filevers Show file version numbers rather than ** check-in versions ** -r|--revision VERSION The specific check-in containing the file ** -l|--log List all versions analyzed | | | | | 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 |
** removed by any subsequent check-in.
**
** Options:
** --filevers Show file version numbers rather than
** check-in versions
** -r|--revision VERSION The specific check-in containing the file
** -l|--log List all versions analyzed
** -n|--limit LIMIT LIMIT can be one of:
** N Up to N versions
** Xs As much as possible in X seconds
** none No limit
** -o|--origin VERSION The origin check-in. By default this is the
** root of the repository. Set to "trunk" or
** similar for a reverse annotation.
** -w|--ignore-all-space Ignore white space when comparing lines
** -Z|--ignore-trailing-space Ignore whitespace at line end
**
** See also: info, finfo, timeline
*/
void annotate_cmd(void){
const char *zRevision; /* Revision name, or NULL for current check-in */
|
| ︙ | ︙ |
| ︙ | ︙ | |||
705 706 707 708 709 710 711 |
g.nameOfExe, zSubCmd);
find_option("html",0,0);
find_option("side-by-side","y",0);
find_option("internal","i",0);
find_option("verbose","v",0);
zTclsh = find_option("tclsh",0,1);
if( zTclsh==0 ){
| | | 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 |
g.nameOfExe, zSubCmd);
find_option("html",0,0);
find_option("side-by-side","y",0);
find_option("internal","i",0);
find_option("verbose","v",0);
zTclsh = find_option("tclsh",0,1);
if( zTclsh==0 ){
zTclsh = db_get("tclsh",0);
}
/* The undocumented --script FILENAME option causes the Tk script to
** be written into the FILENAME instead of being run. This is used
** for testing and debugging. */
zTempFile = find_option("script",0,1);
for(i=firstArg; i<g.argc; i++){
const char *z = g.argv[i];
|
| ︙ | ︙ | |||
741 742 743 744 745 746 747 |
* If evaluation of the Tcl script fails, the reason may be that Tk
* could not be found by the loaded Tcl, or that Tcl cannot be loaded
* dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback
* to using the external "tclsh", if available.
*/
#endif
zTempFile = write_blob_to_temp_file(&script);
| | | 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 |
* If evaluation of the Tcl script fails, the reason may be that Tk
* could not be found by the loaded Tcl, or that Tcl cannot be loaded
* dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback
* to using the external "tclsh", if available.
*/
#endif
zTempFile = write_blob_to_temp_file(&script);
zCmd = mprintf("%$ %$", zTclsh, zTempFile);
fossil_system(zCmd);
file_delete(zTempFile);
fossil_free(zCmd);
}
blob_reset(&script);
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
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 |
zName = "";
}else{
if( *z ){ *z++ = 0; }
zValue = "";
}
if( fossil_islower(zName[0]) ){
cgi_replace_query_parameter(zName, zValue);
}
}
return 0;
}
/*
** Fill Blob with a space-separated list of all command names that
** match the prefix zPrefix.
*/
void dispatch_matching_names(const char *zPrefix, Blob *pList){
int i;
int nPrefix = (int)strlen(zPrefix);
for(i=FOSSIL_FIRST_CMD; i<MX_COMMAND; i++){
if( strncmp(zPrefix, aCommand[i].zName, nPrefix)==0 ){
blob_appendf(pList, " %s", aCommand[i].zName);
}
}
}
/*
** Attempt to reformat plain-text help into HTML for display on a webpage.
**
** The HTML output is appended to Blob pHtml, which should already be
** initialized.
*/
static void help_to_html(const char *zHelp, Blob *pHtml){
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > | > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > | > | | < > > | > > > > | < > > | > > > > > > | > | > | > | > > | > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
zName = "";
}else{
if( *z ){ *z++ = 0; }
zValue = "";
}
if( fossil_islower(zName[0]) ){
cgi_replace_query_parameter(zName, zValue);
}else if( fossil_isupper(zName[0]) ){
cgi_replace_query_parameter_tolower(zName, zValue);
}
}
return 0;
}
/*
** Fill Blob with a space-separated list of all command names that
** match the prefix zPrefix.
*/
void dispatch_matching_names(const char *zPrefix, Blob *pList){
int i;
int nPrefix = (int)strlen(zPrefix);
for(i=FOSSIL_FIRST_CMD; i<MX_COMMAND; i++){
if( strncmp(zPrefix, aCommand[i].zName, nPrefix)==0 ){
blob_appendf(pList, " %s", aCommand[i].zName);
}
}
}
/*
** Return the index of the first non-space character that follows
** a span of two or more spaces. Return 0 if there is not gap.
*/
static int hasGap(const char *z, int n){
int i;
for(i=3; i<n-1; i++){
if( z[i]==' ' && z[i+1]!=' ' && z[i-1]==' ' && z[i-2]!='.' ) return i+1;
}
return 0 ;
}
/*
** Append text to pOut, adding formatting markup. Terms that
** have all lower-case letters are within <tt>..</tt>. Terms
** that have all upper-case letters are within <i>..</i>.
*/
static void appendMixedFont(Blob *pOut, const char *z, int n){
const char *zEnd = "";
int i = 0;
int j;
while( i<n ){
if( z[i]==' ' || z[j]=='=' ){
for(j=i+1; j<n && (z[j]==' ' || z[j]=='='); j++){}
blob_append(pOut, z+i, j-i);
i = j;
}else{
for(j=i; j<n && z[j]!=' ' && z[j]!='=' && !fossil_isalpha(z[j]); j++){}
if( j>=n || z[j]==' ' || z[j]=='=' ){
zEnd = "";
}else{
if( fossil_isupper(z[j]) ){
blob_append(pOut, "<i>",3);
zEnd = "</i>";
}else{
blob_append(pOut, "<tt>", 4);
zEnd = "</tt>";
}
}
while( j<n && z[j]!=' ' && z[j]!='=' ){ j++; }
blob_appendf(pOut, "%#h", j-i, z+i);
if( zEnd[0] ) blob_append(pOut, zEnd, -1);
i = j;
}
}
}
/*
** Attempt to reformat plain-text help into HTML for display on a webpage.
**
** The HTML output is appended to Blob pHtml, which should already be
** initialized.
**
** Formatting rules:
**
** * Bullet lists are indented from the surrounding text by
** at least one space. Each bullet begins with " * ".
**
** * Display lists are indented from the surrounding text.
** Each tag begins with "-" or occur on a line that is
** followed by two spaces and a non-space. <dd> elements can begin
** on the same line as long as they are separated by at least
** two spaces.
**
** * Indented text is show verbatim (<pre>...</pre>)
*/
static void help_to_html(const char *zHelp, Blob *pHtml){
int i;
char c;
int nIndent = 0;
int wantP = 0;
int wantBR = 0;
int aIndent[10];
const char *azEnd[10];
int iLevel = 0;
int isLI = 0;
int isDT = 0;
static const char *zEndDL = "</dl></blockquote>";
static const char *zEndPRE = "</pre></blockquote>";
static const char *zEndUL = "</ul>";
static const char *zEndDD = "</dd>";
aIndent[0] = 0;
azEnd[0] = "";
while( zHelp[0] ){
i = 0;
while( (c = zHelp[i])!=0
&& c!='\n'
&& (c!='%' || strncmp(zHelp+i,"%fossil",7)!=0)
){ i++; }
if( c=='%' ){
if( i ) blob_appendf(pHtml, "%#h", i, zHelp);
zHelp += i + 1;
i = 0;
wantBR = 1;
continue;
}
if( i>2 && zHelp[0]=='>' && zHelp[1]==' ' ){
isDT = 1;
for(nIndent=1; nIndent<i && zHelp[nIndent]==' '; nIndent++){}
}else{
isDT = 0;
for(nIndent=0; nIndent<i && zHelp[nIndent]==' '; nIndent++){}
}
if( nIndent==i ){
if( c==0 ) break;
blob_append(pHtml, "\n", 1);
wantP = 1;
wantBR = 0;
zHelp += i+1;
continue;
}
if( nIndent+2<i && zHelp[nIndent]=='*' && zHelp[nIndent+1]==' ' ){
nIndent += 2;
while( nIndent<i && zHelp[nIndent]==' '){ nIndent++; }
isLI = 1;
}else{
isLI = 0;
}
while( iLevel>0 && aIndent[iLevel]>nIndent ){
blob_append(pHtml, azEnd[iLevel--], -1);
}
if( nIndent>aIndent[iLevel] ){
assert( iLevel<ArraySize(aIndent)-2 );
if( isLI ){
iLevel++;
aIndent[iLevel] = nIndent;
azEnd[iLevel] = zEndUL;
blob_append(pHtml, "<ul>\n", 5);
}else if( isDT
|| zHelp[nIndent]=='-'
|| hasGap(zHelp+nIndent,i-nIndent) ){
iLevel++;
aIndent[iLevel] = nIndent;
azEnd[iLevel] = zEndDL;
blob_append(pHtml, "<blockquote><dl>\n", -1);
}else if( azEnd[iLevel]==zEndDL ){
iLevel++;
aIndent[iLevel] = nIndent;
azEnd[iLevel] = zEndDD;
blob_append(pHtml, "<dd>", 4);
}else if( wantP ){
iLevel++;
aIndent[iLevel] = nIndent;
azEnd[iLevel] = zEndPRE;
blob_append(pHtml, "<blockquote><pre>", -1);
wantP = 0;
}
}
if( isLI ){
blob_append(pHtml, "<li> ", 5);
}
if( wantP ){
blob_append(pHtml, "<p> ", 4);
wantP = 0;
}
if( azEnd[iLevel]==zEndDL ){
int iDD;
blob_append(pHtml, "<dt> ", 5);
iDD = hasGap(zHelp+nIndent, i-nIndent);
if( iDD ){
int x;
assert( iLevel<ArraySize(aIndent)-1 );
iLevel++;
aIndent[iLevel] = x = nIndent+iDD;
azEnd[iLevel] = zEndDD;
appendMixedFont(pHtml, zHelp+nIndent, iDD-2);
blob_appendf(pHtml, "</dt><dd>%#h\n", i-x, zHelp+x);
}else{
appendMixedFont(pHtml, zHelp+nIndent, i-nIndent);
blob_append(pHtml, "</dt>\n", 6);
}
}else if( wantBR ){
appendMixedFont(pHtml, zHelp+nIndent, i-nIndent);
blob_append(pHtml, "<br>\n", 5);
wantBR = 0;
}else{
blob_appendf(pHtml, "%#h\n", i-nIndent, zHelp+nIndent);
}
zHelp += i+1;
i = 0;
if( c==0 ) break;
}
while( iLevel>0 ){
blob_appendf(pHtml, "%s\n", azEnd[iLevel--]);
}
}
/*
** Format help text for TTY display.
*/
static void help_to_text(const char *zHelp, Blob *pText){
int i;
char c;
for(i=0; (c = zHelp[i])!=0; i++){
if( c=='%' && strncmp(zHelp+i,"%fossil",7)==0 ){
if( i>0 ) blob_append(pText, zHelp, i);
blob_append(pText, "fossil", 6);
zHelp += i+7;
i = -1;
continue;
}
if( c=='\n' && strncmp(zHelp+i+1,"> ",2)==0 ){
blob_append(pText, zHelp, i+1);
blob_append(pText, " ", 1);
zHelp += i+2;
i = -1;
continue;
}
}
if( i>0 ){
blob_append(pText, zHelp, i);
}
}
/*
** COMMAND: test-all-help
**
** Usage: %fossil test-all-help ?OPTIONS?
**
|
| ︙ | ︙ | |||
288 289 290 291 292 293 294 |
fossil_print("-->\n");
fossil_print("<!-- start_all_help -->\n");
}else{
fossil_print("---\n");
}
for(i=0; i<MX_COMMAND; i++){
if( (aCommand[i].eCmdFlags & mask)==0 ) continue;
| < | > | > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
fossil_print("-->\n");
fossil_print("<!-- start_all_help -->\n");
}else{
fossil_print("---\n");
}
for(i=0; i<MX_COMMAND; i++){
if( (aCommand[i].eCmdFlags & mask)==0 ) continue;
if( useHtml ){
Blob html;
blob_init(&html, 0, 0);
help_to_html(aCommand[i].zHelp, &html);
fossil_print("<h1>%h</h1>\n", aCommand[i].zName);
fossil_print("%s\n<hr>\n", blob_str(&html));
blob_reset(&html);
}else{
Blob txt;
blob_init(&txt, 0, 0);
help_to_text(aCommand[i].zHelp, &txt);
fossil_print("# %s\n", aCommand[i].zName);
fossil_print("%s\n\n", blob_str(&txt));
blob_reset(&txt);
}
}
if( useHtml ){
fossil_print("<!-- end_all_help -->\n");
}else{
fossil_print("---\n");
}
version_cmd();
}
/*
** Count the number of entries in the aCommand[] table that match
** the given flag.
*/
static int countCmds(unsigned int eFlg){
int n = 0;
int i;
for(i=0; i<MX_COMMAND; i++){
if( (aCommand[i].eCmdFlags & eFlg)!=0 ) n++;
}
return n;
}
/*
** COMMAND: test-command-stats
**
** Print statistics about the built-in command dispatch table.
*/
void test_command_stats_cmd(void){
fossil_print("commands: %4d\n",
countCmds( CMDFLAG_COMMAND ));
fossil_print(" 1st tier %4d\n",
countCmds( CMDFLAG_1ST_TIER ));
fossil_print(" 2nd tier %4d\n",
countCmds( CMDFLAG_2ND_TIER ));
fossil_print(" test %4d\n",
countCmds( CMDFLAG_TEST ));
fossil_print("web-pages: %4d\n",
countCmds( CMDFLAG_WEBPAGE ));
fossil_print("settings: %4d\n",
countCmds( CMDFLAG_SETTING ));
fossil_print("total entries: %4d\n", MX_COMMAND);
}
/*
** Compute an estimate of the edit-distance between to input strings.
**
** The first string is the input. The second is the pattern. Only the
** first 100 characters of the pattern are considered.
*/
static int edit_distance(const char *zA, const char *zB){
int nA = (int)strlen(zA);
int nB = (int)strlen(zB);
int i, j, m;
int p0, p1, c0;
int a[100];
static const int incr = 4;
for(j=0; j<nB; j++) a[j] = 1;
for(i=0; i<nA; i++){
p0 = i==0 ? 0 : i*incr-1;
c0 = i*incr;
for(j=0; j<nB; j++){
int m = 999;
p1 = a[j];
if( zA[i]==zB[j] ){
m = p0;
}else{
m = c0+2;
if( m>p1+2 ) m = p1+2;
if( m>p0+3 ) m = p0+3;
}
c0 = a[j];
a[j] = m;
p0 = p1;
}
}
m = a[nB-1];
for(j=0; j<nB-1; j++){
if( a[j]+1<m ) m = a[j]+1;
}
return m;
}
/*
** Fill the pointer array with names of commands that approximately
** match the input. Return the number of approximate matches.
**
** Closest matches appear first.
*/
int dispatch_approx_match(const char *zIn, int nArray, const char **azArray){
int i;
int bestScore;
int m;
int n = 0;
int mnScore = 0;
int mxScore = 99999;
int iFirst, iLast;
if( zIn[0]=='/' ){
iFirst = 0;
iLast = FOSSIL_FIRST_CMD-1;
}else{
iFirst = FOSSIL_FIRST_CMD;
iLast = MX_COMMAND-1;
}
while( n<nArray ){
bestScore = mxScore;
for(i=iFirst; i<=iLast; i++){
m = edit_distance(zIn, aCommand[i].zName);
if( m<mnScore ) continue;
if( m==mnScore ){
azArray[n++] = aCommand[i].zName;
if( n>=nArray ) return n;
}else if( m<bestScore ){
bestScore = m;
}
}
if( bestScore>=mxScore ) break;
mnScore = bestScore;
}
return n;
}
/*
** COMMAND: test-approx-match
**
** Test the approximate match algorithm
*/
void test_approx_match_command(void){
int i, j, n;
const char *az[20];
for(i=2; i<g.argc; i++){
fossil_print("%s:\n", g.argv[i]);
n = dispatch_approx_match(g.argv[i], 20, az);
for(j=0; j<n; j++){
fossil_print(" %s\n", az[j]);
}
}
}
/*
** WEBPAGE: help
** URL: /help?name=CMD
**
** Show the built-in help text for CMD. CMD can be a command-line interface
** command or a page name from the web interface or a setting.
|
| ︙ | ︙ | |||
343 344 345 346 347 348 349 |
@ unknown command: %h(zCmd)
}else if( rc==2 ){
@ ambiguous command prefix: %h(zCmd)
}else{
if( pCmd->zHelp[0]==0 ){
@ No help available for "%h(pCmd->zName)"
}else{
| | | | 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 |
@ unknown command: %h(zCmd)
}else if( rc==2 ){
@ ambiguous command prefix: %h(zCmd)
}else{
if( pCmd->zHelp[0]==0 ){
@ No help available for "%h(pCmd->zName)"
}else{
@ <div class="helpPage">
help_to_html(pCmd->zHelp, cgi_output_blob());
@ </div>
}
}
}else{
int i;
style_header("Help");
|
| ︙ | ︙ | |||
423 424 425 426 427 428 429 430 431 432 |
/*
** WEBPAGE: test-all-help
**
** Show all help text on a single page. Useful for proof-reading.
*/
void test_all_help_page(void){
int i;
style_header("All Help Text");
for(i=0; i<MX_COMMAND; i++){
if( memcmp(aCommand[i].zName, "test", 4)==0 ) continue;
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | > > | 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 |
/*
** WEBPAGE: test-all-help
**
** Show all help text on a single page. Useful for proof-reading.
*/
void test_all_help_page(void){
int i;
Blob buf;
blob_init(&buf,0,0);
style_header("All Help Text");
@ <dl>
for(i=0; i<MX_COMMAND; i++){
const char *zDesc;
unsigned int e = aCommand[i].eCmdFlags;
if( e & CMDFLAG_1ST_TIER ){
zDesc = "1st tier command";
}else if( e & CMDFLAG_2ND_TIER ){
zDesc = "2nd tier command";
}else if( e & CMDFLAG_TEST ){
zDesc = "test command";
}else if( e & CMDFLAG_WEBPAGE ){
if( e & CMDFLAG_RAWCONTENT ){
zDesc = "raw-content web page";
}else{
zDesc = "web page";
}
}else{
blob_reset(&buf);
if( e & CMDFLAG_VERSIONABLE ){
blob_appendf(&buf, "versionable ");
}
if( e & CMDFLAG_BLOCKTEXT ){
blob_appendf(&buf, "block-text ");
}
if( e & CMDFLAG_BOOLEAN ){
blob_appendf(&buf, "boolean ");
}
blob_appendf(&buf,"setting");
zDesc = blob_str(&buf);
}
if( memcmp(aCommand[i].zName, "test", 4)==0 ) continue;
@ <dt><big><b>%s(aCommand[i].zName)</b></big> (%s(zDesc))</dt>
@ <dd>
help_to_html(aCommand[i].zHelp, cgi_output_blob());
@ </dd>
}
@ </dl>
blob_reset(&buf);
style_footer();
}
static void multi_column_list(const char **azWord, int nWord){
int i, j, len;
int mxLen = 0;
int nCol;
|
| ︙ | ︙ | |||
520 521 522 523 524 525 526 | @ --utc Display times using UTC @ --vfs NAME Cause SQLite to use the NAME VFS ; /* ** COMMAND: help ** | | < | | > | | | | | | | > > > > < > > | 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 |
@ --utc Display times using UTC
@ --vfs NAME Cause SQLite to use the NAME VFS
;
/*
** COMMAND: help
**
** Usage: %fossil help [OPTIONS] [TOPIC]
**
** Display information on how to use TOPIC, which may be a command, webpage, or
** setting. Webpage names begin with "/". If TOPIC is omitted, a list of
** topics is returned.
**
** The following options can be used when TOPIC is omitted:
**
** -a|--all List both command and auxiliary commands
** -o|--options List command-line options common to all commands
** -s|--setting List setting names
** -t|--test List unsupported "test" commands
** -x|--aux List only auxiliary commands
** -w|--www List all web pages
**
** These options can be used when TOPIC is present:
**
** -h|--html Format output as HTML rather than plain text
*/
void help_cmd(void){
int rc;
int isPage = 0;
const char *z;
const char *zCmdOrPage;
const CmdOrPage *pCmd = 0;
int useHtml = 0;
Blob txt;
if( g.argc<3 ){
z = g.argv[0];
fossil_print(
"Usage: %s help TOPIC\n"
"Common commands: (use \"%s help help\" for more options)\n",
z, z);
command_list(0, CMDFLAG_1ST_TIER);
|
| ︙ | ︙ | |||
576 577 578 579 580 581 582 583 584 585 |
command_list(0, CMDFLAG_TEST);
return;
}
else if( find_option("setting","s",0) ){
command_list(0, CMDFLAG_SETTING);
return;
}
isPage = ('/' == *g.argv[2]) ? 1 : 0;
if(isPage){
zCmdOrPage = "page";
| > < < > > > | | > > > > > > > > > > < < < < < | | | < | < < > | < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 |
command_list(0, CMDFLAG_TEST);
return;
}
else if( find_option("setting","s",0) ){
command_list(0, CMDFLAG_SETTING);
return;
}
useHtml = find_option("html","h",0)!=0;
isPage = ('/' == *g.argv[2]) ? 1 : 0;
if(isPage){
zCmdOrPage = "page";
}else{
zCmdOrPage = "command or setting";
}
rc = dispatch_name_search(g.argv[2], CMDFLAG_ANY|CMDFLAG_PREFIX, &pCmd);
if( rc ){
int i, n;
const char *az[5];
if( rc==1 ){
fossil_print("unknown %s: %s\n", zCmdOrPage, g.argv[2]);
}else{
fossil_print("ambiguous %s prefix: %s\n",
zCmdOrPage, g.argv[2]);
}
fossil_print("Did you mean one of:\n");
n = dispatch_approx_match(g.argv[2], 5, az);
for(i=0; i<n; i++){
fossil_print(" * %s\n", az[i]);
}
fossil_print("Also consider using:\n");
fossil_print(" fossil help -a ;# show all commands\n");
fossil_print(" fossil help -w ;# show all web-pages\n");
fossil_print(" fossil help -s ;# show all settings\n");
fossil_exit(1);
}
z = pCmd->zHelp;
if( z==0 ){
fossil_fatal("no help available for the %s %s",
pCmd->zName, zCmdOrPage);
}
if( pCmd->eCmdFlags & CMDFLAG_SETTING ){
fossil_print("Setting: \"%s\"%s\n\n",
pCmd->zName,
(pCmd->eCmdFlags & CMDFLAG_VERSIONABLE)!=0 ? " (versionable)" : ""
);
}
blob_init(&txt, 0, 0);
if( useHtml ){
help_to_html(z, &txt);
}else{
help_to_text(z, &txt);
}
fossil_print("%s\n", blob_str(&txt));
blob_reset(&txt);
}
/*
** Return a pointer to the setting information array.
**
** This routine provides access to the aSetting2[] array which is created
** by the mkindex utility program and included with <page_index.h>.
*/
const Setting *setting_info(int *pnCount){
if( pnCount ) *pnCount = (int)(sizeof(aSetting)/sizeof(aSetting[0])) - 1;
return aSetting;
}
/*****************************************************************************
** A virtual table for accessing the information in aCommand[], and
** especially the help-text
*/
/* helptextVtab_vtab is a subclass of sqlite3_vtab which is
** underlying representation of the virtual table
*/
typedef struct helptextVtab_vtab helptextVtab_vtab;
struct helptextVtab_vtab {
sqlite3_vtab base; /* Base class - must be first */
/* Add new fields here, as necessary */
};
/* helptextVtab_cursor is a subclass of sqlite3_vtab_cursor which will
** serve as the underlying representation of a cursor that scans
** over rows of the result
*/
typedef struct helptextVtab_cursor helptextVtab_cursor;
struct helptextVtab_cursor {
sqlite3_vtab_cursor base; /* Base class - must be first */
/* Insert new fields here. For this helptextVtab we only keep track
** of the rowid */
sqlite3_int64 iRowid; /* The rowid */
};
/*
** The helptextVtabConnect() method is invoked to create a new
** helptext virtual table.
**
** Think of this routine as the constructor for helptextVtab_vtab objects.
**
** All this routine needs to do is:
**
** (1) Allocate the helptextVtab_vtab object and initialize all fields.
**
** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the
** result set of queries against the virtual table will look like.
*/
static int helptextVtabConnect(
sqlite3 *db,
void *pAux,
int argc, const char *const*argv,
sqlite3_vtab **ppVtab,
char **pzErr
){
helptextVtab_vtab *pNew;
int rc;
rc = sqlite3_declare_vtab(db,
"CREATE TABLE x(name,type,flags,helptext)"
);
if( rc==SQLITE_OK ){
pNew = sqlite3_malloc( sizeof(*pNew) );
*ppVtab = (sqlite3_vtab*)pNew;
if( pNew==0 ) return SQLITE_NOMEM;
memset(pNew, 0, sizeof(*pNew));
}
return rc;
}
/*
** This method is the destructor for helptextVtab_vtab objects.
*/
static int helptextVtabDisconnect(sqlite3_vtab *pVtab){
helptextVtab_vtab *p = (helptextVtab_vtab*)pVtab;
sqlite3_free(p);
return SQLITE_OK;
}
/*
** Constructor for a new helptextVtab_cursor object.
*/
static int helptextVtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
helptextVtab_cursor *pCur;
pCur = sqlite3_malloc( sizeof(*pCur) );
if( pCur==0 ) return SQLITE_NOMEM;
memset(pCur, 0, sizeof(*pCur));
*ppCursor = &pCur->base;
return SQLITE_OK;
}
/*
** Destructor for a helptextVtab_cursor.
*/
static int helptextVtabClose(sqlite3_vtab_cursor *cur){
helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur;
sqlite3_free(pCur);
return SQLITE_OK;
}
/*
** Advance a helptextVtab_cursor to its next row of output.
*/
static int helptextVtabNext(sqlite3_vtab_cursor *cur){
helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur;
pCur->iRowid++;
return SQLITE_OK;
}
/*
** Return values of columns for the row at which the helptextVtab_cursor
** is currently pointing.
*/
static int helptextVtabColumn(
sqlite3_vtab_cursor *cur, /* The cursor */
sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
int i /* Which column to return */
){
helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur;
const CmdOrPage *pPage = aCommand + pCur->iRowid;
switch( i ){
case 0: /* name */
sqlite3_result_text(ctx, pPage->zName, -1, SQLITE_STATIC);
break;
case 1: { /* type */
const char *zType = 0;
if( pPage->eCmdFlags & CMDFLAG_COMMAND ){
zType = "command";
}else if( pPage->eCmdFlags & CMDFLAG_WEBPAGE ){
zType = "webpage";
}else if( pPage->eCmdFlags & CMDFLAG_SETTING ){
zType = "setting";
}
sqlite3_result_text(ctx, zType, -1, SQLITE_STATIC);
break;
}
case 2: /* flags */
sqlite3_result_int(ctx, pPage->eCmdFlags);
break;
case 3: /* helptext */
sqlite3_result_text(ctx, pPage->zHelp, -1, SQLITE_STATIC);
break;
}
return SQLITE_OK;
}
/*
** Return the rowid for the current row. In this implementation, the
** rowid is the same as the output value.
*/
static int helptextVtabRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur;
*pRowid = pCur->iRowid;
return SQLITE_OK;
}
/*
** Return TRUE if the cursor has been moved off of the last
** row of output.
*/
static int helptextVtabEof(sqlite3_vtab_cursor *cur){
helptextVtab_cursor *pCur = (helptextVtab_cursor*)cur;
return pCur->iRowid>=MX_COMMAND;
}
/*
** This method is called to "rewind" the helptextVtab_cursor object back
** to the first row of output. This method is always called at least
** once prior to any call to helptextVtabColumn() or helptextVtabRowid() or
** helptextVtabEof().
*/
static int helptextVtabFilter(
sqlite3_vtab_cursor *pVtabCursor,
int idxNum, const char *idxStr,
int argc, sqlite3_value **argv
){
helptextVtab_cursor *pCur = (helptextVtab_cursor *)pVtabCursor;
pCur->iRowid = 1;
return SQLITE_OK;
}
/*
** SQLite will invoke this method one or more times while planning a query
** that uses the virtual table. This routine needs to create
** a query plan for each invocation and compute an estimated cost for that
** plan.
*/
static int helptextVtabBestIndex(
sqlite3_vtab *tab,
sqlite3_index_info *pIdxInfo
){
pIdxInfo->estimatedCost = (double)MX_COMMAND;
pIdxInfo->estimatedRows = MX_COMMAND;
return SQLITE_OK;
}
/*
** This following structure defines all the methods for the
** virtual table.
*/
static sqlite3_module helptextVtabModule = {
/* iVersion */ 0,
/* xCreate */ 0, /* Helptext is eponymous and read-only */
/* xConnect */ helptextVtabConnect,
/* xBestIndex */ helptextVtabBestIndex,
/* xDisconnect */ helptextVtabDisconnect,
/* xDestroy */ 0,
/* xOpen */ helptextVtabOpen,
/* xClose */ helptextVtabClose,
/* xFilter */ helptextVtabFilter,
/* xNext */ helptextVtabNext,
/* xEof */ helptextVtabEof,
/* xColumn */ helptextVtabColumn,
/* xRowid */ helptextVtabRowid,
/* xUpdate */ 0,
/* xBegin */ 0,
/* xSync */ 0,
/* xCommit */ 0,
/* xRollback */ 0,
/* xFindMethod */ 0,
/* xRename */ 0,
/* xSavepoint */ 0,
/* xRelease */ 0,
/* xRollbackTo */ 0,
/* xShadowName */ 0
};
/*
** Register the helptext virtual table
*/
int helptext_vtab_register(sqlite3 *db){
int rc = sqlite3_create_module(db, "helptext", &helptextVtabModule, 0);
return rc;
}
/* End of the helptext virtual table
******************************************************************************/
|
1 2 3 4 5 6 | /* ** Copyright (c) 2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /* ** Copyright (c) 2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** ** 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. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ |
| ︙ | ︙ | |||
37 38 39 40 41 42 43 |
int n;
const unsigned char *x;
/* A table of mimetypes based on file content prefixes
*/
static const struct {
const char *zPrefix; /* The file prefix */
| | | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
int n;
const unsigned char *x;
/* A table of mimetypes based on file content prefixes
*/
static const struct {
const char *zPrefix; /* The file prefix */
const int size; /* Length of the prefix */
const char *zMimetype; /* The corresponding mimetype */
} aMime[] = {
{ "GIF87a", 6, "image/gif" },
{ "GIF89a", 6, "image/gif" },
{ "\211PNG\r\n\032\n", 8, "image/png" },
{ "\377\332\377", 3, "image/jpeg" },
{ "\377\330\377", 3, "image/jpeg" },
|
| ︙ | ︙ | |||
267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
{ "vcd", 3, "application/x-cdlink" },
{ "vda", 3, "application/vda" },
{ "viv", 3, "video/vnd.vivo" },
{ "vivo", 4, "video/vnd.vivo" },
{ "vrml", 4, "model/vrml" },
{ "wav", 3, "audio/x-wav" },
{ "wax", 3, "audio/x-ms-wax" },
{ "wiki", 4, "text/x-fossil-wiki" },
{ "wma", 3, "audio/x-ms-wma" },
{ "wmv", 3, "video/x-ms-wmv" },
{ "wmx", 3, "video/x-ms-wmx" },
{ "wrl", 3, "model/vrml" },
{ "wvx", 3, "video/x-ms-wvx" },
{ "xbm", 3, "image/x-xbitmap" },
| > | 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
{ "vcd", 3, "application/x-cdlink" },
{ "vda", 3, "application/vda" },
{ "viv", 3, "video/vnd.vivo" },
{ "vivo", 4, "video/vnd.vivo" },
{ "vrml", 4, "model/vrml" },
{ "wav", 3, "audio/x-wav" },
{ "wax", 3, "audio/x-ms-wax" },
{ "webp", 4, "image/webp" },
{ "wiki", 4, "text/x-fossil-wiki" },
{ "wma", 3, "audio/x-ms-wma" },
{ "wmv", 3, "video/x-ms-wmv" },
{ "wmx", 3, "video/x-ms-wmx" },
{ "wrl", 3, "model/vrml" },
{ "wvx", 3, "video/x-ms-wvx" },
{ "xbm", 3, "image/x-xbitmap" },
|
| ︙ | ︙ | |||
301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
for(i=1; i<count(aMime); i++){
if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){
fossil_panic("mimetypes out of sequence: %s before %s",
aMime[i-1].zSuffix, aMime[i].zSuffix);
}
}
}
/*
** Guess the mime-type of a document based on its name.
*/
const char *mimetype_from_name(const char *zName){
const char *z;
int i;
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
for(i=1; i<count(aMime); i++){
if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){
fossil_panic("mimetypes out of sequence: %s before %s",
aMime[i-1].zSuffix, aMime[i].zSuffix);
}
}
}
/*
** Looks in the contents of the "mimetypes" setting for a suffix
** matching zSuffix. If found, it returns the configured value
** in memory owned by the app (i.e. do not free() it), else it
** returns 0.
**
** The mimetypes setting is expected to be a list of file extensions
** and mimetypes, with one such mapping per line. A leading '.' on
** extensions is permitted for compatibility with lists imported from
** other tools which require them.
*/
static const char *mimetype_from_name_custom(const char *zSuffix){
static char * zList = 0;
static char const * zEnd = 0;
static int once = 0;
char * z;
int tokenizerState /* 0=expecting a key, 1=skip next token,
** 2=accept next token */;
if(once==0){
once = 1;
zList = db_get("mimetypes",0);
if(zList==0){
return 0;
}
/* Transform zList to simplify the main loop:
replace non-newline spaces with NUL bytes. */
zEnd = zList + strlen(zList);
for(z = zList; z<zEnd; ++z){
if('\n'==*z) continue;
else if(fossil_isspace(*z)){
*z = 0;
}
}
}else if(zList==0){
return 0;
}
tokenizerState = 0;
z = zList;
while( z<zEnd ){
if(*z==0){
++z;
continue;
}
else if('\n'==*z){
if(2==tokenizerState){
/* We were expecting a value for a successful match
here, but got no value. Bail out. */
break;
}else{
/* May happen on malformed inputs. Skip this record. */
tokenizerState = 0;
++z;
continue;
}
}
switch(tokenizerState){
case 0:{ /* This is a file extension */
static char * zCase = 0;
if('.'==*z){
/*ignore an optional leading dot, for compatibility
with some external mimetype lists*/;
if(++z==zEnd){
break;
}
}
if(zCase<z){
/*we have not yet case-folded this section: lower-case it*/
for(zCase = z; zCase<zEnd && *zCase!=0; ++zCase){
if(!(0x80 & *zCase)){
*zCase = (char)fossil_tolower(*zCase);
}
}
}
if(strcmp(z,zSuffix)==0){
tokenizerState = 2 /* Match: accept the next value. */;
}else{
tokenizerState = 1 /* No match: skip the next value. */;
}
z += strlen(z);
break;
}
case 1: /* This is a value, but not a match. Skip it. */
z += strlen(z);
break;
case 2: /* This is the value which matched the previous key. */;
return z;
default:
assert(!"cannot happen - invalid tokenizerState value.");
}
}
return 0;
}
/*
** Guess the mime-type of a document based on its name.
*/
const char *mimetype_from_name(const char *zName){
const char *z;
int i;
|
| ︙ | ︙ | |||
331 332 333 334 335 336 337 338 339 340 341 342 343 344 |
for(i=0; zName[i]; i++){
if( zName[i]=='.' ) z = &zName[i+1];
}
len = strlen(z);
if( len<sizeof(zSuffix)-1 ){
sqlite3_snprintf(sizeof(zSuffix), zSuffix, "%s", z);
for(i=0; zSuffix[i]; i++) zSuffix[i] = fossil_tolower(zSuffix[i]);
first = 0;
last = count(aMime) - 1;
while( first<=last ){
int c;
i = (first+last)/2;
c = fossil_strcmp(zSuffix, aMime[i].zSuffix);
if( c==0 ) return aMime[i].zMimetype;
| > > > > | 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 |
for(i=0; zName[i]; i++){
if( zName[i]=='.' ) z = &zName[i+1];
}
len = strlen(z);
if( len<sizeof(zSuffix)-1 ){
sqlite3_snprintf(sizeof(zSuffix), zSuffix, "%s", z);
for(i=0; zSuffix[i]; i++) zSuffix[i] = fossil_tolower(zSuffix[i]);
z = mimetype_from_name_custom(zSuffix);
if(z!=0){
return z;
}
first = 0;
last = count(aMime) - 1;
while( first<=last ){
int c;
i = (first+last)/2;
c = fossil_strcmp(zSuffix, aMime[i].zSuffix);
if( c==0 ) return aMime[i].zMimetype;
|
| ︙ | ︙ | |||
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 |
** If Fossil is compiled with -DFOSSIL_DEBUG then the "mimetype-test"
** filename is special and verifies the integrity of the mimetype table.
** It should return "ok".
*/
void mimetype_test_cmd(void){
int i;
mimetype_verify();
for(i=2; i<g.argc; i++){
fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i]));
}
}
/*
** WEBPAGE: mimetype_list
**
** Show the built-in table used to guess embedded document mimetypes
** from file suffixes.
*/
void mimetype_list_page(void){
int i;
mimetype_verify();
style_header("Mimetype List");
@ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename
| > > > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | 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 |
** If Fossil is compiled with -DFOSSIL_DEBUG then the "mimetype-test"
** filename is special and verifies the integrity of the mimetype table.
** It should return "ok".
*/
void mimetype_test_cmd(void){
int i;
mimetype_verify();
db_find_and_open_repository(0, 0);
for(i=2; i<g.argc; i++){
fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i]));
}
}
/*
** WEBPAGE: mimetype_list
**
** Show the built-in table used to guess embedded document mimetypes
** from file suffixes.
*/
void mimetype_list_page(void){
int i;
char *zCustomList = 0; /* value of the mimetypes setting */
int nCustomEntries = 0; /* number of entries in the mimetypes
** setting */
mimetype_verify();
style_header("Mimetype List");
@ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename
@ suffixes and the following tables to guess at the appropriate mimetype
@ for each document. Mimetypes may be customized and overridden using
@ <a href="%R/help?cmd=mimetypes">the mimetypes config setting</a>.</p>
zCustomList = db_get("mimetypes",0);
if( zCustomList!=0 ){
Blob list, entry, key, val;
@ <h1>Repository-specific mimetypes</h1>
@ <p>The following extension-to-mimetype mappings are defined via
@ the <a href="%R/help?cmd=mimetypes">mimetypes setting</a>.</p>
@ <table class='sortable mimetypetable' border=1 cellpadding=0 \
@ data-column-types='tt' data-init-sort='0'>
@ <thead>
@ <tr><th>Suffix<th>Mimetype
@ </thead>
@ <tbody>
blob_set(&list, zCustomList);
while( blob_line(&list, &entry)>0 ){
const char *zKey;
if( blob_token(&entry, &key)==0 ) continue;
if( blob_token(&entry, &val)==0 ) continue;
zKey = blob_str(&key);
if( zKey[0]=='.' ) zKey++;
@ <tr><td>%h(zKey)<td>%h(blob_str(&val))</tr>
nCustomEntries++;
}
fossil_free(zCustomList);
if( nCustomEntries==0 ){
/* This can happen if the option is set to an empty/space-only
** value. */
@ <tr><td colspan="2"><em>none</em></tr>
}
@ </tbody></table>
}
@ <h1>Default built-in mimetypes</h1>
if(nCustomEntries>0){
@ <p>Entries starting with an exclamation mark <em><strong>!</strong></em>
@ are overwritten by repository-specific settings.</p>
}
@ <table class='sortable mimetypetable' border=1 cellpadding=0 \
@ data-column-types='tt' data-init-sort='1'>
@ <thead>
@ <tr><th>Suffix<th>Mimetype
@ </thead>
@ <tbody>
for(i=0; i<count(aMime); i++){
const char *zFlag = "";
if(nCustomEntries>0 &&
mimetype_from_name_custom(aMime[i].zSuffix)!=0){
zFlag = "<em><strong>!</strong></em> ";
}
@ <tr><td>%s(zFlag)%h(aMime[i].zSuffix)<td>%h(aMime[i].zMimetype)</tr>
}
@ </tbody></table>
style_table_sorter();
style_footer();
}
/*
|
| ︙ | ︙ | |||
420 421 422 423 424 425 426 |
int seenTitle = 0;
while( fossil_isspace(zIn[0]) ) zIn++;
if( fossil_strnicmp(zIn,"<div",4)!=0 ) return 0;
zIn += 4;
while( zIn[0] ){
if( fossil_isspace(zIn[0]) ) zIn++;
| | | 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 |
int seenTitle = 0;
while( fossil_isspace(zIn[0]) ) zIn++;
if( fossil_strnicmp(zIn,"<div",4)!=0 ) return 0;
zIn += 4;
while( zIn[0] ){
if( fossil_isspace(zIn[0]) ) zIn++;
if( zIn[0]=='>' ) break;
zAttr = zIn;
while( fossil_isalnum(zIn[0]) || zIn[0]=='-' ) zIn++;
nAttr = (int)(zIn - zAttr);
while( fossil_isspace(zIn[0]) ) zIn++;
if( zIn[0]!='=' ) continue;
zIn++;
while( fossil_isspace(zIn[0]) ) zIn++;
|
| ︙ | ︙ | |||
504 505 506 507 508 509 510 511 512 513 514 515 |
rid = db_int(0, "SELECT rid FROM vcache"
" WHERE vid=%d AND fname=%Q", vid, zName);
if( rid && content_get(rid, pContent)==0 ){
rid = 0;
}
return rid;
}
/*
** Transfer content to the output. During the transfer, when text of
** the following form is seen:
**
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > | > | > > > | | > > > > > > > > > > > > > | 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 |
rid = db_int(0, "SELECT rid FROM vcache"
" WHERE vid=%d AND fname=%Q", vid, zName);
if( rid && content_get(rid, pContent)==0 ){
rid = 0;
}
return rid;
}
/*
** Check to verify that z[i] is contained within HTML markup.
**
** This works by looking backwards in the string for the most recent
** '<' or '>' character. If a '<' is found first, then we assume that
** z[i] is within markup. If a '>' is seen or neither character is seen,
** then z[i] is not within markup.
*/
static int isWithinHtmlMarkup(const char *z, int i){
while( i>=0 && z[i]!='>' && z[i]!='<' ){ i--; }
return z[i]=='<';
}
/*
** Check to see if z[i] is contained within an href='...' of markup.
*/
static int isWithinHref(const char *z, int i){
while( i>5
&& !fossil_isspace(z[i])
&& z[i]!='\'' && z[i]!='"'
&& z[i]!='>'
){ i--; }
if( i<=6 ) return 0;
if( z[i]!='\'' && z[i]!='\"' ) return 0;
if( strncmp(&z[i-5],"href=",5)!=0 ) return 0;
if( !fossil_isspace(z[i-6]) ) return 0;
return 1;
}
/*
** Transfer content to the output. During the transfer, when text of
** the following form is seen:
**
** href="$ROOT/..."
** action="$ROOT/..."
** href=".../doc/$CURRENT/..."
**
** Convert $ROOT to the root URI of the repository, and $CURRENT to the
** version number of the /doc/ document currently being displayed (if any).
** Allow ' in place of " and any case for href or action.
**
** Efforts are made to limit this translation to cases where the text is
** fully contained with an HTML markup element.
*/
void convert_href_and_output(Blob *pIn){
int i, base;
int n = blob_size(pIn);
char *z = blob_buffer(pIn);
for(base=0, i=7; i<n; i++){
if( z[i]=='$'
&& strncmp(&z[i],"$ROOT/", 6)==0
&& (z[i-1]=='\'' || z[i-1]=='"')
&& i-base>=9
&& ((fossil_strnicmp(&z[i-6],"href=",5)==0 && fossil_isspace(z[i-7])) ||
(fossil_strnicmp(&z[i-8],"action=",7)==0 && fossil_isspace(z[i-9])) )
&& isWithinHtmlMarkup(z, i-6)
){
blob_append(cgi_output_blob(), &z[base], i-base);
blob_appendf(cgi_output_blob(), "%R");
base = i+5;
}else
if( z[i]=='$'
&& strncmp(&z[i-5],"/doc/$CURRENT/", 11)==0
&& isWithinHref(z,i-5)
&& isWithinHtmlMarkup(z, i-5)
&& strncmp(g.zPath, "doc/",4)==0
){
int j;
for(j=7; g.zPath[j] && g.zPath[j]!='/'; j++){}
blob_append(cgi_output_blob(), &z[base], i-base);
blob_appendf(cgi_output_blob(), "%.*s", j-4, g.zPath+4);
base = i+8;
}
}
blob_append(cgi_output_blob(), &z[base], i-base);
}
/*
** Render a document as the reply to the HTTP request. The body
|
| ︙ | ︙ | |||
593 594 595 596 597 598 599 |
Blob tail;
blob_zero(&tail);
if( wiki_find_title(pBody, &title, &tail) ){
style_header("%s", blob_str(&title));
Th_Render(blob_str(&tail));
blob_reset(&tail);
}else{
| | > | > > > | > | 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 |
Blob tail;
blob_zero(&tail);
if( wiki_find_title(pBody, &title, &tail) ){
style_header("%s", blob_str(&title));
Th_Render(blob_str(&tail));
blob_reset(&tail);
}else{
style_header("%h", zFilename);
Th_Render(blob_str(pBody));
}
}else{
Th_Render(blob_str(pBody));
}
if( !raw ){
style_footer();
}
#endif
}else{
fossil_free(style_csp(1));
cgi_set_content_type(zMime);
cgi_set_content(pBody);
}
}
/*
** WEBPAGE: uv
** WEBPAGE: doc
** URL: /uv/FILE
** URL: /doc/CHECKIN/FILE
**
** CHECKIN can be either tag or hash prefix or timestamp identifying a
** particular check-in, or the name of a branch (meaning the most recent
** check-in on that branch) or one of various magic words:
**
** "tip" means the most recent check-in
**
** "ckout" means the current check-out, if the server is run from
** within a check-out, otherwise it is the same as "tip"
**
** "latest" means use the most recent check-in for the document
** regardless of what branch it occurs on.
**
** FILE is the name of a file to delivered up as a webpage. FILE is relative
** to the root of the source tree of the repository. The FILE must
** be a part of CHECKIN, except when CHECKIN=="ckout" when FILE is read
** directly from disk and need not be a managed file. For /uv, FILE
** can also be the hash of the unversioned file.
**
** The "ckout" CHECKIN is intended for development - to provide a mechanism
** for looking at what a file will look like using the /doc webpage after
** it gets checked in.
**
** The file extension is used to decide how to render the file.
**
|
| ︙ | ︙ | |||
694 695 696 697 698 699 700 701 702 703 704 705 706 707 |
i = 0;
}else{
if( zName==0 || zName[0]==0 ) zName = "tip/index.wiki";
for(i=0; zName[i] && zName[i]!='/'; i++){}
zCheckin = mprintf("%.*s", i, zName);
if( fossil_strcmp(zCheckin,"ckout")==0 && g.localOpen==0 ){
zCheckin = "tip";
}
}
if( nMiss==count(azSuffix) ){
zName = "404.md";
zDfltTitle = "Not Found";
}else if( zName[i]==0 ){
assert( nMiss>=0 && nMiss<count(azSuffix) );
| > > > > > > > > > > | 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 |
i = 0;
}else{
if( zName==0 || zName[0]==0 ) zName = "tip/index.wiki";
for(i=0; zName[i] && zName[i]!='/'; i++){}
zCheckin = mprintf("%.*s", i, zName);
if( fossil_strcmp(zCheckin,"ckout")==0 && g.localOpen==0 ){
zCheckin = "tip";
}else if( fossil_strcmp(zCheckin,"latest")==0 ){
char *zNewCkin = db_text(0,
"SELECT uuid FROM blob, mlink, event, filename"
" WHERE filename.name=%Q"
" AND mlink.fnid=filename.fnid"
" AND blob.rid=mlink.mid"
" AND event.objid=mlink.mid"
" ORDER BY event.mtime DESC LIMIT 1",
zName + i + 1);
if( zNewCkin ) zCheckin = zNewCkin;
}
}
if( nMiss==count(azSuffix) ){
zName = "404.md";
zDfltTitle = "Not Found";
}else if( zName[i]==0 ){
assert( nMiss>=0 && nMiss<count(azSuffix) );
|
| ︙ | ︙ | |||
725 726 727 728 729 730 731 |
}
}else{
goto doc_not_found;
}
}
if( isUV ){
if( db_table_exists("repository","unversioned") ){
| > > | | | | | | | | > > | | < > | 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 |
}
}else{
goto doc_not_found;
}
}
if( isUV ){
if( db_table_exists("repository","unversioned") ){
rid = unversioned_content(zName, &filebody);
if( rid==1 ){
Stmt q;
db_prepare(&q, "SELECT hash, mtime FROM unversioned"
" WHERE name=%Q", zName);
if( db_step(&q)==SQLITE_ROW ){
etag_check(ETAG_HASH, db_column_text(&q,0));
etag_last_modified(db_column_int64(&q,1));
}
db_finalize(&q);
}else if( rid==2 ){
zName = db_text(zName,
"SELECT name FROM unversioned WHERE hash=%Q", zName);
g.isConst = 1;
}
zDfltTitle = zName;
}
}else if( fossil_strcmp(zCheckin,"ckout")==0 ){
/* Read from the local checkout */
char *zFullpath;
db_must_be_within_tree();
zFullpath = mprintf("%s/%s", g.zLocalRoot, zName);
if( file_isfile(zFullpath, RepoFILE)
|
| ︙ | ︙ | |||
917 918 919 920 921 922 923 924 925 926 927 928 929 930 |
if( blob_size(&bgimg)==0 ){
blob_init(&bgimg, (char*)aBackground, sizeof(aBackground));
}
cgi_set_content_type(zMime);
cgi_set_content(&bgimg);
}
/*
** WEBPAGE: docsrch
**
** Search for documents that match a user-supplied full-text search pattern.
** If no pattern is specified (by the s= query parameter) then the user
** is prompted to enter a search string.
| > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
if( blob_size(&bgimg)==0 ){
blob_init(&bgimg, (char*)aBackground, sizeof(aBackground));
}
cgi_set_content_type(zMime);
cgi_set_content(&bgimg);
}
/*
** WEBPAGE: favicon.ico
**
** Return the default favicon.ico image. The returned image is for the
** Fossil lizard icon.
**
** The intended use case here is to supply a favicon for the "fossil ui"
** command. For a permanent website, the recommended process is for
** the admin to set up a project-specific favicon and reference that
** icon in the HTML header using a line like:
**
** <link rel="icon" href="URL-FOR-YOUR-ICON" type="MIMETYPE"/>
**
*/
void favicon_page(void){
Blob favicon;
etag_check(ETAG_CONFIG, 0);
blob_zero(&favicon);
blob_init(&favicon, (char*)aLogo, sizeof(aLogo));
cgi_set_content_type("image/gif");
cgi_set_content(&favicon);
}
/*
** WEBPAGE: docsrch
**
** Search for documents that match a user-supplied full-text search pattern.
** If no pattern is specified (by the s= query parameter) then the user
** is prompted to enter a search string.
|
| ︙ | ︙ |
| ︙ | ︙ | |||
312 313 314 315 316 317 318 |
}
/*
** Decode a fossilized string in-place.
*/
void defossilize(char *z){
int i, j, c;
| | | > | 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
}
/*
** Decode a fossilized string in-place.
*/
void defossilize(char *z){
int i, j, c;
char *zSlash = strchr(z, '\\');
if( zSlash==0 ) return;
i = zSlash - z;
for(j=i; (c=z[i])!=0; i++){
if( c=='\\' && z[i+1] ){
i++;
switch( z[i] ){
case 'n': c = '\n'; break;
case 's': c = ' '; break;
case 't': c = '\t'; break;
|
| ︙ | ︙ | |||
374 375 376 377 378 379 380 |
|| (c&0xFFFFF800)==0xD800
|| (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; }
}
return c;
}
/*
| | > | | > > > > > | > > > > > > > | 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 |
|| (c&0xFFFFF800)==0xD800
|| (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; }
}
return c;
}
/*
** Encode a UTF8 string as a JSON string literal (with or without the
** surrounding "...", depending on whether the 2nd argument is true or
** false) and return a pointer to the encoding. Space to hold the
** encoding is obtained from fossil_malloc() and must be freed by the
** caller.
**
** If nOut is not NULL then it is assigned to the length, in bytes, of
** the returned string (its strlen(), not counting the terminating
** NUL).
*/
char *encode_json_string_literal(const char *zStr, int fAddQuotes,
int * nOut){
const unsigned char *z;
char *zOut;
u32 c;
int n, i, j;
z = (const unsigned char*)zStr;
n = 0;
while( (c = fossil_utf8_read(&z))!=0 ){
if( c=='\\' || c=='"' ){
n += 2;
}else if( c<' ' || c>=0x7f ){
if( c=='\n' || c=='\r' ){
n += 2;
}else{
n += 6;
}
}else{
n++;
}
}
if(fAddQuotes){
n += 2;
}
zOut = fossil_malloc(n+1);
if( zOut==0 ) return 0;
z = (const unsigned char*)zStr;
i = 0;
if(fAddQuotes){
zOut[i++] = '"';
}
while( (c = fossil_utf8_read(&z))!=0 ){
if( c=='\\' ){
zOut[i++] = '\\';
zOut[i++] = c;
}else if( c<' ' || c>=0x7f ){
zOut[i++] = '\\';
if( c=='\n' ){
|
| ︙ | ︙ | |||
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 |
}
i += 4;
}
}else{
zOut[i++] = c;
}
}
zOut[i] = 0;
return zOut;
}
/*
** The characters used for HTTP base64 encoding.
*/
static unsigned char zBase[] =
| > > > > > > | 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 |
}
i += 4;
}
}else{
zOut[i++] = c;
}
}
if(fAddQuotes){
zOut[i++] = '"';
}
zOut[i] = 0;
if(nOut!=0){
*nOut = i;
}
return zOut;
}
/*
** The characters used for HTTP base64 encoding.
*/
static unsigned char zBase[] =
|
| ︙ | ︙ | |||
644 645 646 647 648 649 650 651 652 653 654 655 656 657 |
/*
** Return true if the input string contains only valid base-16 digits.
** If any invalid characters appear in the string, return false.
*/
int validate16(const char *zIn, int nIn){
int i;
if( nIn<0 ) nIn = (int)strlen(zIn);
for(i=0; i<nIn; i++, zIn++){
if( zDecode[zIn[0]&0xff]>63 ){
return zIn[0]==0;
}
}
return 1;
}
| > > > | 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 |
/*
** Return true if the input string contains only valid base-16 digits.
** If any invalid characters appear in the string, return false.
*/
int validate16(const char *zIn, int nIn){
int i;
if( nIn<0 ) nIn = (int)strlen(zIn);
if( zIn[nIn]==0 ){
return strspn(zIn,"0123456789abcdefABCDEF")==nIn;
}
for(i=0; i<nIn; i++, zIn++){
if( zDecode[zIn[0]&0xff]>63 ){
return zIn[0]==0;
}
}
return 1;
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
22 23 24 25 26 27 28 29 30 31 32 33 34 35 | ** in the ETag include: ** ** (1) The mtime on the Fossil executable ** (2) The last change to the CONFIG table ** (3) The last change to the EVENT table ** (4) The value of the display cookie ** (5) A hash value supplied by the page generator ** ** Item (1) is always included in the ETag. The other elements are ** optional. Because (1) is always included as part of the ETag, all ** outstanding ETags can be invalidated by touching the fossil executable. ** ** A page generator routine invokes etag_check() exactly once, with ** arguments that indicates which of the above elements to include in the | > > | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | ** in the ETag include: ** ** (1) The mtime on the Fossil executable ** (2) The last change to the CONFIG table ** (3) The last change to the EVENT table ** (4) The value of the display cookie ** (5) A hash value supplied by the page generator ** (6) The details of the request URI ** (7) The name user as determined by the login cookie ** ** Item (1) is always included in the ETag. The other elements are ** optional. Because (1) is always included as part of the ETag, all ** outstanding ETags can be invalidated by touching the fossil executable. ** ** A page generator routine invokes etag_check() exactly once, with ** arguments that indicates which of the above elements to include in the |
| ︙ | ︙ | |||
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
/*
** Things to monitor
*/
#define ETAG_CONFIG 0x01 /* Output depends on the CONFIG table */
#define ETAG_DATA 0x02 /* Output depends on the EVENT table */
#define ETAG_COOKIE 0x04 /* Output depends on a display cookie value */
#define ETAG_HASH 0x08 /* Output depends on a hash */
#endif
static char zETag[33]; /* The generated ETag */
static int iMaxAge = 0; /* The max-age parameter in the reply */
static sqlite3_int64 iEtagMtime = 0; /* Last-Modified time */
/*
** Generate an ETag
*/
void etag_check(unsigned eFlags, const char *zHash){
| > > > > > > > > > > > > > > > > > > > > < > | | | | > | > | | > > > > > > > > > > > > > > > > > | 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 |
/*
** Things to monitor
*/
#define ETAG_CONFIG 0x01 /* Output depends on the CONFIG table */
#define ETAG_DATA 0x02 /* Output depends on the EVENT table */
#define ETAG_COOKIE 0x04 /* Output depends on a display cookie value */
#define ETAG_HASH 0x08 /* Output depends on a hash */
#define ETAG_QUERY 0x10 /* Output depends on PATH_INFO and QUERY_STRING */
/* and the g.zLogin value */
#endif
static char zETag[33]; /* The generated ETag */
static int iMaxAge = 0; /* The max-age parameter in the reply */
static sqlite3_int64 iEtagMtime = 0; /* Last-Modified time */
static int etagCancelled = 0; /* Never send an etag */
/*
** Return a hash that changes every time the Fossil source code is
** rebuilt.
**
** The FOSSIL_BUILD_HASH string that is returned here gets computed by
** the mkversion utility program. The result is a hash of MANIFEST_UUID
** and the unix timestamp for when the mkversion utility program is run.
**
** During development rebuilds, if you need the source code id to change
** in order to invalidate caches, simply "touch" the "manifest" file in
** the top of the source directory prior to running "make" and a new
** FOSSIL_BUILD_HASH will be generated automatically.
*/
const char *fossil_exe_id(void){
return FOSSIL_BUILD_HASH;
}
/*
** Generate an ETag
*/
void etag_check(unsigned eFlags, const char *zHash){
const char *zIfNoneMatch;
char zBuf[50];
assert( zETag[0]==0 ); /* Only call this routine once! */
if( etagCancelled ) return;
iMaxAge = 86400;
md5sum_init();
/* Always include the executable ID as part of the hash */
md5sum_step_text("exe-id: ", -1);
md5sum_step_text(fossil_exe_id(), -1);
md5sum_step_text("\n", 1);
if( (eFlags & ETAG_HASH)!=0 && zHash ){
md5sum_step_text("hash: ", -1);
md5sum_step_text(zHash, -1);
md5sum_step_text("\n", 1);
iMaxAge = 0;
}
if( eFlags & ETAG_DATA ){
int iKey = db_int(0, "SELECT max(rcvid) FROM rcvfrom");
sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",iKey);
md5sum_step_text("data: ", -1);
md5sum_step_text(zBuf, -1);
md5sum_step_text("\n", 1);
iMaxAge = 60;
}
if( eFlags & ETAG_CONFIG ){
int iKey = db_int(0, "SELECT value FROM config WHERE name='cfgcnt'");
sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",iKey);
md5sum_step_text("config: ", -1);
md5sum_step_text(zBuf, -1);
md5sum_step_text("\n", 1);
iMaxAge = 3600;
}
/* Include the display cookie */
if( eFlags & ETAG_COOKIE ){
md5sum_step_text("display-cookie: ", -1);
md5sum_step_text(PD(DISPLAY_SETTINGS_COOKIE,""), -1);
md5sum_step_text("\n", 1);
iMaxAge = 0;
}
/* Output depends on PATH_INFO and QUERY_STRING */
if( eFlags & ETAG_QUERY ){
const char *zQS = P("QUERY_STRING");
md5sum_step_text("query: ", -1);
md5sum_step_text(PD("PATH_INFO",""), -1);
if( zQS ){
md5sum_step_text("?", 1);
md5sum_step_text(zQS, -1);
}
md5sum_step_text("\n",1);
if( g.zLogin ){
md5sum_step_text("login: ", -1);
md5sum_step_text(g.zLogin, -1);
md5sum_step_text("\n", 1);
}
}
/* Generate the ETag */
memcpy(zETag, md5sum_finish(0), 33);
/* Check to see if the generated ETag matches If-None-Match and
** generate a 304 reply if it does. */
zIfNoneMatch = P("HTTP_IF_NONE_MATCH");
|
| ︙ | ︙ | |||
206 207 208 209 210 211 212 |
db_find_and_open_repository(0, 0);
zKey = find_option("key",0,1);
zHash = find_option("hash",0,1);
if( zKey ) iKey = atoi(zKey);
etag_check(iKey, zHash);
fossil_print("%s\n", etag_tag());
}
| > > > > > > > > | 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
db_find_and_open_repository(0, 0);
zKey = find_option("key",0,1);
zHash = find_option("hash",0,1);
if( zKey ) iKey = atoi(zKey);
etag_check(iKey, zHash);
fossil_print("%s\n", etag_tag());
}
/*
** Cancel the ETag.
*/
void etag_cancel(void){
etagCancelled = 1;
zETag[0] = 0;
}
|
| ︙ | ︙ | |||
61 62 63 64 65 66 67 |
** v=BOOLEAN Show details if TRUE. Default is FALSE. Optional.
**
** Display an existing tech-note identified by its ID, optionally at a
** specific version, and optionally with additional details.
*/
void event_page(void){
int rid = 0; /* rid of the event artifact */
| | | 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
** v=BOOLEAN Show details if TRUE. Default is FALSE. Optional.
**
** Display an existing tech-note identified by its ID, optionally at a
** specific version, and optionally with additional details.
*/
void event_page(void){
int rid = 0; /* rid of the event artifact */
char *zUuid; /* artifact hash corresponding to rid */
const char *zId; /* Event identifier */
const char *zVerbose; /* Value of verbose option */
char *zETime; /* Time of the tech-note */
char *zATime; /* Time the artifact was created */
int specRid; /* rid specified by aid= parameter */
int prevRid, nextRid; /* Previous or next edits of this tech-note */
Manifest *pTNote; /* Parsed technote artifact */
|
| ︙ | ︙ | |||
355 356 357 358 359 360 361 362 363 364 365 366 367 368 |
** w=TEXT Complete text of the technote.
** t=TEXT Time of the technote on the timeline (ISO 8601)
** c=TEXT Timeline comment
** g=TEXT Tags associated with this technote
** mimetype=TEXT Mimetype for w= text
** newclr Use a background color
** clr=TEXT Background color to use if newclr
*/
void eventedit_page(void){
char *zTag;
int rid = 0;
Blob event;
const char *zId;
int n;
| > > > > | 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 |
** w=TEXT Complete text of the technote.
** t=TEXT Time of the technote on the timeline (ISO 8601)
** c=TEXT Timeline comment
** g=TEXT Tags associated with this technote
** mimetype=TEXT Mimetype for w= text
** newclr Use a background color
** clr=TEXT Background color to use if newclr
**
** For GET requests, when editing an existing technote newclr and clr
** are implied if a custom color has been set on the previous version
** of the technote.
*/
void eventedit_page(void){
char *zTag;
int rid = 0;
Blob event;
const char *zId;
int n;
|
| ︙ | ︙ | |||
412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
login_needed(g.anon.Write && (rid ? g.anon.WrWiki : g.anon.NewWiki));
return;
}
/* Figure out the color */
if( rid ){
zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid);
}else{
zClr = "";
isNew = 1;
}
if( P("newclr") ){
zClr = PD("clr",zClr);
if( zClr[0] ) zClrFlag = " checked";
| > > > > > > > > > | 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 |
login_needed(g.anon.Write && (rid ? g.anon.WrWiki : g.anon.NewWiki));
return;
}
/* Figure out the color */
if( rid ){
zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid);
if( zClr && zClr[0] ){
const char * zRequestMethod = P("REQUEST_METHOD");
if(zRequestMethod && 'G'==zRequestMethod[0]){
/* Apply saved color by defaut for GET requests
** (e.g., an Edit menu link).
*/
zClrFlag = " checked";
}
}
}else{
zClr = "";
isNew = 1;
}
if( P("newclr") ){
zClr = PD("clr",zClr);
if( zClr[0] ) zClrFlag = " checked";
|
| ︙ | ︙ | |||
493 494 495 496 497 498 499 500 501 502 503 504 505 506 |
wiki_convert(&com, 0, WIKI_INLINE|WIKI_NOBADLINKS);
@ </td></tr></table>
@ </blockquote>
@ <p><b>Page content preview:</b><p>
@ <blockquote>
blob_init(&event, 0, 0);
blob_append(&event, zBody, -1);
wiki_render_by_mimetype(&event, zMimetype);
@ </blockquote><hr />
blob_reset(&event);
}
for(n=2, z=zBody; z[0]; z++){
if( z[0]=='\n' ) n++;
}
| > | 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 |
wiki_convert(&com, 0, WIKI_INLINE|WIKI_NOBADLINKS);
@ </td></tr></table>
@ </blockquote>
@ <p><b>Page content preview:</b><p>
@ <blockquote>
blob_init(&event, 0, 0);
blob_append(&event, zBody, -1);
safe_html_context(DOCSRC_WIKI);
wiki_render_by_mimetype(&event, zMimetype);
@ </blockquote><hr />
blob_reset(&event);
}
for(n=2, z=zBody; z[0]; z++){
if( z[0]=='\n' ) n++;
}
|
| ︙ | ︙ | |||
530 531 532 533 534 535 536 | @ </td></tr> @ <tr><th align="right" valign="top">Tags:</th> @ <td valign="top"> @ <input type="text" name="g" size="40" value="%h(zTags)" /> @ </td></tr> | | > | 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 |
@ </td></tr>
@ <tr><th align="right" valign="top">Tags:</th>
@ <td valign="top">
@ <input type="text" name="g" size="40" value="%h(zTags)" />
@ </td></tr>
@ <tr><th align="right" valign="top">\
@ %z(href("%R/markup_help"))Markup Style</a>:</th>
@ <td valign="top">
mimetype_option_menu(zMimetype);
@ </td></tr>
@ <tr><th align="right" valign="top">Page Content:</th>
@ <td valign="top">
@ <textarea name="w" class="technoteedit" cols="80"
|
| ︙ | ︙ |
| ︙ | ︙ | |||
168 169 170 171 172 173 174 |
printf(" %s <%s>", zName, zEmail);
free(zName);
free(zEmail);
db_reset(&q);
}
| | | 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
printf(" %s <%s>", zName, zEmail);
free(zName);
free(zEmail);
db_reset(&q);
}
#define REFREPLACEMENT '_'
/*
** Output a sanitized git named reference.
** https://git-scm.com/docs/git-check-ref-format
** This implementation assumes we are only printing
** the branch or tag part of the reference.
*/
|
| ︙ | ︙ | |||
209 210 211 212 213 214 215 |
case '^':
case ':':
case '?':
case '*':
case '[':
case '\\':
zEncoded[w]=REFREPLACEMENT;
| | | 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
case '^':
case ':':
case '?':
case '*':
case '[':
case '\\':
zEncoded[w]=REFREPLACEMENT;
break;
}
}
/* Cannot begin with a . or / */
if( zEncoded[0]=='.' || zEncoded[0] == '/' ) zEncoded[0]=REFREPLACEMENT;
if( i>0 ){
i--; w--;
/* Or end with a . or / */
|
| ︙ | ︙ | |||
880 881 882 883 884 885 886 |
** tag names into "_".
*/
static void gitmirror_sanitize_name(char *z){
static unsigned char aSafe[] = {
/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */
| | | | > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
** tag names into "_".
*/
static void gitmirror_sanitize_name(char *z){
static unsigned char aSafe[] = {
/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */
0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, /* 2x */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, /* 3x */
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, /* 5x */
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, /* 7x */
};
unsigned char *zu = (unsigned char*)z;
int i;
for(i=0; zu[i]; i++){
if( zu[i]>0x7f || !aSafe[zu[i]] ){
zu[i] = '_';
}else if( zu[i]=='/' && (i==0 || zu[i+1]==0 || zu[i+1]=='/') ){
zu[i] = '_';
}else if( zu[i]=='.' && (zu[i+1]==0 || zu[i+1]=='.'
|| (i>0 && zu[i-1]=='.')) ){
zu[i] = '_';
}
}
}
/*
** COMMAND: test-sanitize-name
**
** Usage: %fossil ARG...
**
** This sanitizes each argument and make it part of an "echo" command
** run by the shell.
*/
void test_sanitize_name_cmd(void){
sqlite3_str *pStr;
int i;
char *zCmd;
pStr = sqlite3_str_new(0);
sqlite3_str_appendall(pStr, "echo");
for(i=2; i<g.argc; i++){
char *z = fossil_strdup(g.argv[i]);
gitmirror_sanitize_name(z);
sqlite3_str_appendf(pStr, " \"%s\"", z);
fossil_free(z);
}
zCmd = sqlite3_str_finish(pStr);
fossil_print("Command: %s\n", zCmd);
fossil_system(zCmd);
sqlite3_free(zCmd);
}
/*
** Quote a filename as a C-style string using \\ and \" if necessary.
** If quoting is not necessary, just return a copy of the input string.
**
** The return value is a held in memory obtained from fossil_malloc()
** and must be freed by the caller.
|
| ︙ | ︙ | |||
1284 1285 1286 1287 1288 1289 1290 |
/* Make sure the GIT repository directory exists */
rc = file_mkdir(zMirror, ExtFILE, 0);
if( rc ) fossil_fatal("cannot create directory \"%s\"", zMirror);
/* Make sure GIT has been initialized */
z = mprintf("%s/.git", zMirror);
if( !file_isdir(z, ExtFILE) ){
| | | 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 |
/* Make sure the GIT repository directory exists */
rc = file_mkdir(zMirror, ExtFILE, 0);
if( rc ) fossil_fatal("cannot create directory \"%s\"", zMirror);
/* Make sure GIT has been initialized */
z = mprintf("%s/.git", zMirror);
if( !file_isdir(z, ExtFILE) ){
zCmd = mprintf("git init %$",zMirror);
gitmirror_message(VERB_NORMAL, "%s\n", zCmd);
rc = fossil_system(zCmd);
if( rc ){
fossil_fatal("cannot initialize the git repository using: \"%s\"", zCmd);
}
fossil_free(zCmd);
bNeedRepack = 1;
|
| ︙ | ︙ | |||
1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 |
if( xCmd==0 ) fossil_fatal("cannot open file \"%s\" for writing", zDebug);
}
}else{
zCmd = mprintf("git fast-import"
" --export-marks=.mirror_state/marks.txt"
" --quiet --done");
gitmirror_message(VERB_NORMAL, "%s\n", zCmd);
xCmd = popen(zCmd, "w");
if( zCmd==0 ){
fossil_fatal("cannot start the \"git fast-import\" command");
}
fossil_free(zCmd);
}
/* Run the export */
| > > > > | 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 |
if( xCmd==0 ) fossil_fatal("cannot open file \"%s\" for writing", zDebug);
}
}else{
zCmd = mprintf("git fast-import"
" --export-marks=.mirror_state/marks.txt"
" --quiet --done");
gitmirror_message(VERB_NORMAL, "%s\n", zCmd);
#ifdef _WIN32
xCmd = popen(zCmd, "wb");
#else
xCmd = popen(zCmd, "w");
#endif
if( zCmd==0 ){
fossil_fatal("cannot start the \"git fast-import\" command");
}
fossil_free(zCmd);
}
/* Run the export */
|
| ︙ | ︙ | |||
1478 1479 1480 1481 1482 1483 1484 |
" JOIN mmark ON mmark.uuid=blob.uuid;"
);
while( db_step(&q)==SQLITE_ROW ){
char *zTagname = fossil_strdup(db_column_text(&q,0));
const char *zObj = db_column_text(&q,1);
char *zTagCmd;
gitmirror_sanitize_name(zTagname);
| | | 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 |
" JOIN mmark ON mmark.uuid=blob.uuid;"
);
while( db_step(&q)==SQLITE_ROW ){
char *zTagname = fossil_strdup(db_column_text(&q,0));
const char *zObj = db_column_text(&q,1);
char *zTagCmd;
gitmirror_sanitize_name(zTagname);
zTagCmd = mprintf("git tag -f %$ %$", zTagname, zObj);
fossil_free(zTagname);
gitmirror_message(VERB_NORMAL, "%s\n", zTagCmd);
fossil_system(zTagCmd);
fossil_free(zTagCmd);
}
db_finalize(&q);
|
| ︙ | ︙ | |||
1513 1514 1515 1516 1517 1518 1519 |
char *zRefCmd;
if( fossil_strcmp(zBrname,"trunk")==0 ){
fossil_free(zBrname);
zBrname = fossil_strdup("master");
}else{
gitmirror_sanitize_name(zBrname);
}
| | | 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 |
char *zRefCmd;
if( fossil_strcmp(zBrname,"trunk")==0 ){
fossil_free(zBrname);
zBrname = fossil_strdup("master");
}else{
gitmirror_sanitize_name(zBrname);
}
zRefCmd = mprintf("git update-ref \"refs/heads/%s\" %$", zBrname, zObj);
fossil_free(zBrname);
gitmirror_message(VERB_NORMAL, "%s\n", zRefCmd);
fossil_system(zRefCmd);
fossil_free(zRefCmd);
}
db_finalize(&q);
|
| ︙ | ︙ | |||
1550 1551 1552 1553 1554 1555 1556 |
url_parse_local(zPushUrl, 0, &url);
zPushCmd = mprintf("git push --mirror %s", url.canonical);
}else{
zPushCmd = mprintf("git push --mirror %s", zPushUrl);
}
gitmirror_message(VERB_NORMAL, "%s\n", zPushCmd);
fossil_free(zPushCmd);
| | | 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 |
url_parse_local(zPushUrl, 0, &url);
zPushCmd = mprintf("git push --mirror %s", url.canonical);
}else{
zPushCmd = mprintf("git push --mirror %s", zPushUrl);
}
gitmirror_message(VERB_NORMAL, "%s\n", zPushCmd);
fossil_free(zPushCmd);
zPushCmd = mprintf("git push --mirror %$", zPushUrl);
fossil_system(zPushCmd);
fossil_free(zPushCmd);
}
}
/*
** Implementation of the "fossil git status" command.
|
| ︙ | ︙ | |||
1617 1618 1619 1620 1621 1622 1623 | ** COMMAND: git ** ** Usage: %fossil git SUBCOMMAND ** ** Do incremental import or export operations between Fossil and Git. ** Subcommands: ** | | | 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 | ** COMMAND: git ** ** Usage: %fossil git SUBCOMMAND ** ** Do incremental import or export operations between Fossil and Git. ** Subcommands: ** ** > fossil git export [MIRROR] [OPTIONS] ** ** Write content from the Fossil repository into the Git repository ** in directory MIRROR. The Git repository is created if it does not ** already exist. If the Git repository does already exist, then ** new content added to fossil since the previous export is appended. ** ** Repeat this command whenever new checkins are added to the Fossil |
| ︙ | ︙ | |||
1648 1649 1650 1651 1652 1653 1654 | ** piping it into "git fast-import". ** --force|-f Do the export even if nothing has changed ** --limit N Add no more than N new check-ins to MIRROR. ** Useful for debugging ** --quiet|-q Reduce output. Repeat for even less output. ** --verbose|-v More output. ** | | | | 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 |
** piping it into "git fast-import".
** --force|-f Do the export even if nothing has changed
** --limit N Add no more than N new check-ins to MIRROR.
** Useful for debugging
** --quiet|-q Reduce output. Repeat for even less output.
** --verbose|-v More output.
**
** > fossil git import MIRROR
**
** TBD...
**
** > fossil git status
**
** Show the status of the current Git mirror, if there is one.
*/
void gitmirror_command(void){
char *zCmd;
int nCmd;
if( g.argc<3 ){
|
| ︙ | ︙ |
| ︙ | ︙ | |||
46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
static const char *azCgiEnv[] = {
"AUTH_TYPE",
"AUTH_CONTENT",
"CONTENT_LENGTH",
"CONTENT_TYPE",
"DOCUMENT_ROOT",
"FOSSIL_CAPABILITIES",
"FOSSIL_REPOSITORY",
"FOSSIL_URI",
"FOSSIL_USER",
"GATEWAY_INTERFACE",
"HTTPS",
"HTTP_ACCEPT",
/* "HTTP_ACCEPT_ENCODING", // omitted from sub-cgi */
| > | 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
static const char *azCgiEnv[] = {
"AUTH_TYPE",
"AUTH_CONTENT",
"CONTENT_LENGTH",
"CONTENT_TYPE",
"DOCUMENT_ROOT",
"FOSSIL_CAPABILITIES",
"FOSSIL_NONCE",
"FOSSIL_REPOSITORY",
"FOSSIL_URI",
"FOSSIL_USER",
"GATEWAY_INTERFACE",
"HTTPS",
"HTTP_ACCEPT",
/* "HTTP_ACCEPT_ENCODING", // omitted from sub-cgi */
|
| ︙ | ︙ | |||
95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
if( !fossil_isalnum(c) && c!='_' && c!='-' && c!='.' && c!='/' ){
zFailReason = "illegal character in path";
break;
}
}
return zFailReason;
}
/*
** WEBPAGE: ext raw-content
**
** Relay an HTTP request to secondary CGI after first checking the
** login credentials and setting auxiliary environment variables
** so that the secondary CGI can be aware of the credentials and
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
if( !fossil_isalnum(c) && c!='_' && c!='-' && c!='.' && c!='/' ){
zFailReason = "illegal character in path";
break;
}
}
return zFailReason;
}
/*
** The *pzPath input is a pathname obtained from mprintf().
**
** If
**
** (1) zPathname is the name of a directory, and
** (2) the name ends with "/", and
** (3) the directory contains a file named index.html, index.wiki,
** or index.md (in that order)
**
** then replace the input with a revised name that includes the index.*
** file and return non-zero (true). If any condition is not met, return
** zero and leave the input pathname unchanged.
*/
static int isDirWithIndexFile(char **pzPath){
static const char *azIndexNames[] = {
"index.html", "index.wiki", "index.md"
};
int i;
if( file_isdir(*pzPath, ExtFILE)!=1 ) return 0;
if( sqlite3_strglob("*/", *pzPath)!=0 ) return 0;
for(i=0; i<sizeof(azIndexNames)/sizeof(azIndexNames[0]); i++){
char *zNew = mprintf("%s%s", *pzPath, azIndexNames[i]);
if( file_isfile(zNew, ExtFILE) ){
fossil_free(*pzPath);
*pzPath = zNew;
return 1;
}
fossil_free(zNew);
}
return 0;
}
/*
** WEBPAGE: ext raw-content
**
** Relay an HTTP request to secondary CGI after first checking the
** login credentials and setting auxiliary environment variables
** so that the secondary CGI can be aware of the credentials and
|
| ︙ | ︙ | |||
117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
** static content.
**
** The path after the /ext is the path to the CGI script or static file
** relative to DIR. For security, this path may not contain characters
** other than ASCII letters or digits, ".", "-", "/", and "_". If the
** "." or "-" characters are present in the path then they may not follow
** a "/".
*/
void ext_page(void){
const char *zName = P("name"); /* Path information after /ext */
char *zPath = 0; /* Complete path from extroot */
int nRoot; /* Number of bytes in the extroot name */
char *zScript = 0; /* Name of the CGI script */
int nScript = 0; /* Bytes in the CGI script name */
| > > > > > | 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
** static content.
**
** The path after the /ext is the path to the CGI script or static file
** relative to DIR. For security, this path may not contain characters
** other than ASCII letters or digits, ".", "-", "/", and "_". If the
** "." or "-" characters are present in the path then they may not follow
** a "/".
**
** If the path after /ext ends with "/" and is the name of a directory then
** that directory is searched for files named "index.html", "index.wiki",
** and "index.md" (in that order) and if found, those filenames are
** appended to the path.
*/
void ext_page(void){
const char *zName = P("name"); /* Path information after /ext */
char *zPath = 0; /* Complete path from extroot */
int nRoot; /* Number of bytes in the extroot name */
char *zScript = 0; /* Name of the CGI script */
int nScript = 0; /* Bytes in the CGI script name */
|
| ︙ | ︙ | |||
161 162 163 164 165 166 167 |
zFailReason = "???";
if( file_isdir(g.zExtRoot,ExtFILE)!=1 ){
zFailReason = "extroot is not a directory";
goto ext_not_found;
}
zPath = mprintf("%s/%s", g.zExtRoot, zName);
nRoot = (int)strlen(g.zExtRoot);
| | | 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
zFailReason = "???";
if( file_isdir(g.zExtRoot,ExtFILE)!=1 ){
zFailReason = "extroot is not a directory";
goto ext_not_found;
}
zPath = mprintf("%s/%s", g.zExtRoot, zName);
nRoot = (int)strlen(g.zExtRoot);
if( file_isfile(zPath, ExtFILE) || isDirWithIndexFile(&zPath) ){
nScript = (int)strlen(zPath);
zScript = zPath;
}else{
for(i=nRoot+1; zPath[i]; i++){
char c = zPath[i];
if( c=='/' ){
int isDir, isFile;
|
| ︙ | ︙ | |||
218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
mprintf("%T/ext/%T",g.zTop,zScript+nRoot+1));
cgi_replace_parameter("SCRIPT_DIRECTORY", file_dirname(zScript));
cgi_replace_parameter("PATH_INFO", zName + strlen(zScript+nRoot+1));
if( g.zLogin ){
cgi_replace_parameter("REMOTE_USER", g.zLogin);
cgi_set_parameter_nocopy("FOSSIL_USER", g.zLogin, 0);
}
cgi_set_parameter_nocopy("FOSSIL_REPOSITORY", g.zRepositoryName, 0);
cgi_set_parameter_nocopy("FOSSIL_URI", g.zTop, 0);
cgi_set_parameter_nocopy("FOSSIL_CAPABILITIES",
db_text("","SELECT fullcap(cap) FROM user WHERE login=%Q",
g.zLogin ? g.zLogin : "nobody"), 0);
cgi_replace_parameter("GATEWAY_INTERFACE","CGI/1.0");
for(i=0; i<sizeof(azCgiEnv)/sizeof(azCgiEnv[0]); i++){
| > | 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
mprintf("%T/ext/%T",g.zTop,zScript+nRoot+1));
cgi_replace_parameter("SCRIPT_DIRECTORY", file_dirname(zScript));
cgi_replace_parameter("PATH_INFO", zName + strlen(zScript+nRoot+1));
if( g.zLogin ){
cgi_replace_parameter("REMOTE_USER", g.zLogin);
cgi_set_parameter_nocopy("FOSSIL_USER", g.zLogin, 0);
}
cgi_set_parameter_nocopy("FOSSIL_NONCE", style_nonce(), 0);
cgi_set_parameter_nocopy("FOSSIL_REPOSITORY", g.zRepositoryName, 0);
cgi_set_parameter_nocopy("FOSSIL_URI", g.zTop, 0);
cgi_set_parameter_nocopy("FOSSIL_CAPABILITIES",
db_text("","SELECT fullcap(cap) FROM user WHERE login=%Q",
g.zLogin ? g.zLogin : "nobody"), 0);
cgi_replace_parameter("GATEWAY_INTERFACE","CGI/1.0");
for(i=0; i<sizeof(azCgiEnv)/sizeof(azCgiEnv[0]); i++){
|
| ︙ | ︙ |
| ︙ | ︙ | |||
292 293 294 295 296 297 298 |
** If eFType is ExtFile then symbolic links are followed and so this
** routine can only return PERM_EXE and PERM_REG.
**
** On windows, this routine returns only PERM_REG.
*/
int file_perm(const char *zFilename, int eFType){
#if !defined(_WIN32)
| | | 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
** If eFType is ExtFile then symbolic links are followed and so this
** routine can only return PERM_EXE and PERM_REG.
**
** On windows, this routine returns only PERM_REG.
*/
int file_perm(const char *zFilename, int eFType){
#if !defined(_WIN32)
if( !getStat(zFilename, eFType) ){
if( S_ISREG(fx.fileStat.st_mode) && ((S_IXUSR)&fx.fileStat.st_mode)!=0 )
return PERM_EXE;
else if( db_allow_symlinks() && S_ISLNK(fx.fileStat.st_mode) )
return PERM_LNK;
}
#endif
return PERM_REG;
|
| ︙ | ︙ | |||
344 345 346 347 348 349 350 351 352 353 354 355 356 357 |
rc = 1; /* It exists and is a real directory. */
}else{
rc = 2; /* It exists and is something else. */
}
free(zFN);
return rc;
}
/*
** Wrapper around the access() system call.
*/
int file_access(const char *zFilename, int flags){
int rc;
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
rc = 1; /* It exists and is a real directory. */
}else{
rc = 2; /* It exists and is something else. */
}
free(zFN);
return rc;
}
/*
** Return true (1) if zFilename seems like it seems like a valid
** repository database.
*/
int file_is_repository(const char *zFilename){
i64 sz;
sqlite3 *db = 0;
sqlite3_stmt *pStmt = 0;
int rc;
int i;
static const char *azReqTab[] = {
"blob", "delta", "rcvfrom", "user", "config"
};
if( !file_isfile(zFilename, ExtFILE) ) return 0;
sz = file_size(zFilename, ExtFILE);
if( sz<35328 ) return 0;
if( sz%512!=0 ) return 0;
rc = sqlite3_open_v2(zFilename, &db,
SQLITE_OPEN_READWRITE, 0);
if( rc!=0 ) goto not_a_repo;
for(i=0; i<count(azReqTab); i++){
if( sqlite3_table_column_metadata(db, "main", azReqTab[i],0,0,0,0,0,0) ){
goto not_a_repo;
}
}
rc = sqlite3_prepare_v2(db, "SELECT 1 FROM config WHERE name='project-code'",
-1, &pStmt, 0);
if( rc ) goto not_a_repo;
rc = sqlite3_step(pStmt);
if( rc!=SQLITE_ROW ) goto not_a_repo;
sqlite3_finalize(pStmt);
sqlite3_close(db);
return 1;
not_a_repo:
sqlite3_finalize(pStmt);
sqlite3_close(db);
return 0;
}
/*
** Wrapper around the access() system call.
*/
int file_access(const char *zFilename, int flags){
int rc;
|
| ︙ | ︙ | |||
497 498 499 500 501 502 503 504 505 506 507 508 509 510 |
out = fossil_fopen(zTo, "wb");
if( out==0 ) fossil_fatal("cannot open \"%s\" for writing", zTo);
while( (got=fread(zBuf, 1, sizeof(zBuf), in))>0 ){
fwrite(zBuf, 1, got, out);
}
fclose(in);
fclose(out);
}
/*
** COMMAND: test-file-copy
**
** Usage: %fossil test-file-copy SOURCE DESTINATION
**
| > | 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 |
out = fossil_fopen(zTo, "wb");
if( out==0 ) fossil_fatal("cannot open \"%s\" for writing", zTo);
while( (got=fread(zBuf, 1, sizeof(zBuf), in))>0 ){
fwrite(zBuf, 1, got, out);
}
fclose(in);
fclose(out);
if( file_isexe(zFrom, ExtFILE) ) file_setexe(zTo, 1);
}
/*
** COMMAND: test-file-copy
**
** Usage: %fossil test-file-copy SOURCE DESTINATION
**
|
| ︙ | ︙ | |||
874 875 876 877 878 879 880 881 882 883 884 885 886 887 |
if( z[i+2]=='.' && (z[i+3]=='/' || z[i+3]==0) ) return 0;
}
}
}
if( z[i-1]=='/' ) return 0;
return 1;
}
/*
** If the last component of the pathname in z[0]..z[j-1] is something
** other than ".." then back it out and return true. If the last
** component is empty or if it is ".." then return false.
*/
static int backup_dir(const char *z, int *pJ){
| > > > > > > > > > > > > > > > > > | 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 |
if( z[i+2]=='.' && (z[i+3]=='/' || z[i+3]==0) ) return 0;
}
}
}
if( z[i-1]=='/' ) return 0;
return 1;
}
int file_is_simple_pathname_nonstrict(const char *z){
unsigned char c = (unsigned char) z[0];
if( c=='/' || c==0 ) return 0;
if( c=='.' ){
if( z[1]=='/' || z[1]==0 ) return 0;
if( z[1]=='.' && (z[2]=='/' || z[2]==0) ) return 0;
}
while( (z = strchr(z+1, '/'))!=0 ){
if( z[1]=='/' ) return 0;
if( z[1]==0 ) return 0;
if( z[1]=='.' ){
if( z[2]=='/' || z[2]==0 ) return 0;
if( z[2]=='.' && (z[3]=='/' || z[3]==0) ) return 0;
}
}
return 1;
}
/*
** If the last component of the pathname in z[0]..z[j-1] is something
** other than ".." then back it out and return true. If the last
** component is empty or if it is ".." then return false.
*/
static int backup_dir(const char *z, int *pJ){
|
| ︙ | ︙ | |||
907 908 909 910 911 912 913 914 915 916 917 918 919 920 |
** If the slash parameter is non-zero, the trailing slash, if any,
** is retained.
*/
int file_simplify_name(char *z, int n, int slash){
int i = 1, j;
assert( z!=0 );
if( n<0 ) n = strlen(z);
/* On windows and cygwin convert all \ characters to /
* and remove extended path prefix if present */
#if defined(_WIN32) || defined(__CYGWIN__)
for(j=0; j<n; j++){
if( z[j]=='\\' ) z[j] = '/';
}
| > | 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 |
** If the slash parameter is non-zero, the trailing slash, if any,
** is retained.
*/
int file_simplify_name(char *z, int n, int slash){
int i = 1, j;
assert( z!=0 );
if( n<0 ) n = strlen(z);
if( n==0 ) return 0;
/* On windows and cygwin convert all \ characters to /
* and remove extended path prefix if present */
#if defined(_WIN32) || defined(__CYGWIN__)
for(j=0; j<n; j++){
if( z[j]=='\\' ) z[j] = '/';
}
|
| ︙ | ︙ | |||
1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 |
zOut[0] = fossil_toupper(zOut[0]);
}
}
#endif
blob_resize(pOut, file_simplify_name(blob_buffer(pOut),
blob_size(pOut), slash));
}
/*
** Emits the effective or raw stat() information for the specified
** file or directory, optionally preserving the trailing slash and
** resetting the cached stat() information.
*/
static void emitFileStat(
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 |
zOut[0] = fossil_toupper(zOut[0]);
}
}
#endif
blob_resize(pOut, file_simplify_name(blob_buffer(pOut),
blob_size(pOut), slash));
}
/*
** The input is the name of an executable, such as one might
** type on a command-line. This routine resolves that name into
** a full pathname. The result is obtained from fossil_malloc()
** and should be freed by the caller.
**
** This routine only works on unix. On Windows, simply return
** a copy of the input.
*/
char *file_fullexename(const char *zCmd){
#ifdef _WIN32
return fossil_strdup(zCmd);
#else
char *zPath;
char *z;
if( zCmd[0]=='/' ){
return fossil_strdup(zCmd);
}
if( strchr(zCmd,'/')!=0 ){
Blob out = BLOB_INITIALIZER;
file_canonical_name(zCmd, &out, 0);
z = fossil_strdup(blob_str(&out));
blob_reset(&out);
return z;
}
zPath = fossil_getenv("PATH");
while( zPath && zPath[0] ){
int n;
char *zColon;
zColon = strchr(zPath, ':');
n = zColon ? (int)(zColon-zPath) : (int)strlen(zPath);
z = mprintf("%.*s/%s", n, zPath, zCmd);
if( file_isexe(z, ExtFILE) ){
return z;
}
fossil_free(z);
if( zColon==0 ) break;
zPath = zColon+1;
}
return fossil_strdup(zCmd);
#endif
}
/*
** COMMAND: test-which
**
** Usage: %fossil test-which ARGS...
**
** For each argument, search the PATH for the executable with the name
** and print its full pathname.
*/
void test_which_cmd(void){
int i;
for(i=2; i<g.argc; i++){
char *z = file_fullexename(g.argv[i]);
fossil_print("%z\n", z);
}
}
/*
** Emits the effective or raw stat() information for the specified
** file or directory, optionally preserving the trailing slash and
** resetting the cached stat() information.
*/
static void emitFileStat(
|
| ︙ | ︙ | |||
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 |
fossil_print("[%s] -> [%s]\n", zPath, blob_buffer(&x));
blob_reset(&x);
memset(&testFileStat, 0, sizeof(struct fossilStat));
rc = fossil_stat(zPath, &testFileStat, 0);
fossil_print(" stat_rc = %d\n", rc);
sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", testFileStat.st_size);
fossil_print(" stat_size = %s\n", zBuf);
z = db_text(0, "SELECT datetime(%lld, 'unixepoch')", testFileStat.st_mtime);
sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld (%s)", testFileStat.st_mtime, z);
fossil_free(z);
fossil_print(" stat_mtime = %s\n", zBuf);
fossil_print(" stat_mode = 0%o\n", testFileStat.st_mode);
memset(&testFileStat, 0, sizeof(struct fossilStat));
rc = fossil_stat(zPath, &testFileStat, 1);
| > | 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 |
fossil_print("[%s] -> [%s]\n", zPath, blob_buffer(&x));
blob_reset(&x);
memset(&testFileStat, 0, sizeof(struct fossilStat));
rc = fossil_stat(zPath, &testFileStat, 0);
fossil_print(" stat_rc = %d\n", rc);
sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", testFileStat.st_size);
fossil_print(" stat_size = %s\n", zBuf);
if( g.db==0 ) sqlite3_open(":memory:", &g.db);
z = db_text(0, "SELECT datetime(%lld, 'unixepoch')", testFileStat.st_mtime);
sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld (%s)", testFileStat.st_mtime, z);
fossil_free(z);
fossil_print(" stat_mtime = %s\n", zBuf);
fossil_print(" stat_mode = 0%o\n", testFileStat.st_mode);
memset(&testFileStat, 0, sizeof(struct fossilStat));
rc = fossil_stat(zPath, &testFileStat, 1);
|
| ︙ | ︙ | |||
1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 |
fossil_print(" file_mtime(RepoFILE) = %s\n", zBuf);
fossil_print(" file_mode(RepoFILE) = 0%o\n", file_mode(zPath,RepoFILE));
fossil_print(" file_isfile(RepoFILE) = %d\n", file_isfile(zPath,RepoFILE));
fossil_print(" file_isfile_or_link = %d\n", file_isfile_or_link(zPath));
fossil_print(" file_islink = %d\n", file_islink(zPath));
fossil_print(" file_isexe(RepoFILE) = %d\n", file_isexe(zPath,RepoFILE));
fossil_print(" file_isdir(RepoFILE) = %d\n", file_isdir(zPath,RepoFILE));
if( reset ) resetStat();
}
/*
** COMMAND: test-file-environment
**
** Usage: %fossil test-file-environment FILENAME...
| > | 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 |
fossil_print(" file_mtime(RepoFILE) = %s\n", zBuf);
fossil_print(" file_mode(RepoFILE) = 0%o\n", file_mode(zPath,RepoFILE));
fossil_print(" file_isfile(RepoFILE) = %d\n", file_isfile(zPath,RepoFILE));
fossil_print(" file_isfile_or_link = %d\n", file_isfile_or_link(zPath));
fossil_print(" file_islink = %d\n", file_islink(zPath));
fossil_print(" file_isexe(RepoFILE) = %d\n", file_isexe(zPath,RepoFILE));
fossil_print(" file_isdir(RepoFILE) = %d\n", file_isdir(zPath,RepoFILE));
fossil_print(" file_is_repository = %d\n", file_is_repository(zPath));
if( reset ) resetStat();
}
/*
** COMMAND: test-file-environment
**
** Usage: %fossil test-file-environment FILENAME...
|
| ︙ | ︙ | |||
1172 1173 1174 1175 1176 1177 1178 |
int i;
int slashFlag = find_option("slash",0,0)!=0;
int resetFlag = find_option("reset",0,0)!=0;
const char *zAllow = find_option("allow-symlinks",0,1);
if( find_option("open-config", 0, 0)!=0 ){
Th_OpenConfig(1);
}
| | | 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 |
int i;
int slashFlag = find_option("slash",0,0)!=0;
int resetFlag = find_option("reset",0,0)!=0;
const char *zAllow = find_option("allow-symlinks",0,1);
if( find_option("open-config", 0, 0)!=0 ){
Th_OpenConfig(1);
}
db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
fossil_print("filenames_are_case_sensitive() = %d\n",
filenames_are_case_sensitive());
fossil_print("db_allow_symlinks_by_default() = %d\n",
db_allow_symlinks_by_default());
if( zAllow ){
g.allowSymlinks = !is_false(zAllow);
}
|
| ︙ | ︙ | |||
1766 1767 1768 1769 1770 1771 1772 |
}
}else{
rc = 1;
}
return rc;
#else
extern char **environ;
| | | 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 |
}
}else{
rc = 1;
}
return rc;
#else
extern char **environ;
environ[0] = 0;
return 0;
#endif
}
/*
** Like fopen() but always takes a UTF8 argument.
**
|
| ︙ | ︙ | |||
1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 |
fossil_path_free(uName);
fossil_unicode_free(uMode);
#else
FILE *f = fopen(zName, zMode);
#endif
return f;
}
/*
** Return non-NULL if zFilename contains pathname elements that
** are reserved on Windows. The returned string is the disallowed
** path element.
*/
const char *file_is_win_reserved(const char *zPath){
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 |
fossil_path_free(uName);
fossil_unicode_free(uMode);
#else
FILE *f = fopen(zName, zMode);
#endif
return f;
}
/*
** Works like fclose() except that:
**
** 1) is a no-op if f is 0 or if it is stdin.
**
** 2) If f is one of (stdout, stderr), it is flushed but not closed.
*/
void fossil_fclose(FILE *f){
if(f!=0){
if(stdout==f || stderr==f){
fflush(f);
}else if(stdin!=f){
fclose(f);
}
}
}
/*
** Works like fopen(zName,"wb") except that:
**
** 1) If zName is "-", the stdout handle is returned.
**
** 2) Else file_mkfolder() is used to create all directories
** which lead up to the file before opening it.
**
** 3) It fails fatally if the file cannot be opened.
*/
FILE *fossil_fopen_for_output(const char *zFilename){
if(zFilename[0]=='-' && zFilename[1]==0){
return stdout;
}else{
FILE * p;
file_mkfolder(zFilename, ExtFILE, 1, 0);
p = fossil_fopen(zFilename, "wb");
if( p==0 ){
#if _WIN32
const char *zReserved = file_is_win_reserved(zFilename);
if( zReserved ){
fossil_fatal("cannot open \"%s\" because \"%s\" is "
"a reserved name on Windows", zFilename,
zReserved);
}
#endif
fossil_fatal("unable to open file \"%s\" for writing",
zFilename);
}
return p;
}
}
/*
** Return non-NULL if zFilename contains pathname elements that
** are reserved on Windows. The returned string is the disallowed
** path element.
*/
const char *file_is_win_reserved(const char *zPath){
|
| ︙ | ︙ |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 |
/*
** Copyright (c) 2020 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** 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.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code for the /fileedit page and related bits.
*/
#include "config.h"
#include "fileedit.h"
#include <assert.h>
#include <stdarg.h>
/*
** State for the "mini-checkin" infrastructure, which enables the
** ability to commit changes to a single file without a checkout
** db, e.g. for use via an HTTP request.
**
** Use CheckinMiniInfo_init() to cleanly initialize one to a known
** valid/empty default state.
**
** Memory for all non-const pointer members is owned by the
** CheckinMiniInfo instance, unless explicitly noted otherwise, and is
** freed by CheckinMiniInfo_cleanup(). Similarly, each instance owns
** any memory for its own Blob members, but NOT for its pointers to
** blobs.
*/
struct CheckinMiniInfo {
Manifest * pParent; /* parent checkin. Memory is owned by this
object. */
char *zParentUuid; /* Full UUID of pParent */
char *zFilename; /* Name of single file to commit. Must be
relative to the top of the repo. */
Blob fileContent; /* Content of file referred to by zFilename. */
Blob fileHash; /* Hash of this->fileContent, using the repo's
preferred hash method. */
Blob comment; /* Check-in comment text */
char *zCommentMimetype; /* Mimetype of comment. May be NULL */
char *zUser; /* User name */
char *zDate; /* Optionally force this date string (anything
supported by date_in_standard_format()).
Maybe be NULL. */
Blob *pMfOut; /* If not NULL, checkin_mini() will write a
copy of the generated manifest here. This
memory is NOT owned by CheckinMiniInfo. */
int filePerm; /* Permissions (via file_perm()) of the input
file. We need to store this before calling
checkin_mini() because the real input file
name may differ from the repo-centric
this->zFilename, and checkin_mini() requires
the permissions of the original file. For
web commits, set this to PERM_REG or (when
editing executable scripts) PERM_EXE before
calling checkin_mini(). */
int flags; /* Bitmask of fossil_cimini_flags. */
};
typedef struct CheckinMiniInfo CheckinMiniInfo;
/*
** CheckinMiniInfo::flags values.
*/
enum fossil_cimini_flags {
/*
** Must have a value of 0. All other flags have unspecified values.
*/
CIMINI_NONE = 0,
/*
** Tells checkin_mini() to use dry-run mode.
*/
CIMINI_DRY_RUN = 1,
/*
** Tells checkin_mini() to allow forking from a non-leaf commit.
*/
CIMINI_ALLOW_FORK = 1<<1,
/*
** Tells checkin_mini() to dump its generated manifest to stdout.
*/
CIMINI_DUMP_MANIFEST = 1<<2,
/*
** By default, content containing what appears to be a merge conflict
** marker is not permitted. This flag relaxes that requirement.
*/
CIMINI_ALLOW_MERGE_MARKER = 1<<3,
/*
** By default mini-checkins are not allowed to be "older"
** than their parent. i.e. they may not have a timestamp
** which predates their parent. This flag bypasses that
** check.
*/
CIMINI_ALLOW_OLDER = 1<<4,
/*
** Indicates that the content of the newly-checked-in file is
** converted, if needed, to use the same EOL style as the previous
** version of that file. Only the in-memory/in-repo copies are
** affected, not the original file (if any).
*/
CIMINI_CONVERT_EOL_INHERIT = 1<<5,
/*
** Indicates that the input's EOLs should be converted to Unix-style.
*/
CIMINI_CONVERT_EOL_UNIX = 1<<6,
/*
** Indicates that the input's EOLs should be converted to Windows-style.
*/
CIMINI_CONVERT_EOL_WINDOWS = 1<<7,
/*
** A hint to checkin_mini() to "prefer" creation of a delta manifest.
** It may decide not to for various reasons.
*/
CIMINI_PREFER_DELTA = 1<<8,
/*
** A "stronger hint" to checkin_mini() to prefer creation of a delta
** manifest if it at all can. It will decide not to only if creation
** of a delta is not a realistic option or if it's forbitted by the
** forbid-delta-manifests repo config option. For this to work, it
** must be set together with the CIMINI_PREFER_DELTA flag, but the two
** cannot be combined in this enum.
**
** This option is ONLY INTENDED FOR TESTING, used in bypassing
** heuristics which may otherwise disable generation of a delta on the
** grounds of efficiency (e.g. not generating a delta if the parent
** non-delta only has a few F-cards).
*/
CIMINI_STRONGLY_PREFER_DELTA = 1<<9,
/*
** Tells checkin_mini() to permit the addition of a new file. Normally
** this is disabled because there are hypothetically many cases where
** it could cause the inadvertent addition of a new file when an
** update to an existing was intended, as a side-effect of name-case
** differences.
*/
CIMINI_ALLOW_NEW_FILE = 1<<10
};
/*
** Initializes p to a known-valid default state.
*/
static void CheckinMiniInfo_init( CheckinMiniInfo * p ){
memset(p, 0, sizeof(CheckinMiniInfo));
p->flags = CIMINI_NONE;
p->filePerm = -1;
p->comment = p->fileContent = p->fileHash = empty_blob;
}
/*
** Frees all memory owned by p, but does not free p.
*/
static void CheckinMiniInfo_cleanup( CheckinMiniInfo * p ){
blob_reset(&p->comment);
blob_reset(&p->fileContent);
blob_reset(&p->fileHash);
if(p->pParent){
manifest_destroy(p->pParent);
}
fossil_free(p->zFilename);
fossil_free(p->zDate);
fossil_free(p->zParentUuid);
fossil_free(p->zCommentMimetype);
fossil_free(p->zUser);
CheckinMiniInfo_init(p);
}
/*
** Internal helper which returns an F-card perms string suitable for
** writing as-is into a manifest. If it's not empty, it includes a
** leading space to separate it from the F-card's hash field.
*/
static const char * mfile_permint_mstring(int perm){
switch(perm){
case PERM_EXE: return " x";
case PERM_LNK: return " l";
default: return "";
}
}
/*
** Given a ManifestFile permission string (or NULL), it returns one of
** PERM_REG, PERM_EXE, or PERM_LNK.
*/
static int mfile_permstr_int(const char *zPerm){
if(!zPerm || !*zPerm) return PERM_REG;
else if(strstr(zPerm,"x")) return PERM_EXE;
else if(strstr(zPerm,"l")) return PERM_LNK;
else return PERM_REG/*???*/;
}
/*
** Internal helper for checkin_mini() and friends. Appends an F-card
** for p to pOut.
*/
static void checkin_mini_append_fcard(Blob *pOut,
const ManifestFile *p){
if(p->zUuid){
assert(*p->zUuid);
blob_appendf(pOut, "F %F %s%s", p->zName,
p->zUuid,
mfile_permint_mstring(manifest_file_mperm(p)));
if(p->zPrior){
assert(*p->zPrior);
blob_appendf(pOut, " %F\n", p->zPrior);
}else{
blob_append(pOut, "\n", 1);
}
}else{
/* File was removed from parent delta. */
blob_appendf(pOut, "F %F\n", p->zName);
}
}
/*
** Handles the F-card parts for create_manifest_mini().
**
** If asDelta is true, F-cards will be handled as for a delta
** manifest, and the caller MUST have added a B-card to pOut before
** calling this.
**
** Returns 1 on success, 0 on error, and writes any error message to
** pErr (if it's not NULL). The only non-immediately-fatal/panic error
** is if pCI->filePerm is PERM_LNK or pCI would update a PERM_LNK
** in-repo file.
*/
static int create_manifest_mini_fcards( Blob * pOut,
CheckinMiniInfo * pCI,
int asDelta,
Blob * pErr){
int wroteThisCard = 0;
const ManifestFile * pFile;
int (*fncmp)(char const *, char const *) = /* filename comparator */
filenames_are_case_sensitive()
? fossil_strcmp
: fossil_stricmp;
#define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0
#define write_this_card(NAME) \
blob_appendf(pOut, "F %F %b%s\n", (NAME), &pCI->fileHash, \
mfile_permint_mstring(pCI->filePerm)); \
wroteThisCard = 1
assert(pCI->filePerm!=PERM_LNK && "This should have been validated before.");
assert(pCI->filePerm==PERM_REG || pCI->filePerm==PERM_EXE);
if(PERM_LNK==pCI->filePerm){
goto err_no_symlink;
}
manifest_file_rewind(pCI->pParent);
if(asDelta!=0 && (pCI->pParent->zBaseline==0
|| pCI->pParent->nFile==0)){
/* Parent is a baseline or a delta with no F-cards, so this is
** the simplest case: create a delta with a single F-card.
*/
pFile = manifest_file_find(pCI->pParent, pCI->zFilename);
if(pFile!=0 && manifest_file_mperm(pFile)==PERM_LNK){
goto err_no_symlink;
}
write_this_card(pFile ? pFile->zName : pCI->zFilename);
return 1;
}
while(1){
int cmp;
if(asDelta==0){
pFile = manifest_file_next(pCI->pParent, 0);
}else{
/* Parent is a delta manifest with F-cards. Traversal of delta
** manifest file entries is normally done via
** manifest_file_next(), which takes into account the
** differences between the delta and its parent and returns
** F-cards from both. Each successive delta from the same
** baseline includes all F-card changes from the previous
** deltas, so we instead clone the parent's F-cards except for
** the one (if any) which matches the new file.
*/
pFile = pCI->pParent->iFile < pCI->pParent->nFile
? &pCI->pParent->aFile[pCI->pParent->iFile++]
: 0;
}
if(0==pFile) break;
cmp = fncmp(pFile->zName, pCI->zFilename);
if(cmp<0){
checkin_mini_append_fcard(pOut,pFile);
}else{
if(cmp==0 || 0==wroteThisCard){
assert(0==wroteThisCard);
if(PERM_LNK==manifest_file_mperm(pFile)){
goto err_no_symlink;
}
write_this_card(cmp==0 ? pFile->zName : pCI->zFilename);
}
if(cmp>0){
assert(wroteThisCard!=0);
checkin_mini_append_fcard(pOut,pFile);
}
}
}
if(wroteThisCard==0){
write_this_card(pCI->zFilename);
}
return 1;
err_no_symlink:
mf_err((pErr,"Cannot commit or overwrite symlinks "
"via mini-checkin."));
return 0;
#undef write_this_card
#undef mf_err
}
/*
** Creates a manifest file, written to pOut, from the state in the
** fully-populated and semantically valid pCI argument. pCI is not
** *semantically* modified by this routine but cannot be const because
** blob_str() may need to NUL-terminate any given blob.
**
** Returns true on success. On error, returns 0 and, if pErr is not
** NULL, writes an error message there.
**
** Intended only to be called via checkin_mini() or routines which
** have already completely vetted pCI for semantic validity.
*/
static int create_manifest_mini( Blob * pOut, CheckinMiniInfo * pCI,
Blob * pErr){
Blob zCard = empty_blob; /* Z-card checksum */
int asDelta = 0;
#define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0
assert(blob_str(&pCI->fileHash));
assert(pCI->pParent);
assert(pCI->zFilename);
assert(pCI->zUser);
assert(pCI->zDate);
/* Potential TODOs include...
**
** - Maybe add support for tags. Those can be edited via /info page,
** and feel like YAGNI/feature creep for this purpose.
*/
blob_zero(pOut);
manifest_file_rewind(pCI->pParent) /* force load of baseline */;
/* Determine whether we want to create a delta manifest... */
if((CIMINI_PREFER_DELTA & pCI->flags)
&& ((CIMINI_STRONGLY_PREFER_DELTA & pCI->flags)
|| (pCI->pParent->pBaseline
? pCI->pParent->pBaseline
: pCI->pParent)->nFile > 15
/* 15 is arbitrary: don't create a delta when there is only a
** tiny gain for doing so. That heuristic is not *quite*
** right, in that when we're deriving from another delta, we
** really should compare the F-card count between it and its
** baseline, and create a delta if the baseline has (say)
** twice or more as many F-cards as the previous delta. */)
&& !db_get_boolean("forbid-delta-manifests",0)
){
asDelta = 1;
blob_appendf(pOut, "B %s\n",
pCI->pParent->zBaseline
? pCI->pParent->zBaseline
: pCI->zParentUuid);
}
blob_reserve(pOut, 1024 *
(asDelta ? 2 : pCI->pParent->nFile/11+1
/* In the fossil core repo, each 12-ish F-cards (on
** average) take up roughly 1kb */));
if(blob_size(&pCI->comment)!=0){
blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment));
}else{
blob_append(pOut, "C (no\\scomment)\n", 16);
}
blob_appendf(pOut, "D %s\n", pCI->zDate);
if(create_manifest_mini_fcards(pOut,pCI,asDelta,pErr)==0){
return 0;
}
if(pCI->zCommentMimetype!=0 && pCI->zCommentMimetype[0]!=0){
blob_appendf(pOut, "N %F\n", pCI->zCommentMimetype);
}
blob_appendf(pOut, "P %s\n", pCI->zParentUuid);
blob_appendf(pOut, "U %F\n", pCI->zUser);
md5sum_blob(pOut, &zCard);
blob_appendf(pOut, "Z %b\n", &zCard);
blob_reset(&zCard);
return 1;
#undef mf_err
}
/*
** A so-called "single-file/mini/web checkin" is a slimmed-down form
** of the checkin command which accepts only a single file and is
** intended to accept edits to a file via the web interface or from
** the CLI from outside of a checkout.
**
** Being fully non-interactive is a requirement for this function,
** thus it cannot perform autosync or similar activities (which
** includes checking for repo locks).
**
** This routine uses the state from the given fully-populated pCI
** argument to add pCI->fileContent to the database, and create and
** save a manifest for that change. Ownership of pCI and its contents
** are unchanged.
**
** This function may may modify pCI as follows:
**
** - If one of Manifest pCI->pParent or pCI->zParentUuid are NULL,
** then the other will be assigned based on its counterpart. Both
** may not be NULL.
**
** - pCI->zDate is normalized to/replaced with a valid date/time
** string. If its original value cannot be validated then
** this function fails. If pCI->zDate is NULL, the current time
** is used.
**
** - If the CIMINI_CONVERT_EOL_INHERIT flag is set,
** pCI->fileContent appears to be plain text, and its line-ending
** style differs from its previous version, it is converted to the
** same EOL style as the previous version. If this is done, the
** pCI->fileHash is re-computed. Note that only pCI->fileContent,
** not the original file, is affected by the conversion.
**
** - Else if one of the CIMINI_CONVERT_EOL_WINDOWS or
** CIMINI_CONVERT_EOL_UNIX flags are set, pCI->fileContent is
** converted, if needed, to the corresponding EOL style.
**
** - If EOL conversion takes place, pCI->fileHash is re-calculated.
**
** - If pCI->fileHash is empty, this routine populates it with the
** repository's preferred hash algorithm (after any EOL conversion).
**
** - pCI->comment may be converted to Unix-style newlines.
**
** pCI's ownership is not modified.
**
** This function validates pCI's state and fails if any validation
** fails.
**
** On error, returns false (0) and, if pErr is not NULL, writes a
** diagnostic message there.
**
** Returns true on success. If pRid is not NULL, the RID of the
** resulting manifest is written to *pRid.
**
** The checkin process is largely influenced by pCI->flags, and that
** must be populated before calling this. See the fossil_cimini_flags
** enum for the docs for each flag.
*/
static int checkin_mini(CheckinMiniInfo * pCI, int *pRid, Blob * pErr){
Blob mf = empty_blob; /* output manifest */
int rid = 0, frid = 0; /* various RIDs */
int isPrivate; /* whether this is private content
or not */
ManifestFile * zFilePrev; /* file entry from pCI->pParent */
int prevFRid = 0; /* RID of file's prev. version */
#define ci_err(EXPR) if(pErr!=0){blob_appendf EXPR;} goto ci_error
db_begin_transaction();
if(pCI->pParent==0 && pCI->zParentUuid==0){
ci_err((pErr, "Cannot determine parent version."));
}
else if(pCI->pParent==0){
pCI->pParent = manifest_get_by_name(pCI->zParentUuid, 0);
if(pCI->pParent==0){
ci_err((pErr,"Cannot load manifest for [%S].", pCI->zParentUuid));
}
}else if(pCI->zParentUuid==0){
pCI->zParentUuid = rid_to_uuid(pCI->pParent->rid);
assert(pCI->zParentUuid);
}
assert(pCI->pParent->rid>0);
if(leaf_is_closed(pCI->pParent->rid)){
ci_err((pErr,"Cannot commit to a closed leaf."));
/* Remember that in order to override this we'd also need to
** cancel TAG_CLOSED on pCI->pParent. There would seem to be no
** reason we can't do that via the generated manifest, but the
** commit command does not offer that option, so mini-checkin
** probably shouldn't, either.
*/
}
if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){
ci_err((pErr,"No such user: %s", pCI->zUser));
}
if(!(CIMINI_ALLOW_FORK & pCI->flags)
&& !is_a_leaf(pCI->pParent->rid)){
ci_err((pErr,"Parent [%S] is not a leaf and forking is disabled.",
pCI->zParentUuid));
}
if(!(CIMINI_ALLOW_MERGE_MARKER & pCI->flags)
&& contains_merge_marker(&pCI->fileContent)){
ci_err((pErr,"Content appears to contain a merge conflict marker."));
}
if(!file_is_simple_pathname(pCI->zFilename, 1)){
ci_err((pErr,"Invalid filename for use in a repository: %s",
pCI->zFilename));
}
if(!(CIMINI_ALLOW_OLDER & pCI->flags)
&& !checkin_is_younger(pCI->pParent->rid, pCI->zDate)){
ci_err((pErr,"Checkin time (%s) may not be older "
"than its parent (%z).",
pCI->zDate,
db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%lf)",
pCI->pParent->rDate)
));
}
{
/*
** Normalize the timestamp. We don't use date_in_standard_format()
** because that has side-effects we don't want to trigger here.
*/
char * zDVal = db_text(
0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)",
pCI->zDate ? pCI->zDate : "now");
if(zDVal==0 || zDVal[0]==0){
fossil_free(zDVal);
ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate));
}
fossil_free(pCI->zDate);
pCI->zDate = zDVal;
}
{ /* Confirm that only one EOL policy is in place. */
int n = 0;
if(CIMINI_CONVERT_EOL_INHERIT & pCI->flags) ++n;
if(CIMINI_CONVERT_EOL_UNIX & pCI->flags) ++n;
if(CIMINI_CONVERT_EOL_WINDOWS & pCI->flags) ++n;
if(n>1){
ci_err((pErr,"More than 1 EOL conversion policy was specified."));
}
}
/* Potential TODOs include:
**
** - Commit allows an empty checkin only with a flag, but we
** currently disallow an empty checkin entirely. Conform with
** commit?
**
** Non-TODOs:
**
** - Check for a commit lock would require auto-sync, which this
** code cannot do if it's going to be run via a web page.
*/
/*
** Confirm that pCI->zFilename can be found in pCI->pParent. If
** not, fail unless the CIMINI_ALLOW_NEW_FILE flag is set. This is
** admittedly an artificial limitation, not strictly necessary. We
** do it to hopefully reduce the chance of an "oops" where file
** X/Y/z gets committed as X/Y/Z or X/y/z due to a typo or
** case-sensitivity mismatch between the user/repo/filesystem, or
** some such.
*/
manifest_file_rewind(pCI->pParent);
zFilePrev = manifest_file_find(pCI->pParent, pCI->zFilename);
if(!(CIMINI_ALLOW_NEW_FILE & pCI->flags)
&& (!zFilePrev
|| !zFilePrev->zUuid/*was removed from parent delta manifest*/)
){
ci_err((pErr,"File [%s] not found in manifest [%S]. "
"Adding new files is currently not permitted.",
pCI->zFilename, pCI->zParentUuid));
}else if(zFilePrev
&& manifest_file_mperm(zFilePrev)==PERM_LNK){
ci_err((pErr,"Cannot save a symlink via a mini-checkin."));
}
if(zFilePrev){
prevFRid = fast_uuid_to_rid(zFilePrev->zUuid);
}
if(((CIMINI_CONVERT_EOL_INHERIT & pCI->flags)
|| (CIMINI_CONVERT_EOL_UNIX & pCI->flags)
|| (CIMINI_CONVERT_EOL_WINDOWS & pCI->flags))
&& blob_size(&pCI->fileContent)>0
){
/* Convert to the requested EOL style. Note that this inherently
** runs a risk of breaking content, e.g. string literals which
** contain embedded newlines. Note that HTML5 specifies that
** form-submitted TEXTAREA content gets normalized to CRLF-style:
**
** https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element
*/
const int pseudoBinary = LOOK_LONG | LOOK_NUL;
const int lookFlags = LOOK_CRLF | LOOK_LONE_LF | pseudoBinary;
const int lookNew = looks_like_utf8( &pCI->fileContent, lookFlags );
if(!(pseudoBinary & lookNew)){
int rehash = 0;
/*fossil_print("lookNew=%08x\n",lookNew);*/
if(CIMINI_CONVERT_EOL_INHERIT & pCI->flags){
Blob contentPrev = empty_blob;
int lookOrig, nOrig;
content_get(prevFRid, &contentPrev);
lookOrig = looks_like_utf8(&contentPrev, lookFlags);
nOrig = blob_size(&contentPrev);
blob_reset(&contentPrev);
/*fossil_print("lookOrig=%08x\n",lookOrig);*/
if(nOrig>0 && lookOrig!=lookNew){
/* If there is a newline-style mismatch, adjust the new
** content version to the previous style, then re-hash the
** content. Note that this means that what we insert is NOT
** what's in the filesystem.
*/
if(!(lookOrig & LOOK_CRLF) && (lookNew & LOOK_CRLF)){
/* Old has Unix-style, new has Windows-style. */
blob_to_lf_only(&pCI->fileContent);
rehash = 1;
}else if((lookOrig & LOOK_CRLF) && !(lookNew & LOOK_CRLF)){
/* Old has Windows-style, new has Unix-style. */
blob_add_cr(&pCI->fileContent);
rehash = 1;
}
}
}else{
const int oldSize = blob_size(&pCI->fileContent);
if(CIMINI_CONVERT_EOL_UNIX & pCI->flags){
if(LOOK_CRLF & lookNew){
blob_to_lf_only(&pCI->fileContent);
}
}else{
assert(CIMINI_CONVERT_EOL_WINDOWS & pCI->flags);
if(!(LOOK_CRLF & lookNew)){
blob_add_cr(&pCI->fileContent);
}
}
if(blob_size(&pCI->fileContent)!=oldSize){
rehash = 1;
}
}
if(rehash!=0){
hname_hash(&pCI->fileContent, 0, &pCI->fileHash);
}
}
}/* end EOL conversion */
if(blob_size(&pCI->fileHash)==0){
/* Hash the content if it's not done already... */
hname_hash(&pCI->fileContent, 0, &pCI->fileHash);
assert(blob_size(&pCI->fileHash)>0);
}
if(zFilePrev){
/* Has this file been changed since its previous commit? Note
** that we have to delay this check until after the potentially
** expensive EOL conversion. */
assert(blob_size(&pCI->fileHash));
if(0==fossil_strcmp(zFilePrev->zUuid, blob_str(&pCI->fileHash))
&& manifest_file_mperm(zFilePrev)==pCI->filePerm){
ci_err((pErr,"File is unchanged. Not committing."));
}
}
#if 1
/* Do we really want to normalize comment EOLs? Web-posting will
** submit them in CRLF or LF format, depending on how exactly the
** content is submitted (FORM (CRLF) or textarea-to-POST (LF, at
** least in theory)). */
blob_to_lf_only(&pCI->comment);
#endif
/* Create, save, deltify, and crosslink the manifest... */
if(create_manifest_mini(&mf, pCI, pErr)==0){
return 0;
}
isPrivate = content_is_private(pCI->pParent->rid);
rid = content_put_ex(&mf, 0, 0, 0, isPrivate);
if(pCI->flags & CIMINI_DUMP_MANIFEST){
fossil_print("%b", &mf);
}
if(pCI->pMfOut!=0){
/* Cross-linking clears mf, so we have to copy it,
** instead of taking over its memory. */
blob_reset(pCI->pMfOut);
blob_append(pCI->pMfOut, blob_buffer(&mf), blob_size(&mf));
}
content_deltify(rid, &pCI->pParent->rid, 1, 0);
manifest_crosslink(rid, &mf, 0);
blob_reset(&mf);
/* Save and deltify the file content... */
frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash),
0, 0, isPrivate);
if(zFilePrev!=0){
assert(prevFRid>0);
content_deltify(frid, &prevFRid, 1, 0);
}
db_end_transaction((CIMINI_DRY_RUN & pCI->flags) ? 1 : 0);
if(pRid!=0){
*pRid = rid;
}
return 1;
ci_error:
assert(db_transaction_nesting_depth()>0);
db_end_transaction(1);
return 0;
#undef ci_err
}
/*
** COMMAND: test-ci-mini
**
** This is an on-going experiment, subject to change or removal at
** any time.
**
** Usage: %fossil test-ci-mini ?OPTIONS? FILENAME
**
** where FILENAME is a repo-relative name as it would appear in the
** vfile table.
**
** Options:
**
** --repository|-R REPO The repository file to commit to.
** --as FILENAME The repository-side name of the input
** file, relative to the top of the
** repository. Default is the same as the
** input file name.
** --comment|-m COMMENT Required checkin comment.
** --comment-file|-M FILE Reads checkin comment from the given file.
** --revision|-r VERSION Commit from this version. Default is
** the checkout version (if available) or
** trunk (if used without a checkout).
** --allow-fork Allows the commit to be made against a
** non-leaf parent. Note that no autosync
** is performed beforehand.
** --allow-merge-conflict Allows checkin of a file even if it
** appears to contain a fossil merge conflict
** marker.
** --user-override USER USER to use instead of the current
** default.
** --date-override DATETIME DATE to use instead of 'now'.
** --allow-older Allow a commit to be older than its
** ancestor.
** --convert-eol-inherit Convert EOL style of the checkin to match
** the previous version's content.
** --convert-eol-unix Convert the EOL style to Unix.
** --convert-eol-windows Convert the EOL style to Windows.
** (only one of the --convert-eol-X options may be used and they only
** modified the saved blob, not the input file.)
** --delta Prefer to generate a delta manifest, if
** able. The forbid-delta-manifests repo
** config option trumps this, as do certain
** heuristics.
** --allow-new-file Allow addition of a new file this way.
** Disabled by default to avoid that case-
** sensitivity errors inadvertently lead to
** adding a new file where an update is
** intended.
** --dump-manifest|-d Dumps the generated manifest to stdout
** immediately after it's generated.
** --save-manifest FILE Saves the generated manifest to a file
** after successfully processing it.
** --wet-run Disables the default dry-run mode.
**
** Example:
**
** %fossil test-ci-mini -R REPO -m ... -r foo --as src/myfile.c myfile.c
**
*/
void test_ci_mini_cmd(void){
CheckinMiniInfo cimi; /* checkin state */
int newRid = 0; /* RID of new version */
const char * zFilename; /* argv[2] */
const char * zComment; /* -m comment */
const char * zCommentFile; /* -M FILE */
const char * zAsFilename; /* --as filename */
const char * zRevision; /* --revision|-r [=trunk|checkout] */
const char * zUser; /* --user-override */
const char * zDate; /* --date-override */
char const * zManifestFile = 0;/* --save-manifest FILE */
/* This function should perform only the minimal "business logic" it
** needs in order to fully/properly populate the CheckinMiniInfo and
** then pass it on to checkin_mini() to do most of the validation
** and work. The point of this is to avoid duplicate code when a web
** front-end is added for checkin_mini().
*/
CheckinMiniInfo_init(&cimi);
zComment = find_option("comment","m",1);
zCommentFile = find_option("comment-file","M",1);
zAsFilename = find_option("as",0,1);
zRevision = find_option("revision","r",1);
zUser = find_option("user-override",0,1);
zDate = find_option("date-override",0,1);
zManifestFile = find_option("save-manifest",0,1);
if(find_option("wet-run",0,0)==0){
cimi.flags |= CIMINI_DRY_RUN;
}
if(find_option("allow-fork",0,0)!=0){
cimi.flags |= CIMINI_ALLOW_FORK;
}
if(find_option("dump-manifest","d",0)!=0){
cimi.flags |= CIMINI_DUMP_MANIFEST;
}
if(find_option("allow-merge-conflict",0,0)!=0){
cimi.flags |= CIMINI_ALLOW_MERGE_MARKER;
}
if(find_option("allow-older",0,0)!=0){
cimi.flags |= CIMINI_ALLOW_OLDER;
}
if(find_option("convert-eol-inherit",0,0)!=0){
cimi.flags |= CIMINI_CONVERT_EOL_INHERIT;
}else if(find_option("convert-eol-unix",0,0)!=0){
cimi.flags |= CIMINI_CONVERT_EOL_UNIX;
}else if(find_option("convert-eol-windows",0,0)!=0){
cimi.flags |= CIMINI_CONVERT_EOL_WINDOWS;
}
if(find_option("delta",0,0)!=0){
cimi.flags |= CIMINI_PREFER_DELTA;
}
if(find_option("delta2",0,0)!=0){
/* Undocumented. For testing only. */
cimi.flags |= CIMINI_PREFER_DELTA | CIMINI_STRONGLY_PREFER_DELTA;
}
if(find_option("allow-new-file",0,0)!=0){
cimi.flags |= CIMINI_ALLOW_NEW_FILE;
}
db_find_and_open_repository(0, 0);
verify_all_options();
user_select();
if(g.argc!=3){
usage("INFILE");
}
if(zComment && zCommentFile){
fossil_fatal("Only one of -m or -M, not both, may be used.");
}else{
if(zCommentFile && *zCommentFile){
blob_read_from_file(&cimi.comment, zCommentFile, ExtFILE);
}else if(zComment && *zComment){
blob_append(&cimi.comment, zComment, -1);
}
if(!blob_size(&cimi.comment)){
fossil_fatal("Non-empty checkin comment is required.");
}
}
db_begin_transaction();
zFilename = g.argv[2];
cimi.zFilename = mprintf("%/", zAsFilename ? zAsFilename : zFilename);
cimi.filePerm = file_perm(zFilename, ExtFILE);
cimi.zUser = mprintf("%s", zUser ? zUser : login_name());
if(zDate){
cimi.zDate = mprintf("%s", zDate);
}
if(zRevision==0 || zRevision[0]==0){
if(g.localOpen/*checkout*/){
zRevision = db_lget("checkout-hash", 0)/*leak*/;
}else{
zRevision = "trunk";
}
}
name_to_uuid2(zRevision, "ci", &cimi.zParentUuid);
if(cimi.zParentUuid==0){
fossil_fatal("Cannot determine version to commit to.");
}
blob_read_from_file(&cimi.fileContent, zFilename, ExtFILE);
{
Blob theManifest = empty_blob; /* --save-manifest target */
Blob errMsg = empty_blob;
int rc;
if(zManifestFile){
cimi.pMfOut = &theManifest;
}
rc = checkin_mini(&cimi, &newRid, &errMsg);
if(rc){
assert(blob_size(&errMsg)==0);
}else{
assert(blob_size(&errMsg));
fossil_fatal("%b", &errMsg);
}
if(zManifestFile){
fossil_print("Writing manifest to: %s\n", zManifestFile);
assert(blob_size(&theManifest)>0);
blob_write_to_file(&theManifest, zManifestFile);
blob_reset(&theManifest);
}
}
if(newRid!=0){
fossil_print("New version%s: %z\n",
(cimi.flags & CIMINI_DRY_RUN) ? " (dry run)" : "",
rid_to_uuid(newRid));
}
db_end_transaction(0/*checkin_mini() will have triggered it to roll
** back in dry-run mode, but we need access to
** the transaction-written db state in this
** routine.*/);
if(!(cimi.flags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){
fossil_warning("The checkout state is now out of sync "
"with regards to this commit. It needs to be "
"'update'd or 'close'd and re-'open'ed.");
}
CheckinMiniInfo_cleanup(&cimi);
}
/*
** If the fileedit-glob setting has a value, this returns its Glob
** object (in memory owned by this function), else it returns NULL.
*/
Glob *fileedit_glob(void){
static Glob * pGlobs = 0;
static int once = 0;
if(0==pGlobs && once==0){
char * zGlobs = db_get("fileedit-glob",0);
once = 1;
if(0!=zGlobs && 0!=*zGlobs){
pGlobs = glob_create(zGlobs);
}
fossil_free(zGlobs);
}
return pGlobs;
}
/*
** Returns true if the given filename qualifies for online editing by
** the current user, else returns false.
**
** Editing requires that the user have the Write permission and that
** the filename match the glob defined by the fileedit-glob setting.
** A missing or empty value for that glob disables all editing.
*/
int fileedit_is_editable(const char *zFilename){
Glob * pGlobs = fileedit_glob();
if(pGlobs!=0 && zFilename!=0 && *zFilename!=0 && 0!=g.perm.Write){
return glob_match(pGlobs, zFilename);
}else{
return 0;
}
}
/*
** Given a repo-relative filename and a manifest RID, returns the UUID
** of the corresponding file entry. Returns NULL if no match is
** found. If pFilePerm is not NULL, the file's permission flag value
** is written to *pFilePerm.
*/
static char *fileedit_file_uuid(char const *zFilename,
int vid, int *pFilePerm){
Stmt stmt = empty_Stmt;
char * zFileUuid = 0;
db_prepare(&stmt, "SELECT uuid, perm FROM files_of_checkin "
"WHERE filename=%Q %s AND checkinID=%d",
zFilename, filename_collation(), vid);
if(SQLITE_ROW==db_step(&stmt)){
zFileUuid = mprintf("%s",db_column_text(&stmt, 0));
if(pFilePerm){
*pFilePerm = mfile_permstr_int(db_column_text(&stmt, 1));
}
}
db_finalize(&stmt);
return zFileUuid;
}
/*
** Returns true if the current user is allowed to edit the given
** filename, as determined by fileedit_is_editable(), else false,
** in which case it queues up an error response and the caller
** must return immediately.
*/
static int fileedit_ajax_check_filename(const char * zFilename){
if(0==fileedit_is_editable(zFilename)){
ajax_route_error(403, "File is disallowed by the "
"fileedit-glob setting.");
return 0;
}
return 1;
}
/*
** Passed the values of the "checkin" and "filename" request
** properties, this function verifies that they are valid and
** populates:
**
** - *zRevUuid = the fully-expanded value of zRev (owned by the
** caller). zRevUuid may be NULL.
**
** - *vid = the RID of zRevUuid. May not be NULL.
**
** - *frid = the RID of zFilename's blob content. May not be NULL
** unless zFilename is also NULL. If BOTH of zFilename and frid are
** NULL then no confirmation is done on the filename argument - only
** zRev is checked.
**
** Returns 0 if the given file is not in the given checkin or if
** fileedit_ajax_check_filename() fails, else returns true. If it
** returns false, it queues up an error response and the caller must
** return immediately.
*/
static int fileedit_ajax_setup_filerev(const char * zRev,
char ** zRevUuid,
int * vid,
const char * zFilename,
int * frid){
char * zFileUuid = 0; /* file content UUID */
const int checkFile = zFilename!=0 || frid!=0;
if(checkFile && !fileedit_ajax_check_filename(zFilename)){
return 0;
}
*vid = symbolic_name_to_rid(zRev, "ci");
if(0==*vid){
ajax_route_error(404,"Cannot resolve name as a checkin: %s",
zRev);
return 0;
}else if(*vid<0){
ajax_route_error(400,"Checkin name is ambiguous: %s",
zRev);
return 0;
}
if(checkFile){
zFileUuid = fileedit_file_uuid(zFilename, *vid, 0);
if(zFileUuid==0){
ajax_route_error(404, "Checkin does not contain file.");
return 0;
}
}
if(zRevUuid!=0){
*zRevUuid = rid_to_uuid(*vid);
}
if(checkFile){
assert(zFileUuid!=0);
if(frid!=0){
*frid = fast_uuid_to_rid(zFileUuid);
}
fossil_free(zFileUuid);
}
return 1;
}
/*
** AJAX route /fileedit?ajax=content
**
** Query parameters:
**
** filename=FILENAME
** checkin=CHECKIN_NAME
**
** User must have Write access to use this page.
**
** Responds with the raw content of the given page. On error it
** produces a JSON response as documented for ajax_route_error().
**
** Extra response headers:
**
** x-fileedit-file-perm: empty or "x" or "l", representing PERM_REG,
** PERM_EXE, or PERM_LINK, respectively.
**
** x-fileedit-checkin-branch: branch name for the passed-in checkin.
*/
static void fileedit_ajax_content(void){
const char * zFilename = 0;
const char * zRev = 0;
int vid, frid;
Blob content = empty_blob;
const char * zMime;
ajax_get_fnci_args( &zFilename, &zRev );
if(!ajax_route_bootstrap(1,0)
|| !fileedit_ajax_setup_filerev(zRev, 0, &vid,
zFilename, &frid)){
return;
}
zMime = mimetype_from_name(zFilename);
content_get(frid, &content);
if(0==zMime){
if(looks_like_binary(&content)){
zMime = "application/octet-stream";
}else{
zMime = "text/plain";
}
}
{ /* Send the is-exec bit via response header so that the UI can be
** updated to account for that. */
int fperm = 0;
char * zFuuid = fileedit_file_uuid(zFilename, vid, &fperm);
const char * zPerm = mfile_permint_mstring(fperm);
assert(zFuuid);
cgi_printf_header("x-fileedit-file-perm:%s\r\n", zPerm);
fossil_free(zFuuid);
}
{ /* Send branch name via response header for UI usability reasons */
char * zBranch = branch_of_rid(vid);
if(zBranch!=0 && zBranch[0]!=0){
cgi_printf_header("x-fileedit-checkin-branch: %s\r\n", zBranch);
}
fossil_free(zBranch);
}
cgi_set_content_type(zMime);
cgi_set_content(&content);
}
/*
** AJAX route /fileedit?ajax=diff
**
** Required query parameters:
**
** filename=FILENAME
** content=text
** checkin=checkin version
**
** Optional parameters:
**
** sbs=integer (1=side-by-side or 0=unified, default=0)
**
** ws=integer (0=diff whitespace, 1=ignore EOL ws, 2=ignore all ws)
**
** Reminder to self: search info.c for isPatch to see how a
** patch-style siff can be produced.
**
** User must have Write access to use this page.
**
** Responds with the HTML content of the diff. On error it produces a
** JSON response as documented for ajax_route_error().
*/
static void fileedit_ajax_diff(void){
/*
** Reminder: we only need the filename to perform valdiation
** against fileedit_is_editable(), else this route could be
** abused to get diffs against content disallowed by the
** whitelist.
*/
const char * zFilename = 0;
const char * zRev = 0;
const char * zContent = P("content");
char * zRevUuid = 0;
int vid, frid, iFlag;
u64 diffFlags = DIFF_HTML | DIFF_NOTTOOBIG;
Blob content = empty_blob;
iFlag = atoi(PD("sbs","0"));
if(0==iFlag){
diffFlags |= DIFF_LINENO;
}else{
diffFlags |= DIFF_SIDEBYSIDE;
}
iFlag = atoi(PD("ws","2"));
if(2==iFlag){
diffFlags |= DIFF_IGNORE_ALLWS;
}else if(1==iFlag){
diffFlags |= DIFF_IGNORE_EOLWS;
}
diffFlags |= DIFF_STRIP_EOLCR;
ajax_get_fnci_args( &zFilename, &zRev );
if(!ajax_route_bootstrap(1,1)
|| !fileedit_ajax_setup_filerev(zRev, &zRevUuid, &vid,
zFilename, &frid)){
return;
}
if(!zContent){
zContent = "";
}
cgi_set_content_type("text/html");
blob_init(&content, zContent, -1);
{
Blob orig = empty_blob;
content_get(frid, &orig);
ajax_render_diff(&orig, &content, diffFlags);
blob_reset(&orig);
}
fossil_free(zRevUuid);
blob_reset(&content);
}
/*
** Sets up and validates most, but not all, of p's checkin-related
** state from the CGI environment. Returns 0 on success or a suggested
** HTTP result code on error, in which case a message will have been
** written to pErr.
**
** It always fails if it cannot completely resolve the 'file' and 'r'
** parameters, including verifying that the refer to a real
** file/version combination and editable by the current user. All
** others are optional (at this level, anyway, but upstream code might
** require them).
**
** If the 3rd argument is not NULL and an error is related to a
** missing arg then *bIsMissingArg is set to true. This is
** intended to allow /fileedit to squelch certain initialization
** errors.
**
** Intended to be used only by /filepage and /filepage_commit.
*/
static int fileedit_setup_cimi_from_p(CheckinMiniInfo * p, Blob * pErr,
int * bIsMissingArg){
char * zFileUuid = 0; /* UUID of file content */
const char * zFlag; /* generic flag */
int rc = 0, vid = 0, frid = 0; /* result code, checkin/file rids */
#define fail(EXPR) blob_appendf EXPR; goto end_fail
zFlag = PD("filename",P("fn"));
if(zFlag==0 || !*zFlag){
rc = 400;
if(bIsMissingArg){
*bIsMissingArg = 1;
}
fail((pErr,"Missing required 'filename' parameter."));
}
p->zFilename = mprintf("%s",zFlag);
if(0==fileedit_is_editable(p->zFilename)){
rc = 403;
fail((pErr,"Filename [%h] is disallowed "
"by the [fileedit-glob] repository "
"setting.",
p->zFilename));
}
zFlag = PD("checkin",P("ci"));
if(!zFlag){
rc = 400;
if(bIsMissingArg){
*bIsMissingArg = 1;
}
fail((pErr,"Missing required 'checkin' parameter."));
}
vid = symbolic_name_to_rid(zFlag, "ci");
if(0==vid){
rc = 404;
fail((pErr,"Could not resolve checkin version."));
}else if(vid<0){
rc = 400;
fail((pErr,"Checkin name is ambiguous."));
}
p->zParentUuid = rid_to_uuid(vid)/*fully expand it*/;
zFileUuid = fileedit_file_uuid(p->zFilename, vid, &p->filePerm);
if(!zFileUuid){
rc = 404;
fail((pErr,"Checkin [%S] does not contain file: "
"[%h]", p->zParentUuid, p->zFilename));
}else if(PERM_LNK==p->filePerm){
rc = 400;
fail((pErr,"Editing symlinks is not permitted."));
}
/* Find the repo-side file entry or fail... */
frid = fast_uuid_to_rid(zFileUuid);
assert(frid);
/* Read file content from submit request or repo... */
zFlag = P("content");
if(zFlag==0){
content_get(frid, &p->fileContent);
}else{
blob_init(&p->fileContent,zFlag,-1);
}
if(looks_like_binary(&p->fileContent)){
rc = 400;
fail((pErr,"File appears to be binary. Cannot edit: "
"[%h]",p->zFilename));
}
zFlag = PT("comment");
if(zFlag!=0 && *zFlag!=0){
blob_append(&p->comment, zFlag, -1);
}
zFlag = P("comment_mimetype");
if(zFlag){
p->zCommentMimetype = mprintf("%s",zFlag);
zFlag = 0;
}
#define p_int(K) atoi(PD(K,"0"))
if(p_int("dry_run")!=0){
p->flags |= CIMINI_DRY_RUN;
}
if(p_int("allow_fork")!=0){
p->flags |= CIMINI_ALLOW_FORK;
}
if(p_int("allow_older")!=0){
p->flags |= CIMINI_ALLOW_OLDER;
}
if(0==p_int("exec_bit")){
p->filePerm = PERM_REG;
}else{
p->filePerm = PERM_EXE;
}
if(p_int("allow_merge_conflict")!=0){
p->flags |= CIMINI_ALLOW_MERGE_MARKER;
}
if(p_int("prefer_delta")!=0){
p->flags |= CIMINI_PREFER_DELTA;
}
/* EOL conversion policy... */
switch(p_int("eol")){
case 1: p->flags |= CIMINI_CONVERT_EOL_UNIX; break;
case 2: p->flags |= CIMINI_CONVERT_EOL_WINDOWS; break;
default: p->flags |= CIMINI_CONVERT_EOL_INHERIT; break;
}
#undef p_int
/*
** TODO?: date-override date selection field. Maybe use
** an input[type=datetime-local].
*/
p->zUser = mprintf("%s",g.zLogin);
return 0;
end_fail:
#undef fail
fossil_free(zFileUuid);
return rc ? rc : 500;
}
/*
** AJAX route /fileedit?ajax=filelist
**
** Fetches a JSON-format list of leaves and/or filenames for use in
** creating a file selection list in /fileedit. It has different modes
** of operation depending on its arguments:
**
** 'leaves': just fetch a list of open leaf versions, in this
** format:
**
** [
** {checkin: UUID, branch: branchName, timestamp: string}
** ]
**
** The entries are ordered newest first.
**
** 'checkin=CHECKIN_NAME': fetch the current list of is-editable files
** for the current user and given checkin name:
**
** {
** checkin: UUID,
** editableFiles: [ filename1, ... filenameN ] // sorted by name
** }
**
** On error it produces a JSON response as documented for
** ajax_route_error().
*/
static void fileedit_ajax_filelist(void){
const char * zCi = PD("checkin",P("ci"));
Blob sql = empty_blob;
Stmt q = empty_Stmt;
int i = 0;
if(!ajax_route_bootstrap(1,0)){
return;
}
cgi_set_content_type("application/json");
if(zCi!=0){
char * zCiFull = 0;
int vid = 0;
if(0==fileedit_ajax_setup_filerev(zCi, &zCiFull, &vid, 0, 0)){
/* Error already reported */
return;
}
CX("{\"checkin\":%!j,"
"\"editableFiles\":[", zCiFull);
blob_append_sql(&sql, "SELECT filename FROM files_of_checkin(%Q) "
"ORDER BY filename %s",
zCiFull, filename_collation());
db_prepare_blob(&q, &sql);
while( SQLITE_ROW==db_step(&q) ){
const char * zFilename = db_column_text(&q, 0);
if(fileedit_is_editable(zFilename)){
if(i++){
CX(",");
}
CX("%!j", zFilename);
}
}
db_finalize(&q);
CX("]}");
}else if(P("leaves")!=0){
blob_append(&sql, timeline_query_for_tty(), -1);
blob_append_sql(&sql, " AND blob.rid IN (SElECT rid FROM leaf "
"WHERE NOT EXISTS("
"SELECT 1 from tagxref WHERE tagid=%d AND "
"tagtype>0 AND rid=leaf.rid"
")) "
"ORDER BY mtime DESC", TAG_CLOSED);
db_prepare_blob(&q, &sql);
CX("[");
while( SQLITE_ROW==db_step(&q) ){
if(i++){
CX(",");
}
CX("{");
CX("\"checkin\":%!j,", db_column_text(&q, 1));
CX("\"branch\":%!j,", db_column_text(&q, 7));
CX("\"timestamp\":%!j", db_column_text(&q, 2));
CX("}");
}
CX("]");
db_finalize(&q);
}else{
ajax_route_error(500, "Unhandled URL argument.");
}
}
/*
** AJAX route /fileedit?ajax=commit
**
** Required query parameters:
**
** filename=FILENAME
** checkin=Parent checkin UUID
** content=text
** comment=non-empty text
**
** Optional query parameters:
**
** comment_mimetype=text (NOT currently honored)
**
** dry_run=int (1 or 0)
**
** include_manifest=int (1 or 0), whether to include
** the generated manifest in the response.
**
**
** User must have Write permissions to use this page.
**
** Responds with JSON (with some state repeated
** from the input in order to avoid certain race conditions
** client-side):
**
** {
** checkin: newUUID,
** filename: theFilename,
** mimetype: string,
** branch: name of the checkin's branch,
** isExe: bool,
** dryRun: bool,
** manifest: text of manifest,
** }
**
** On error it produces a JSON response as documented for
** ajax_route_error().
*/
static void fileedit_ajax_commit(void){
Blob err = empty_blob; /* Error messages */
Blob manifest = empty_blob; /* raw new manifest */
CheckinMiniInfo cimi; /* checkin state */
int rc; /* generic result code */
int newVid = 0; /* new version's RID */
char * zNewUuid = 0; /* newVid's UUID */
char const * zMimetype;
char * zBranch = 0;
if(!ajax_route_bootstrap(1,1)){
return;
}
db_begin_transaction();
CheckinMiniInfo_init(&cimi);
rc = fileedit_setup_cimi_from_p(&cimi, &err, 0);
if(0!=rc){
ajax_route_error(rc,"%b",&err);
goto end_cleanup;
}
if(blob_size(&cimi.comment)==0){
ajax_route_error(400,"Empty checkin comment is not permitted.");
goto end_cleanup;
}
if(0!=atoi(PD("include_manifest","0"))){
cimi.pMfOut = &manifest;
}
checkin_mini(&cimi, &newVid, &err);
if(blob_size(&err)){
ajax_route_error(500,"%b",&err);
goto end_cleanup;
}
assert(newVid>0);
zNewUuid = rid_to_uuid(newVid);
cgi_set_content_type("application/json");
CX("{");
CX("\"checkin\":%!j,", zNewUuid);
CX("\"filename\":%!j,", cimi.zFilename);
CX("\"isExe\": %s,", cimi.filePerm==PERM_EXE ? "true" : "false");
zMimetype = mimetype_from_name(cimi.zFilename);
if(zMimetype!=0){
CX("\"mimetype\": %!j,", zMimetype);
}
zBranch = branch_of_rid(newVid);
if(zBranch!=0){
CX("\"branch\": %!j,", zBranch);
fossil_free(zBranch);
}
CX("\"dryRun\": %s",
(CIMINI_DRY_RUN & cimi.flags) ? "true" : "false");
if(blob_size(&manifest)>0){
CX(",\"manifest\": %!j", blob_str(&manifest));
}
CX("}");
end_cleanup:
db_end_transaction(0/*noting that dry-run mode will have already
** set this to rollback mode. */);
fossil_free(zNewUuid);
blob_reset(&err);
blob_reset(&manifest);
CheckinMiniInfo_cleanup(&cimi);
}
/*
** WEBPAGE: fileedit
**
** Enables the online editing and committing of individual text files.
** Requires that the user have Write permissions.
**
** Optional query parameters:
**
** filename=FILENAME Repo-relative path to the file.
** checkin=VERSION Checkin version, using any unambiguous
** supported symbolic version name.
**
** Internal-use parameters:
**
** name=string The name of a page-specific AJAX operation.
**
** Noting that fossil internally stores all URL path components after
** the first as the "name" value. Thus /fileedit?name=blah is
** equivalent to /fileedit/blah. The latter is the preferred
** form. This means, however, that no fileedit ajax routes may make
** use of the name parameter.
**
** Which additional parameters are used by each distinct ajax value is
** an internal implementation detail and may change with any given
** build of this code. An unknown "name" value triggers an error, as
** documented for ajax_route_error().
*/
void fileedit_page(void){
const char * zFilename = 0; /* filename. We'll accept 'name'
because that param is handled
specially by the core. */
const char * zRev = 0; /* checkin version */
const char * zFileMime = 0; /* File mime type guess */
CheckinMiniInfo cimi; /* Checkin state */
int previewRenderMode = AJAX_RENDER_GUESS; /* preview mode */
Blob err = empty_blob; /* Error report */
Blob endScript = empty_blob; /* Script code to run at the
end. This content will be
combined into a single JS
function call, thus each
entry must end with a
semicolon. */
const char *zAjax = P("name"); /* Name of AJAX route for
sub-dispatching. */
/* Allow no access to this page without check-in privilege */
login_check_credentials();
if( !g.perm.Write ){
if(zAjax!=0){
ajax_route_error(403, "Write permissions required.");
}else{
login_needed(g.anon.Write);
}
return;
}
/* No access to anything on this page if the fileedit-glob is empty */
if( fileedit_glob()==0 ){
if(zAjax!=0){
ajax_route_error(403, "Online editing is disabled for this "
"repository.");
return;
}
style_header("File Editor (disabled)");
CX("<h1>Online File Editing Is Disabled</h1>\n");
if( g.perm.Admin ){
CX("<p>To enable online editing, the "
"<a href='%R/setup_settings'>"
"<code>fileedit-glob</code> repository setting</a>\n"
"must be set to a comma- and/or newine-delimited list of glob\n"
"values matching files which may be edited online."
"</p>\n");
}else{
CX("<p>Online editing is disabled for this repository.</p>\n");
}
style_footer();
return;
}
/* Dispatch AJAX methods based tail of the request URI.
** The AJAX parts do their own permissions/CSRF check and
** fail with a JSON-format response if needed.
*/
if( 0!=zAjax ){
/* preview mode is handled via /ajax/preview-text */
if(0==strcmp("content",zAjax)){
fileedit_ajax_content();
}else if(0==strcmp("filelist",zAjax)){
fileedit_ajax_filelist();
}else if(0==strcmp("diff",zAjax)){
fileedit_ajax_diff();
}else if(0==strcmp("commit",zAjax)){
fileedit_ajax_commit();
}else{
ajax_route_error(500, "Unhandled ajax route name.");
}
return;
}
db_begin_transaction();
CheckinMiniInfo_init(&cimi);
style_header("File Editor");
/* As of this point, don't use return or fossil_fatal(). Write any
** error in (&err) and goto end_footer instead so that we can be
** sure to emit the error message, do any cleanup, and end the
** transaction cleanly.
*/
{
int isMissingArg = 0;
if(fileedit_setup_cimi_from_p(&cimi, &err, &isMissingArg)==0){
zFilename = cimi.zFilename;
zRev = cimi.zParentUuid;
assert(zRev);
assert(zFilename);
zFileMime = mimetype_from_name(cimi.zFilename);
}else if(isMissingArg!=0){
/* Squelch these startup warnings - they're non-fatal now but
** used to be fatal. */
blob_reset(&err);
}
}
/********************************************************************
** All errors which "could" have happened up to this point are of a
** degree which keep us from rendering the rest of the page, and
** thus have already caused us to skipped to the end of the page to
** render the errors. Any up-coming errors, barring malloc failure
** or similar, are not "that" fatal. We can/should continue
** rendering the page, then output the error message at the end.
********************************************************************/
/* The CSS for this page lives in a common file but much of it we
** don't want inadvertently being used by other pages. We don't
** have a common, page-specific container we can filter our CSS
** selectors, but we do have the BODY, which we can decorate with
** whatever CSS we wish...
*/
style_emit_script_tag(0,0);
CX("document.body.classList.add('fileedit');\n");
style_emit_script_tag(1,0);
/* Status bar */
CX("<div id='fossil-status-bar' "
"title='Status message area. Double-click to clear them.'>"
"Status messages will go here.</div>\n"
/* will be moved into the tab container via JS */);
/* Main tab container... */
CX("<div id='fileedit-tabs' class='tab-container'></div>");
/***** File/version info tab *****/
{
CX("<div id='fileedit-tab-fileselect' "
"data-tab-parent='fileedit-tabs' "
"data-tab-label='File Info & Selection'"
">");
CX("<fieldset id='file-version-details'>"
"<legend>File/Version</legend>"
"<div>No file loaded.</div>"
"</fieldset>");
CX("<h1>Select a file to edit:</h1>");
CX("<div id='fileedit-file-selector'></div>");
CX("</div>"/*#fileedit-tab-fileselect*/);
}
/******* Content tab *******/
{
CX("<div id='fileedit-tab-content' "
"data-tab-parent='fileedit-tabs' "
"data-tab-label='File Content'"
">");
CX("<div class='flex-container flex-row child-gap-small'>");
CX("<button class='fileedit-content-reload confirmer' "
"title='Reload the file from the server, discarding "
"any local edits. To help avoid accidental loss of "
"edits, it requires confirmation (a second click) within "
"a few seconds or it will not reload.'"
">Discard & Reload</button>");
style_select_list_int("select-font-size",
"editor_font_size", "Editor font size",
NULL/*tooltip*/,
100,
"100%", 100, "125%", 125,
"150%", 150, "175%", 175,
"200%", 200, NULL);
CX("</div>");
CX("<div class='flex-container flex-column stretch'>");
CX("<textarea name='content' id='fileedit-content-editor' "
"class='fileedit' "
"rows='20' cols='80'>");
CX("</textarea>");
CX("</div>"/*textarea wrapper*/);
CX("</div>"/*#tab-file-content*/);
}
/****** Preview tab ******/
{
CX("<div id='fileedit-tab-preview' "
"data-tab-parent='fileedit-tabs' "
"data-tab-label='Preview'"
">");
CX("<div class='fileedit-options flex-container flex-row'>");
CX("<button id='btn-preview-refresh' "
"data-f-preview-from='fileContent' "
/* ^^^ fossil.page[methodName]() OR text source elem ID,
** but we need a method in order to support clients swapping out
** the text editor with their own. */
"data-f-preview-via='_postPreview' "
/* ^^^ fossil.page[methodName](content, callback) */
"data-f-preview-to='#fileedit-tab-preview-wrapper' "
/* ^^^ dest elem ID */
">Refresh</button>");
/* Toggle auto-update of preview when the Preview tab is selected. */
style_labeled_checkbox("cb-preview-autoupdate",
NULL,
"Auto-refresh?",
"1", 1,
"If on, the preview will automatically "
"refresh when this tab is selected.");
/* Default preview rendering mode selection... */
previewRenderMode = zFileMime
? ajax_render_mode_for_mimetype(zFileMime)
: AJAX_RENDER_GUESS;
style_select_list_int("select-preview-mode",
"preview_render_mode",
"Preview Mode",
"Preview mode format.",
previewRenderMode,
"Guess", AJAX_RENDER_GUESS,
"Wiki/Markdown", AJAX_RENDER_WIKI,
"HTML (iframe)", AJAX_RENDER_HTML_IFRAME,
"HTML (inline)", AJAX_RENDER_HTML_INLINE,
"Plain Text", AJAX_RENDER_PLAIN_TEXT,
NULL);
/* Allow selection of HTML preview iframe height */
style_select_list_int("select-preview-html-ems",
"preview_html_ems",
"HTML Preview IFrame Height (EMs)",
"Height (in EMs) of the iframe used for "
"HTML preview",
40 /*default*/,
"", 20, "", 40,
"", 60, "", 80,
"", 100, NULL);
/* Selection of line numbers for text preview */
style_labeled_checkbox("cb-line-numbers",
"preview_ln",
"Add line numbers to plain-text previews?",
"1", P("preview_ln")!=0,
"If on, plain-text files (only) will get "
"line numbers added to the preview.");
CX("</div>"/*.fileedit-options*/);
CX("<div id='fileedit-tab-preview-wrapper'></div>");
CX("</div>"/*#fileedit-tab-preview*/);
}
/****** Diff tab ******/
{
CX("<div id='fileedit-tab-diff' "
"data-tab-parent='fileedit-tabs' "
"data-tab-label='Diff'"
">");
CX("<div class='fileedit-options flex-container flex-row' "
"id='fileedit-tab-diff-buttons'>");
CX("<button class='sbs'>Side-by-side</button>"
"<button class='unified'>Unified</button>");
if(0){
/* For the time being let's just ignore all whitespace
** changes, as files with Windows-style EOLs always show
** more diffs than we want then they're submitted to
** ?ajax=diff because JS normalizes them to Unix EOLs.
** We can revisit this decision later. */
style_select_list_int("diff-ws-policy",
"diff_ws", "Whitespace",
"Whitespace handling policy.",
2,
"Diff all whitespace", 0,
"Ignore EOL whitespace", 1,
"Ignore all whitespace", 2,
NULL);
}
CX("</div>");
CX("<div id='fileedit-tab-diff-wrapper'>"
"Diffs will be shown here."
"</div>");
CX("</div>"/*#fileedit-tab-diff*/);
}
/****** Commit ******/
CX("<div id='fileedit-tab-commit' "
"data-tab-parent='fileedit-tabs' "
"data-tab-label='Commit'"
">");
{
/******* Commit flags/options *******/
CX("<div class='fileedit-options flex-container flex-row'>");
style_labeled_checkbox("cb-dry-run",
"dry_run", "Dry-run?", "1",
0,
"In dry-run mode, the Commit button performs"
"all work needed for committing changes but "
"then rolls back the transaction, and thus "
"does not really commit.");
style_labeled_checkbox("cb-allow-fork",
"allow_fork", "Allow fork?", "1",
cimi.flags & CIMINI_ALLOW_FORK,
"Allow committing to create a fork?");
style_labeled_checkbox("cb-allow-older",
"allow_older", "Allow older?", "1",
cimi.flags & CIMINI_ALLOW_OLDER,
"Allow saving against a parent version "
"which has a newer timestamp?");
style_labeled_checkbox("cb-exec-bit",
"exec_bit", "Executable?", "1",
PERM_EXE==cimi.filePerm,
"Set the executable bit?");
style_labeled_checkbox("cb-allow-merge-conflict",
"allow_merge_conflict",
"Allow merge conflict markers?", "1",
cimi.flags & CIMINI_ALLOW_MERGE_MARKER,
"Allow saving even if the content contains "
"what appear to be fossil merge conflict "
"markers?");
style_labeled_checkbox("cb-prefer-delta",
"prefer_delta",
"Prefer delta manifest?", "1",
db_get_boolean("forbid-delta-manifests",0)
? 0
: (db_get_boolean("seen-delta-manifest",0)
|| cimi.flags & CIMINI_PREFER_DELTA),
"Will create a delta manifest, instead of "
"baseline, if conditions are favorable to "
"do so. This option is only a suggestion.");
style_labeled_checkbox("cb-include-manifest",
"include_manifest",
"Response manifest?", "1",
0,
"Include the manifest in the response? "
"It's generally only useful for debug "
"purposes.");
style_select_list_int("select-eol-style",
"eol", "EOL Style",
"EOL conversion policy, noting that "
"webpage-side processing may implicitly change "
"the line endings of the input.",
(cimi.flags & CIMINI_CONVERT_EOL_UNIX)
? 1 : (cimi.flags & CIMINI_CONVERT_EOL_WINDOWS
? 2 : 0),
"Inherit", 0,
"Unix", 1,
"Windows", 2,
NULL);
CX("</div>"/*checkboxes*/);
}
{ /******* Commit comment, button, and result manifest *******/
CX("<fieldset class='fileedit-options commit-message'>"
"<legend>Message (required)</legend><div>\n");
/* We have two comment input fields, defaulting to single-line
** mode. JS code sets up the ability to toggle between single-
** and multi-line modes. */
CX("<input type='text' name='comment' "
"id='fileedit-comment'></input>");
CX("<textarea name='commentBig' class='hidden' "
"rows='5' id='fileedit-comment-big'></textarea>\n");
{ /* comment options... */
CX("<div class='flex-container flex-column child-gap-small'>");
CX("<button id='comment-toggle' "
"title='Toggle between single- and multi-line comment mode, "
"noting that switching from multi- to single-line will cause "
"newlines to get stripped.'"
">Toggle single-/multi-line</button> ");
if(0){
/* Manifests support an N-card (comment mime type) but it has
** yet to be honored where comments are rendered, so we don't
** currently offer it as an option here:
** https://fossil-scm.org/forum/forumpost/662da045a1
**
** If/when it's ever implemented, simply enable this block and
** adjust the container's layout accordingly (as of this
** writing, that means changing the CSS class from
** 'flex-container flex-column' to 'flex-container flex-row').
*/
style_select_list_str("comment-mimetype", "comment_mimetype",
"Comment style:",
"Specify how fossil will interpret the "
"comment string.",
NULL,
"Fossil", "text/x-fossil-wiki",
"Markdown", "text/x-markdown",
"Plain text", "text/plain",
NULL);
CX("</div>\n");
}
CX("<div class='fileedit-hint flex-container flex-row'>"
"(Warning: switching from multi- to single-line mode will "
"strip out all newlines!)</div>");
}
CX("</div></fieldset>\n"/*commit comment options*/);
CX("<div class='flex-container flex-column' "
"id='fileedit-commit-button-wrapper'>"
"<button id='fileedit-btn-commit'>Commit</button>"
"</div>\n");
CX("<div id='fileedit-manifest'></div>\n"
/* Manifest gets rendered here after a commit. */);
}
CX("</div>"/*#fileedit-tab-commit*/);
/****** Help/Tips ******/
CX("<div id='fileedit-tab-help' "
"data-tab-parent='fileedit-tabs' "
"data-tab-label='Help'"
">");
{
CX("<h1>Help & Tips</h1>");
CX("<ul>");
CX("<li><strong>Only files matching the <code>fileedit-glob</code> "
"repository setting</strong> can be edited online. That setting "
"must be a comma- or newline-delimited list of glob patterns "
"for files which may be edited online.</li>");
CX("<li>Committing edits creates a new commit record with a single "
"modified file.</li>");
CX("<li>\"Delta manifests\" (see the checkbox on the Commit tab) "
"make for smaller commit records, especially in repositories "
"with many files.</li>");
CX("<li>The file selector allows, for usability's sake, only files "
"in leaf check-ins to be selected, but files may be edited via "
"non-leaf check-ins by passing them as the <code>filename</code> "
"and <code>checkin</code> URL arguments to this page.</li>");
CX("<li>The editor stores some number of local edits in one of "
"<code>window.fileStorage</code> or "
"<code>window.sessionStorage</code>, if able, but which storage "
"is unspecified and may differ across environments. When "
"committing or force-reloading a file, local edits to that "
"file/check-in combination are discarded.</li>");
CX("</ul>");
}
CX("</div>"/*#fileedit-tab-help*/);
{
/* Dynamically populate the editor, display any error in the err
** blob, and/or switch to tab #0, where the file selector
** lives... */
blob_appendf(&endScript,
"fossil.onPageLoad(");
if(zRev && zFilename){
assert(0==blob_size(&err));
blob_appendf(&endScript,
"()=>fossil.page.loadFile(%!j,%!j)",
zFilename, cimi.zParentUuid);
}else{
blob_appendf(&endScript,"function(){\n");
if(blob_size(&err)>0){
blob_appendf(&endScript,
"fossil.error(%!j);\n",
blob_str(&err));
}
blob_appendf(&endScript,
"fossil.page.tabs.switchToTab(0);\n");
blob_appendf(&endScript,"}");
}
blob_appendf(&endScript,");\n");
}
blob_reset(&err);
CheckinMiniInfo_cleanup(&cimi);
style_emit_script_fossil_bootstrap(0);
append_diff_javascript(1);
style_emit_script_fetch(0);
style_emit_script_tabs(0)/*also emits fossil.dom*/;
style_emit_script_confirmer(0);
style_emit_script_builtin(0, "fossil.storage.js");
/*
** Set up a JS-side mapping of the AJAX_RENDER_xyz values. This is
** used for dynamically toggling certain UI components on and off.
** Must come before fossil.page.fileedit.js.
*/
ajax_emit_js_preview_modes(1);
style_emit_script_builtin(0, "fossil.page.fileedit.js");
if(blob_size(&endScript)>0){
style_emit_script_tag(0,0);
CX("\n(function(){\n");
CX("try{\n%b}\n"
"catch(e){"
"fossil.error(e); console.error('Exception:',e);"
"}\n",
&endScript);
CX("})();");
style_emit_script_tag(1,0);
}
db_end_transaction(0);
style_footer();
}
|
| ︙ | ︙ | |||
197 198 199 200 201 202 203 |
" AND event.objid=ci.rid"
" ORDER BY event.mtime DESC LIMIT %d OFFSET %d",
TAG_BRANCH, zFilename, filename_collation(),
iLimit, iOffset
);
blob_zero(&line);
if( iBrief ){
| | | 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
" AND event.objid=ci.rid"
" ORDER BY event.mtime DESC LIMIT %d OFFSET %d",
TAG_BRANCH, zFilename, filename_collation(),
iLimit, iOffset
);
blob_zero(&line);
if( iBrief ){
fossil_print("History for %s\n", blob_str(&fname));
}
while( db_step(&q)==SQLITE_ROW ){
const char *zFileUuid = db_column_text(&q, 0);
const char *zCiUuid = db_column_text(&q,1);
const char *zDate = db_column_text(&q, 2);
const char *zCom = db_column_text(&q, 3);
const char *zUser = db_column_text(&q, 4);
|
| ︙ | ︙ | |||
282 283 284 285 286 287 288 | ** ** a=DATETIME Only show changes after DATETIME ** b=DATETIME Only show changes before DATETIME ** m=HASH Mark this particular file version ** n=NUM Show the first NUM changes only ** brbg Background color by branch name ** ubg Background color by user name | | | | | 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 |
**
** a=DATETIME Only show changes after DATETIME
** b=DATETIME Only show changes before DATETIME
** m=HASH Mark this particular file version
** n=NUM Show the first NUM changes only
** brbg Background color by branch name
** ubg Background color by user name
** ci=HASH Ancestors of a particular check-in
** orig=HASH If both ci and orig are supplied, only show those
** changes on a direct path from orig to ci.
** showid Show RID values for debugging
**
** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in
** year-month-day form, it may be truncated, and it may also name a
** timezone offset from UTC as "-HH:MM" (westward) or "+HH:MM"
** (eastward). Either no timezone suffix or "Z" means UTC.
*/
void finfo_page(void){
Stmt q;
const char *zFilename = PD("name","");
char zPrevDate[20];
const char *zA;
const char *zB;
int n;
int baseCheckin;
int origCheckin = 0;
int fnid;
|
| ︙ | ︙ | |||
319 320 321 322 323 324 325 |
int tmFlags = 0; /* Viewing mode */
const char *zStyle; /* Viewing mode name */
const char *zMark; /* Mark this version of the file */
int selRid = 0; /* RID of the marked file version */
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
| > > | > > > < < | 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 |
int tmFlags = 0; /* Viewing mode */
const char *zStyle; /* Viewing mode name */
const char *zMark; /* Mark this version of the file */
int selRid = 0; /* RID of the marked file version */
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
if( fnid==0 ){
style_header("No such file");
}else{
style_header("History for %s", zFilename);
}
login_anonymous_available();
tmFlags = timeline_ss_submenu();
if( tmFlags & TIMELINE_COLUMNAR ){
zStyle = "Columnar";
}else if( tmFlags & TIMELINE_COMPACT ){
zStyle = "Compact";
}else if( tmFlags & TIMELINE_VERBOSE ){
zStyle = "Verbose";
}else if( tmFlags & TIMELINE_CLASSIC ){
zStyle = "Classic";
}else{
zStyle = "Modern";
}
url_initialize(&url, "finfo");
if( brBg ) url_add_parameter(&url, "brbg", 0);
if( uBg ) url_add_parameter(&url, "ubg", 0);
baseCheckin = name_to_rid_www("ci");
zPrevDate[0] = 0;
cookie_render();
if( fnid==0 ){
@ No such file: %h(zFilename)
style_footer();
return;
}
if( g.perm.Admin ){
style_submenu_element("MLink Table", "%R/mlink?name=%t", zFilename);
|
| ︙ | ︙ | |||
366 367 368 369 370 371 372 |
blob_append_sql(&sql,
"SELECT"
" datetime(min(event.mtime),toLocal())," /* Date of change */
" coalesce(event.ecomment, event.comment)," /* Check-in comment */
" coalesce(event.euser, event.user)," /* User who made chng */
" mlink.pid," /* Parent file rid */
" mlink.fid," /* File rid */
| | | | | 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
blob_append_sql(&sql,
"SELECT"
" datetime(min(event.mtime),toLocal())," /* Date of change */
" coalesce(event.ecomment, event.comment)," /* Check-in comment */
" coalesce(event.euser, event.user)," /* User who made chng */
" mlink.pid," /* Parent file rid */
" mlink.fid," /* File rid */
" (SELECT uuid FROM blob WHERE rid=mlink.pid)," /* Parent file hash */
" blob.uuid," /* Current file hash */
" (SELECT uuid FROM blob WHERE rid=mlink.mid)," /* Check-in hash */
" event.bgcolor," /* Background color */
" (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0"
" AND tagxref.rid=mlink.mid)," /* Branchname */
" mlink.mid," /* check-in ID */
" mlink.pfnid," /* Previous filename */
" blob.size" /* File size */
" FROM mlink, event, blob"
|
| ︙ | ︙ | |||
433 434 435 436 437 438 439 |
if( origCheckin ){
blob_appendf(&title, "Changes to file ");
}else if( n>0 ){
blob_appendf(&title, "First %d ancestors of file ", n);
}else{
blob_appendf(&title, "Ancestors of file ");
}
| | > | | | > > > | 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 |
if( origCheckin ){
blob_appendf(&title, "Changes to file ");
}else if( n>0 ){
blob_appendf(&title, "First %d ancestors of file ", n);
}else{
blob_appendf(&title, "Ancestors of file ");
}
blob_appendf(&title,"%z%h</a>",
href("%R/file?name=%T&ci=%!S", zFilename, zUuid),
zFilename);
if( fShowId ) blob_appendf(&title, " (%d)", fnid);
blob_append(&title, origCheckin ? " between " : " from ", -1);
blob_appendf(&title, "check-in %z%S</a>", zLink, zUuid);
if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin);
fossil_free(zUuid);
if( origCheckin ){
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", origCheckin);
zLink = href("%R/info/%!S", zUuid);
blob_appendf(&title, " and check-in %z%S</a>", zLink, zUuid);
fossil_free(zUuid);
}
}else{
blob_appendf(&title, "History for ");
hyperlinked_path(zFilename, &title, 0, "tree", "", LINKPATH_FILE);
if( fShowId ) blob_appendf(&title, " (%d)", fnid);
}
if( uBg ){
blob_append(&title, " (color-coded by user)", -1);
}
@ <h2>%b(&title)</h2>
blob_reset(&title);
pGraph = graph_init();
@ <table id="timelineTable%d(iTableId)" class="timelineTable">
if( baseCheckin ){
db_prepare(&qparent,
"SELECT DISTINCT pid FROM mlink"
|
| ︙ | ︙ | |||
519 520 521 522 523 524 525 |
zTime[5] = 0;
if( frid==selRid ){
@ <tr class='timelineSelected'>
}else{
@ <tr>
}
@ <td class="timelineTime">\
| | | | | 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 |
zTime[5] = 0;
if( frid==selRid ){
@ <tr class='timelineSelected'>
}else{
@ <tr>
}
@ <td class="timelineTime">\
@ %z(href("%R/file?name=%T&ci=%!S",zFilename,zCkin))%s(zTime)</a></td>
@ <td class="timelineGraph"><div id="m%d(gidx)" class="tl-nodemark"></div>
@ </td>
if( zBgClr && zBgClr[0] ){
@ <td class="timeline%s(zStyle)Cell" id='mc%d(gidx)'>
}else{
@ <td class="timeline%s(zStyle)Cell">
}
if( tmFlags & TIMELINE_COMPACT ){
@ <span class='timelineCompactComment' data-id='%d(frid)'>
}else{
@ <span class='timeline%s(zStyle)Comment'>
if( (tmFlags & TIMELINE_VERBOSE)!=0 && zUuid ){
hyperlink_to_version(zUuid);
@ part of check-in \
hyperlink_to_version(zCkin);
}
}
@ %W(zCom)</span>
if( (tmFlags & TIMELINE_COMPACT)!=0 ){
@ <span class='timelineEllipsis' data-id='%d(frid)' \
@ id='ellipsis-%d(frid)'>...</span>
@ <span class='clutter timelineCompactDetail'
|
| ︙ | ︙ | |||
556 557 558 559 560 561 562 |
}
if( tmFlags & TIMELINE_COMPACT ){
cgi_printf("<span class='clutter' id='detail-%d'>",frid);
}
cgi_printf("<span class='timeline%sDetail'>", zStyle);
if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ) cgi_printf("(");
if( zUuid && (tmFlags & TIMELINE_VERBOSE)==0 ){
| | | | 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 |
}
if( tmFlags & TIMELINE_COMPACT ){
cgi_printf("<span class='clutter' id='detail-%d'>",frid);
}
cgi_printf("<span class='timeline%sDetail'>", zStyle);
if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ) cgi_printf("(");
if( zUuid && (tmFlags & TIMELINE_VERBOSE)==0 ){
@ file: %z(href("%R/file?name=%T&ci=%!S",zFilename,zCkin))[%S(zUuid)]</a>
if( fShowId ){
int srcId = delta_source_rid(frid);
if( srcId>0 ){
@ id: %d(frid)←%d(srcId)
}else{
@ id: %d(frid)
}
}
}
@ check-in: \
hyperlink_to_version(zCkin);
if( fShowId ){
@ (%d(fmid))
}
@ user: \
hyperlink_to_user(zUser, zDate, ",");
@ branch: %z(href("%R/timeline?t=%T",zBr))%h(zBr)</a>,
if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ){
|
| ︙ | ︙ | |||
616 617 618 619 620 621 622 623 624 625 626 627 628 629 |
@ [annotate]</a>
@ %z(href("%R/blame?filename=%h&checkin=%s",z,zCkin))
@ [blame]</a>
@ %z(href("%R/timeline?n=all&uf=%!S",zUuid))[check-ins using]</a>
if( fpid>0 ){
@ %z(href("%R/fdiff?v1=%!S&v2=%!S",zPUuid,zUuid))[diff]</a>
}
@ </span></span>
}
if( fDebug & FINFO_DEBUG_MLINK ){
int ii;
char *zAncLink;
@ <br />fid=%d(frid) pid=%d(fpid) mid=%d(fmid)
if( nParent>0 ){
| > > > | 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 |
@ [annotate]</a>
@ %z(href("%R/blame?filename=%h&checkin=%s",z,zCkin))
@ [blame]</a>
@ %z(href("%R/timeline?n=all&uf=%!S",zUuid))[check-ins using]</a>
if( fpid>0 ){
@ %z(href("%R/fdiff?v1=%!S&v2=%!S",zPUuid,zUuid))[diff]</a>
}
if( fileedit_is_editable(zFilename) ){
@ %z(href("%R/fileedit?filename=%T&checkin=%!S",zFilename,zCkin))[edit]</a>
}
@ </span></span>
}
if( fDebug & FINFO_DEBUG_MLINK ){
int ii;
char *zAncLink;
@ <br />fid=%d(frid) pid=%d(fpid) mid=%d(fmid)
if( nParent>0 ){
|
| ︙ | ︙ | |||
778 779 780 781 782 783 784 |
/* 5 */ " (SELECT uuid FROM blob WHERE rid=mlink.pmid),"
/* 6 */ " mperm,"
/* 7 */ " isaux"
" FROM mlink WHERE mid=%d ORDER BY 1",
mid
);
@ <h1>MLINK table for check-in %h(zCI)</h1>
| | | 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 |
/* 5 */ " (SELECT uuid FROM blob WHERE rid=mlink.pmid),"
/* 6 */ " mperm,"
/* 7 */ " isaux"
" FROM mlink WHERE mid=%d ORDER BY 1",
mid
);
@ <h1>MLINK table for check-in %h(zCI)</h1>
render_checkin_context(mid, 0, 1);
style_table_sorter();
@ <hr />
@ <div class='brlist'>
@ <table class='sortable' data-column-types='ttxtttt' data-init-sort='1'>
@ <thead><tr>
@ <th>File</th>
@ <th>Parent<br>Check-in</th>
|
| ︙ | ︙ |
| ︙ | ︙ | |||
55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
ForumEntry *pFirst; /* First entry in chronological order */
ForumEntry *pLast; /* Last entry in chronological order */
ForumEntry *pDisplay; /* Entries in display order */
ForumEntry *pTail; /* Last on the display list */
int mxIndent; /* Maximum indentation level */
};
#endif /* INTERFACE */
/*
** Delete a complete ForumThread and all its entries.
*/
static void forumthread_delete(ForumThread *pThread){
ForumEntry *pEntry, *pNext;
for(pEntry=pThread->pFirst; pEntry; pEntry = pNext){
| > > > > > > > > > > > > > > > > > | 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 |
ForumEntry *pFirst; /* First entry in chronological order */
ForumEntry *pLast; /* Last entry in chronological order */
ForumEntry *pDisplay; /* Entries in display order */
ForumEntry *pTail; /* Last on the display list */
int mxIndent; /* Maximum indentation level */
};
#endif /* INTERFACE */
/*
** Return true if the forum entry with the given rid has been
** subsequently edited.
*/
int forum_rid_has_been_edited(int rid){
static Stmt q;
int res;
db_static_prepare(&q,
"SELECT 1 FROM forumpost A, forumpost B"
" WHERE A.fpid=$rid AND B.froot=A.froot AND B.fprev=$rid"
);
db_bind_int(&q, "$rid", rid);
res = db_step(&q)==SQLITE_ROW;
db_reset(&q);
return res;
}
/*
** Delete a complete ForumThread and all its entries.
*/
static void forumthread_delete(ForumThread *pThread){
ForumEntry *pEntry, *pNext;
for(pEntry=pThread->pFirst; pEntry; pEntry = pNext){
|
| ︙ | ︙ | |||
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 |
** Construct a ForumThread object given the root record id.
*/
static ForumThread *forumthread_create(int froot, int computeHierarchy){
ForumThread *pThread;
ForumEntry *pEntry;
Stmt q;
int sid = 1;
pThread = fossil_malloc( sizeof(*pThread) );
memset(pThread, 0, sizeof(*pThread));
db_prepare(&q,
"SELECT fpid, firt, fprev, (SELECT uuid FROM blob WHERE rid=fpid)"
" FROM forumpost"
" WHERE froot=%d ORDER BY fmtime",
froot
);
while( db_step(&q)==SQLITE_ROW ){
pEntry = fossil_malloc( sizeof(*pEntry) );
memset(pEntry, 0, sizeof(*pEntry));
pEntry->fpid = db_column_int(&q, 0);
pEntry->firt = db_column_int(&q, 1);
pEntry->fprev = db_column_int(&q, 2);
pEntry->zUuid = fossil_strdup(db_column_text(&q,3));
pEntry->mfirt = pEntry->firt;
pEntry->sid = sid++;
pEntry->pPrev = pThread->pLast;
pEntry->pNext = 0;
if( pThread->pLast==0 ){
pThread->pFirst = pEntry;
}else{
pThread->pLast->pNext = pEntry;
}
pThread->pLast = pEntry;
}
db_finalize(&q);
/* Establish which entries are the latest edit. After this loop
** completes, entries that have non-NULL pLeaf should not be
** displayed.
*/
for(pEntry=pThread->pFirst; pEntry; pEntry=pEntry->pNext){
if( pEntry->fprev ){
| > > > > > > > | 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 |
** Construct a ForumThread object given the root record id.
*/
static ForumThread *forumthread_create(int froot, int computeHierarchy){
ForumThread *pThread;
ForumEntry *pEntry;
Stmt q;
int sid = 1;
Bag seen = Bag_INIT;
pThread = fossil_malloc( sizeof(*pThread) );
memset(pThread, 0, sizeof(*pThread));
db_prepare(&q,
"SELECT fpid, firt, fprev, (SELECT uuid FROM blob WHERE rid=fpid)"
" FROM forumpost"
" WHERE froot=%d ORDER BY fmtime",
froot
);
while( db_step(&q)==SQLITE_ROW ){
pEntry = fossil_malloc( sizeof(*pEntry) );
memset(pEntry, 0, sizeof(*pEntry));
pEntry->fpid = db_column_int(&q, 0);
pEntry->firt = db_column_int(&q, 1);
pEntry->fprev = db_column_int(&q, 2);
pEntry->zUuid = fossil_strdup(db_column_text(&q,3));
pEntry->mfirt = pEntry->firt;
pEntry->sid = sid++;
pEntry->pPrev = pThread->pLast;
pEntry->pNext = 0;
bag_insert(&seen, pEntry->fpid);
if( pThread->pLast==0 ){
pThread->pFirst = pEntry;
}else{
pThread->pLast->pNext = pEntry;
}
if( pEntry->firt && !bag_find(&seen,pEntry->firt) ){
pEntry->firt = froot;
pEntry->mfirt = froot;
}
pThread->pLast = pEntry;
}
db_finalize(&q);
bag_clear(&seen);
/* Establish which entries are the latest edit. After this loop
** completes, entries that have non-NULL pLeaf should not be
** displayed.
*/
for(pEntry=pThread->pFirst; pEntry; pEntry=pEntry->pNext){
if( pEntry->fprev ){
|
| ︙ | ︙ | |||
197 198 199 200 201 202 203 204 205 206 207 |
forumentry_add_to_display(pThread, pEntry);
forumthread_display_order(pThread, pEntry);
}
/* Return the result */
return pThread;
}
/*
** COMMAND: test-forumthread
**
| > > > > > > > > > > > > > > > > > > > > > > > > > > | | > > > > > > > > > | | | | | > > > > > > > > | 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 |
forumentry_add_to_display(pThread, pEntry);
forumthread_display_order(pThread, pEntry);
}
/* Return the result */
return pThread;
}
/*
** List all forum threads to standard output.
*/
static void forum_thread_list(void){
Stmt q;
db_prepare(&q,
" SELECT"
" datetime(max(fmtime)),"
" sum(fprev IS NULL),"
" froot"
" FROM forumpost"
" GROUP BY froot"
" ORDER BY 1;"
);
fossil_print(" id cnt most recent post\n");
fossil_print("------ ---- -------------------\n");
while( db_step(&q)==SQLITE_ROW ){
fossil_print("%6d %4d %s\n",
db_column_int(&q, 2),
db_column_int(&q, 1),
db_column_text(&q, 0)
);
}
db_finalize(&q);
}
/*
** COMMAND: test-forumthread
**
** Usage: %fossil test-forumthread [THREADID]
**
** Display a summary of all messages on a thread THREADID. If the
** THREADID argument is omitted, then show a list of all threads.
**
** This command is intended for testing an analysis only.
*/
void forumthread_cmd(void){
int fpid;
int froot;
const char *zName;
ForumThread *pThread;
ForumEntry *p;
db_find_and_open_repository(0,0);
verify_all_options();
if( g.argc==2 ){
forum_thread_list();
return;
}
if( g.argc!=3 ) usage("THREADID");
zName = g.argv[2];
fpid = symbolic_name_to_rid(zName, "f");
if( fpid<=0 ){
fpid = db_int(0, "SELECT rid FROM blob WHERE rid=%d", atoi(zName));
}
if( fpid<=0 ){
fossil_fatal("Unknown or ambiguous forum id: \"%s\"", zName);
}
froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
if( froot==0 ){
fossil_fatal("Not a forum post: \"%s\"", zName);
}
fossil_print("fpid = %d\n", fpid);
fossil_print("froot = %d\n", froot);
pThread = forumthread_create(froot, 1);
fossil_print("Chronological:\n");
fossil_print(
/* 0 1 2 3 4 5 6 7 */
/* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
" sid fpid firt fprev mfirt pLeaf nReply hash\n");
for(p=pThread->pFirst; p; p=p->pNext){
fossil_print("%4d %9d %9d %9d %9d %9d %6d %8.8s\n", p->sid,
p->fpid, p->firt, p->fprev, p->mfirt, p->pLeaf ? p->pLeaf->fpid : 0,
p->nReply, p->zUuid);
}
fossil_print("\nDisplay\n");
for(p=pThread->pDisplay; p; p=p->pDisplay){
fossil_print("%*s", (p->nIndent-1)*3, "");
if( p->pLeaf ){
fossil_print("%d->%d\n", p->fpid, p->pLeaf->fpid);
}else{
fossil_print("%d\n", p->fpid);
}
}
forumthread_delete(pThread);
}
/*
** Render a forum post for display
*/
void forum_render(
const char *zTitle, /* The title. Might be NULL for no title */
const char *zMimetype, /* Mimetype of the message */
const char *zContent, /* Content of the message */
const char *zClass, /* Put in a <div> if not NULL */
int bScroll /* Large message content scrolls if true */
){
if( zClass ){
@ <div class='%s(zClass)'>
}
if( zTitle ){
if( zTitle[0] ){
@ <h1>%h(zTitle)</h1>
}else{
@ <h1><i>Deleted</i></h1>
}
}
if( zContent && zContent[0] ){
Blob x;
if( bScroll ){
@ <div class='forumPostBody'>
}else{
@ <div class='forumPostFullBody'>
}
blob_init(&x, 0, 0);
blob_append(&x, zContent, -1);
safe_html_context(DOCSRC_FORUM);
wiki_render_by_mimetype(&x, zMimetype);
blob_reset(&x);
@ </div>
}else{
@ <i>Deleted</i>
}
if( zClass ){
@ </div>
}
}
|
| ︙ | ︙ | |||
300 301 302 303 304 305 306 307 308 309 310 | @ <br> @ <label><input type="checkbox" name="trust"> @ Trust user "%h(pPost->zUser)" @ so that future posts by "%h(pPost->zUser)" do not require moderation. @ </label> @ <input type="hidden" name="trustuser" value="%h(pPost->zUser)"> } /* ** Display all posts in a forum thread in chronological order */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > | > > > | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | < < | | > | > | 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 |
@ <br>
@ <label><input type="checkbox" name="trust">
@ Trust user "%h(pPost->zUser)"
@ so that future posts by "%h(pPost->zUser)" do not require moderation.
@ </label>
@ <input type="hidden" name="trustuser" value="%h(pPost->zUser)">
}
/*
** Compute a display name from a login name.
**
** If the input login is found in the USER table, then check the USER.INFO
** field to see if it has display-name followed by an email address.
** If it does, that becomes the new display name. If not, let the display
** name just be the login.
**
** Space to hold the returned name is obtained from fossil_strdup() or
** mprintf() and should be freed by the caller.
*/
char *display_name_from_login(const char *zLogin){
static Stmt q;
char *zResult;
db_static_prepare(&q,
"SELECT display_name(info) FROM user WHERE login=$login"
);
db_bind_text(&q, "$login", zLogin);
if( db_step(&q)==SQLITE_ROW && db_column_type(&q,0)==SQLITE_TEXT ){
const char *zDisplay = db_column_text(&q,0);
if( fossil_strcmp(zDisplay,zLogin)==0 ){
zResult = fossil_strdup(zLogin);
}else{
zResult = mprintf("%s (%s)", zDisplay, zLogin);
}
}else{
zResult = fossil_strdup(zLogin);
}
db_reset(&q);
return zResult;
}
/*
** Display all posts in a forum thread in chronological order
*/
static void forum_display_chronological(int froot, int target, int bRawMode){
ForumThread *pThread = forumthread_create(froot, 0);
ForumEntry *p;
int notAnon = login_is_individual();
char cMode = bRawMode ? 'r' : 'c';
for(p=pThread->pFirst; p; p=p->pNext){
char *zDate;
Manifest *pPost;
int isPrivate; /* True for posts awaiting moderation */
int sameUser; /* True if author is also the reader */
const char *zUuid;
char *zDisplayName; /* The display name */
int sid;
pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
if( pPost==0 ) continue;
if( p->fpid==target ){
@ <div id="forum%d(p->fpid)" class="forumTime forumSel">
}else if( p->pLeaf!=0 ){
@ <div id="forum%d(p->fpid)" class="forumTime forumObs">
}else{
@ <div id="forum%d(p->fpid)" class="forumTime">
}
if( pPost->zThreadTitle ){
@ <h1>%h(pPost->zThreadTitle)</h1>
}
zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
zDisplayName = display_name_from_login(pPost->zUser);
sid = p->pEdit ? p->pEdit->sid : p->sid;
@ <h3 class='forumPostHdr'>(%d(sid)) By %h(zDisplayName) on %h(zDate)
fossil_free(zDisplayName);
fossil_free(zDate);
if( p->pEdit ){
@ edit of %z(href("%R/forumpost/%S?t=%c",p->pEdit->zUuid,cMode))\
@ %d(p->pEdit->sid)</a>
}
if( g.perm.Debug ){
@ <span class="debug">\
@ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span>
}
if( p->firt ){
ForumEntry *pIrt = p->pPrev;
while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev;
if( pIrt ){
@ in reply to %z(href("%R/forumpost/%S?t=%c",pIrt->zUuid,cMode))\
@ %d(pIrt->sid)</a>
}
}
zUuid = p->zUuid;
if( p->pLeaf ){
@ updated by %z(href("%R/forumpost/%S?t=%c",p->pLeaf->zUuid,cMode))\
@ %d(p->pLeaf->sid)</a>
zUuid = p->pLeaf->zUuid;
}
if( p->fpid!=target ){
@ %z(href("%R/forumpost/%S?t=%c",zUuid,cMode))[link]</a>
}
if( !bRawMode ){
@ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
}
isPrivate = content_is_private(p->fpid);
sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
@ </h3>
if( isPrivate && !g.perm.ModForum && !sameUser ){
@ <p><span class="modpending">Awaiting Moderator Approval</span></p>
}else{
const char *zMimetype;
if( bRawMode ){
zMimetype = "text/plain";
}else if( p->pLeaf!=0 ){
zMimetype = "text/plain";
}else{
zMimetype = pPost->zMimetype;
}
forum_render(0, zMimetype, pPost->zWiki, 0, 1);
}
if( g.perm.WrForum && p->pLeaf==0 ){
int sameUser = login_is_individual()
&& fossil_strcmp(pPost->zUser, g.zLogin)==0;
@ <p><form action="%R/forumedit" method="POST">
@ <input type="hidden" name="fpid" value="%s(p->zUuid)">
if( !isPrivate ){
/* Reply and Edit are only available if the post has already
** been approved */
@ <input type="submit" name="reply" value="Reply">
if( g.perm.Admin || sameUser ){
@ <input type="submit" name="edit" value="Edit">
@ <input type="submit" name="nullout" value="Delete">
}
}else if( g.perm.ModForum ){
/* Provide moderators with moderation buttons for posts that
** are pending moderation */
@ <input type="submit" name="approve" value="Approve">
@ <input type="submit" name="reject" value="Reject">
generateTrustControls(pPost);
}else if( sameUser ){
/* A post that is pending moderation can be deleted by the
** person who originally submitted the post */
@ <input type="submit" name="reject" value="Delete">
}
@ </form></p>
}
manifest_destroy(pPost);
@ </div>
}
/* Undocumented "threadtable" query parameter causes thread table
** to be displayed for debugging purposes.
*/
if( PB("threadtable") ){
@ <hr>
@ <table border="1" cellpadding="3" cellspacing="0">
@ <tr><th>sid<th>fpid<th>firt<th>fprev<th>mfirt<th>pLeaf<th>nReply<th>hash
for(p=pThread->pFirst; p; p=p->pNext){
@ <tr><td>%d(p->sid)<td>%d(p->fpid)<td>%d(p->firt)\
@ <td>%d(p->fprev)<td>%d(p->mfirt)\
@ <td>%d(p->pLeaf?p->pLeaf->fpid:0)<td>%d(p->nReply)\
@ <td>%S(p->zUuid)</tr>
}
@ </table>
}
forumthread_delete(pThread);
}
/*
** Display all the edit history of post "target".
*/
static void forum_display_history(int froot, int target, int bRawMode){
ForumThread *pThread = forumthread_create(froot, 0);
ForumEntry *p;
int notAnon = login_is_individual();
char cMode = bRawMode ? 'r' : 'c';
ForumEntry *pLeaf = 0;
int cnt = 0;
for(p=pThread->pFirst; p; p=p->pNext){
if( p->fpid==target ){
pLeaf = p->pLeaf ? p->pLeaf : p;
break;
}
}
for(p=pThread->pFirst; p; p=p->pNext){
char *zDate;
Manifest *pPost;
int isPrivate; /* True for posts awaiting moderation */
int sameUser; /* True if author is also the reader */
const char *zUuid;
char *zDisplayName; /* The display name */
if( p->fpid!=pLeaf->fpid && p->pLeaf!=pLeaf ) continue;
cnt++;
pPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
if( pPost==0 ) continue;
@ <div id="forum%d(p->fpid)" class="forumTime">
zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
zDisplayName = display_name_from_login(pPost->zUser);
@ <h3 class='forumPostHdr'>(%d(p->sid)) By %h(zDisplayName) on %h(zDate)
fossil_free(zDisplayName);
fossil_free(zDate);
if( g.perm.Debug ){
@ <span class="debug">\
@ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span>
}
if( p->firt && cnt==1 ){
ForumEntry *pIrt = p->pPrev;
while( pIrt && pIrt->fpid!=p->firt ) pIrt = pIrt->pPrev;
if( pIrt ){
@ in reply to %z(href("%R/forumpost/%S?t=%c",pIrt->zUuid,cMode))\
@ %d(pIrt->sid)</a>
}
}
zUuid = p->zUuid;
@ %z(href("%R/forumpost/%S?t=c",zUuid))[link]</a>
if( !bRawMode ){
@ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
}
isPrivate = content_is_private(p->fpid);
sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
@ </h3>
if( isPrivate && !g.perm.ModForum && !sameUser ){
@ <p><span class="modpending">Awaiting Moderator Approval</span></p>
}else{
forum_render(0, bRawMode?"text/plain":pPost->zMimetype, pPost->zWiki,
0, 1);
}
if( g.perm.WrForum && p->pLeaf==0 ){
int sameUser = login_is_individual()
&& fossil_strcmp(pPost->zUser, g.zLogin)==0;
@ <p><form action="%R/forumedit" method="POST">
@ <input type="hidden" name="fpid" value="%s(p->zUuid)">
if( !isPrivate ){
|
| ︙ | ︙ | |||
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 |
}
while( iIndentScale>1 && iIndentScale*pThread->mxIndent>25 ){
iIndentScale--;
}
for(p=pThread->pDisplay; p; p=p->pDisplay){
int isPrivate; /* True for posts awaiting moderation */
int sameUser; /* True if reader is also the poster */
pOPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
if( p->pLeaf ){
fpid = p->pLeaf->fpid;
zUuid = p->pLeaf->zUuid;
pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
}else{
fpid = p->fpid;
zUuid = p->zUuid;
pPost = pOPost;
}
zSel = p->fpid==target ? " forumSel" : "";
if( p->nIndent==1 ){
@ <div id='forum%d(fpid)' class='forumHierRoot%s(zSel)'>
}else{
@ <div id='forum%d(fpid)' class='forumHier%s(zSel)' \
@ style='margin-left: %d((p->nIndent-1)*iIndentScale)ex;'>
}
| > < > > | > | | > > > | > | | 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 |
}
while( iIndentScale>1 && iIndentScale*pThread->mxIndent>25 ){
iIndentScale--;
}
for(p=pThread->pDisplay; p; p=p->pDisplay){
int isPrivate; /* True for posts awaiting moderation */
int sameUser; /* True if reader is also the poster */
char *zDisplayName; /* User name to be displayed */
pOPost = manifest_get(p->fpid, CFTYPE_FORUM, 0);
if( p->pLeaf ){
fpid = p->pLeaf->fpid;
zUuid = p->pLeaf->zUuid;
pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
}else{
fpid = p->fpid;
zUuid = p->zUuid;
pPost = pOPost;
}
zSel = p->fpid==target ? " forumSel" : "";
if( p->nIndent==1 ){
@ <div id='forum%d(fpid)' class='forumHierRoot%s(zSel)'>
}else{
@ <div id='forum%d(fpid)' class='forumHier%s(zSel)' \
@ style='margin-left: %d((p->nIndent-1)*iIndentScale)ex;'>
}
if( pPost==0 ) continue;
if( pPost->zThreadTitle ){
@ <h1>%h(pPost->zThreadTitle)</h1>
}
zDate = db_text(0, "SELECT datetime(%.17g)", pOPost->rDate);
zDisplayName = display_name_from_login(pOPost->zUser);
@ <h3 class='forumPostHdr'>\
@ (%d(p->sid)) By %h(zDisplayName) on %h(zDate)
fossil_free(zDisplayName);
fossil_free(zDate);
if( g.perm.Debug ){
@ <span class="debug">\
@ <a href="%R/artifact/%h(p->zUuid)">(artifact-%d(p->fpid))</a></span>
}
if( p->pLeaf ){
zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
if( fossil_strcmp(pOPost->zUser,pPost->zUser)==0 ){
@ and edited on %h(zDate)
}else{
@ as edited by %h(pPost->zUser) on %h(zDate)
}
fossil_free(zDate);
if( g.perm.Debug ){
@ <span class="debug">\
@ <a href="%R/artifact/%h(p->pLeaf->zUuid)">\
@ (artifact-%d(p->pLeaf->fpid))</a></span>
}
@ %z(href("%R/forumpost/%S?t=y",p->zUuid))[history]</a>
manifest_destroy(pOPost);
}
if( fpid!=target ){
@ %z(href("%R/forumpost/%S",zUuid))[link]</a>
}
@ %z(href("%R/forumpost/%S?raw",zUuid))[source]</a>
if( p->firt ){
ForumEntry *pIrt = p->pPrev;
while( pIrt && pIrt->fpid!=p->mfirt ) pIrt = pIrt->pPrev;
if( pIrt ){
@ in reply to %z(href("%R/forumpost/%S?t=h",pIrt->zUuid))\
@ %d(pIrt->sid)</a>
}
}
@ </h3>
isPrivate = content_is_private(fpid);
sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
if( isPrivate && !g.perm.ModForum && !sameUser ){
@ <p><span class="modpending">Awaiting Moderator Approval</span></p>
}else{
forum_render(0, pPost->zMimetype, pPost->zWiki, 0, 1);
}
if( g.perm.WrForum ){
@ <p><form action="%R/forumedit" method="POST">
@ <input type="hidden" name="fpid" value="%s(zUuid)">
if( !isPrivate ){
/* Reply and Edit are only available if the post has already
** been approved */
|
| ︙ | ︙ | |||
522 523 524 525 526 527 528 | ** it's entire thread. The selected posting is enclosed within ** <div class='forumSel'>...</div>. Javascript is used to move the ** selected posting into view after the page loads. ** ** Query parameters: ** ** name=X REQUIRED. The hash of the post to display | | > | > > > > > > > > > > > > > > > > > > > > > > > > > | > | > > > > > < | > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > | 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 |
** it's entire thread. The selected posting is enclosed within
** <div class='forumSel'>...</div>. Javascript is used to move the
** selected posting into view after the page loads.
**
** Query parameters:
**
** name=X REQUIRED. The hash of the post to display
** t=MODE Display mode.
** 'c' for chronological
** 'h' for hierarchical
** 'a' for automatic
** 'r' for raw
** 'y' for history of post X only
** raw If present, show only the post specified and
** show its original unformatted source text.
*/
void forumpost_page(void){
forumthread_page();
}
/*
** Add an appropriate style_header() to include title of the
** given forum post.
*/
static int forumthread_page_header(int froot, int fpid){
char *zThreadTitle = 0;
zThreadTitle = db_text("",
"SELECT"
" substr(event.comment,instr(event.comment,':')+2)"
" FROM forumpost, event"
" WHERE event.objid=forumpost.fpid"
" AND forumpost.fpid=%d;",
fpid
);
style_header("%s%s", zThreadTitle, zThreadTitle[0] ? "" : "Forum");
fossil_free(zThreadTitle);
return 0;
}
/*
** WEBPAGE: forumthread
**
** Show all forum messages associated with a particular message thread.
** The result is basically the same as /forumpost except that none of
** the postings in the thread are selected.
**
** Query parameters:
**
** name=X REQUIRED. The hash of any post of the thread.
** t=MODE Display mode. MODE is...
** 'c' for chronological, or
** 'h' for hierarchical, or
** 'a' for automatic, or
** 'r' for raw.
** raw Show only the post given by name= and show it unformatted
** hist Show only the edit history for the name= post
*/
void forumthread_page(void){
int fpid;
int froot;
const char *zName = P("name");
const char *zMode = PD("t","a");
int bRaw = PB("raw");
login_check_credentials();
if( !g.perm.RdForum ){
login_needed(g.anon.RdForum);
return;
}
if( zName==0 ){
webpage_error("Missing \"name=\" query parameter");
}
fpid = symbolic_name_to_rid(zName, "f");
if( fpid<=0 ){
webpage_error("Unknown or ambiguous forum id: \"%s\"", zName);
}
froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
if( froot==0 ){
webpage_error("Not a forum post: \"%s\"", zName);
}
if( fossil_strcmp(g.zPath,"forumthread")==0 ) fpid = 0;
if( zMode[0]=='a' ){
if( cgi_from_mobile() ){
zMode = "c"; /* Default to chronological on mobile */
}else{
zMode = "h";
}
}
if( zMode[0]!='y' ){
forumthread_page_header(froot, fpid);
}
if( bRaw && fpid ){
Manifest *pPost;
pPost = manifest_get(fpid, CFTYPE_FORUM, 0);
if( pPost==0 ){
@ <p>No such forum post: %h(zName)
}else{
int isPrivate = content_is_private(fpid);
int notAnon = login_is_individual();
int sameUser = notAnon && fossil_strcmp(pPost->zUser, g.zLogin)==0;
if( isPrivate && !g.perm.ModForum && !sameUser ){
@ <p><span class="modpending">Awaiting Moderator Approval</span></p>
}else{
forum_render(0, "text/plain", pPost->zWiki, 0, 0);
}
manifest_destroy(pPost);
}
}else if( zMode[0]=='c' ){
style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName);
style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName);
forum_display_chronological(froot, fpid, 0);
}else if( zMode[0]=='r' ){
style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName);
style_submenu_element("Hierarchical", "%R/%s/%s?t=h", g.zPath, zName);
forum_display_chronological(froot, fpid, 1);
}else if( zMode[0]=='y' ){
style_header("Edit History Of A Forum Post");
style_submenu_element("Complete Thread", "%R/%s/%s?t=a", g.zPath, zName);
forum_display_history(froot, fpid, 1);
}else{
style_submenu_element("Chronological", "%R/%s/%s?t=c", g.zPath, zName);
style_submenu_element("Unformatted", "%R/%s/%s?t=r", g.zPath, zName);
forum_display_hierarchical(froot, fpid);
}
style_load_js("forum.js");
style_footer();
}
/*
** Return true if a forum post should be moderated.
*/
static int forum_need_moderation(void){
if( P("domod") ) return 1;
if( g.perm.WrTForum ) return 0;
if( g.perm.ModForum ) return 0;
return 1;
}
/*
** Return true if the string is white-space only.
*/
static int whitespace_only(const char *z){
if( z==0 ) return 1;
while( z[0] && fossil_isspace(z[0]) ){ z++; }
return z[0]==0;
}
/*
** Add a new Forum Post artifact to the repository.
**
** Return true if a redirect occurs.
*/
static int forum_post(
const char *zTitle, /* Title. NULL for replies */
int iInReplyTo, /* Post replying to. 0 for new threads */
int iEdit, /* Post being edited, or zero for a new post */
const char *zUser, /* Username. NULL means use login name */
const char *zMimetype, /* Mimetype of content. */
const char *zContent /* Content */
){
char *zDate;
char *zI;
char *zG;
int iBasis;
Blob x, cksum, formatCheck, errMsg;
Manifest *pPost;
int nContent = zContent ? (int)strlen(zContent) : 0;
schema_forum();
if( iEdit==0 && whitespace_only(zContent) ){
return 0;
}
if( iInReplyTo==0 && iEdit>0 ){
iBasis = iEdit;
iInReplyTo = db_int(0, "SELECT firt FROM forumpost WHERE fpid=%d", iEdit);
}else{
iBasis = iInReplyTo;
}
webpage_assert( (zTitle==0)+(iInReplyTo==0)==1 );
|
| ︙ | ︙ | |||
658 659 660 661 662 663 664 |
if( login_is_nobody() ){
zUser = "anonymous";
}else{
zUser = login_name();
}
}
blob_appendf(&x, "U %F\n", zUser);
| | | 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 |
if( login_is_nobody() ){
zUser = "anonymous";
}else{
zUser = login_name();
}
}
blob_appendf(&x, "U %F\n", zUser);
blob_appendf(&x, "W %d\n%s\n", nContent, zContent);
md5sum_blob(&x, &cksum);
blob_appendf(&x, "Z %b\n", &cksum);
blob_reset(&cksum);
/* Verify that the artifact we are creating is well-formed */
blob_init(&formatCheck, 0, 0);
blob_init(&errMsg, 0, 0);
|
| ︙ | ︙ | |||
682 683 684 685 686 687 688 |
@ <div class='debug'>
@ This is the artifact that would have been generated:
@ <pre>%h(blob_str(&x))</pre>
@ </div>
blob_reset(&x);
return 0;
}else{
| | > > | > | | | | 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 |
@ <div class='debug'>
@ This is the artifact that would have been generated:
@ <pre>%h(blob_str(&x))</pre>
@ </div>
blob_reset(&x);
return 0;
}else{
int nrid = wiki_put(&x, iEdit>0 ? iEdit : 0,
forum_need_moderation());
blob_reset(&x);
cgi_redirectf("%R/forumpost/%S", rid_to_uuid(nrid));
return 1;
}
}
/*
** Paint the form elements for entering a Forum post
*/
static void forum_entry_widget(
const char *zTitle,
const char *zMimetype,
const char *zContent
){
if( zTitle ){
@ Title: <input type="input" name="title" value="%h(zTitle)" size="50"
@ maxlength="125"><br>
}
@ %z(href("%R/markup_help"))Markup style</a>:
mimetype_option_menu(zMimetype);
@ <br><textarea aria-label="Content:" name="content" class="wikiedit" \
@ cols="80" rows="25" wrap="virtual">%h(zContent)</textarea><br>
}
/*
** WEBPAGE: forumnew
** WEBPAGE: forumedit
**
** Start a new thread on the forum or reply to an existing thread.
|
| ︙ | ︙ | |||
788 789 790 791 792 793 794 |
const char *zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE);
const char *zContent = PDT("content","");
login_check_credentials();
if( !g.perm.WrForum ){
login_needed(g.anon.WrForum);
return;
}
| | | | | | > | 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 |
const char *zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE);
const char *zContent = PDT("content","");
login_check_credentials();
if( !g.perm.WrForum ){
login_needed(g.anon.WrForum);
return;
}
if( P("submit") && cgi_csrf_safe(1) ){
if( forum_post(zTitle, 0, 0, 0, zMimetype, zContent) ) return;
}
if( P("preview") && !whitespace_only(zContent) ){
@ <h1>Preview:</h1>
forum_render(zTitle, zMimetype, zContent, "forumEdit", 1);
}
style_header("New Forum Thread");
@ <form action="%R/forume1" method="POST">
@ <h1>New Thread:</h1>
forum_from_line();
forum_entry_widget(zTitle, zMimetype, zContent);
@ <input type="submit" name="preview" value="Preview">
if( P("preview") && !whitespace_only(zContent) ){
@ <input type="submit" name="submit" value="Submit">
}else{
@ <input type="submit" name="submit" value="Submit" disabled>
}
if( g.perm.Debug ){
/* Give extra control over the post to users with the special
* Debug capability, which includes Admin and Setup users */
@ <div class="debug">
@ <label><input type="checkbox" name="dryrun" %s(PCK("dryrun"))> \
@ Dry run</label>
@ <br><label><input type="checkbox" name="domod" %s(PCK("domod"))> \
@ Require moderator approval</label>
@ <br><label><input type="checkbox" name="showqp" %s(PCK("showqp"))> \
@ Show query parameters</label>
|
| ︙ | ︙ | |||
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 |
** Edit an existing forum message.
** Query parameters:
**
** fpid=X Hash of the post to be editted. REQUIRED
*/
void forumedit_page(void){
int fpid;
Manifest *pPost = 0;
const char *zMimetype = 0;
const char *zContent = 0;
const char *zTitle = 0;
int isCsrfSafe;
int isDelete = 0;
login_check_credentials();
if( !g.perm.WrForum ){
login_needed(g.anon.WrForum);
return;
}
fpid = symbolic_name_to_rid(PD("fpid",""), "f");
if( fpid<=0 || (pPost = manifest_get(fpid, CFTYPE_FORUM, 0))==0 ){
webpage_error("Missing or invalid fpid query parameter");
}
if( P("cancel") ){
cgi_redirectf("%R/forumpost/%S",P("fpid"));
return;
}
isCsrfSafe = cgi_csrf_safe(1);
if( g.perm.ModForum && isCsrfSafe ){
if( P("approve") ){
const char *zUserToTrust;
| > > > > > > > | | 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 |
** Edit an existing forum message.
** Query parameters:
**
** fpid=X Hash of the post to be editted. REQUIRED
*/
void forumedit_page(void){
int fpid;
int froot;
Manifest *pPost = 0;
Manifest *pRootPost = 0;
const char *zMimetype = 0;
const char *zContent = 0;
const char *zTitle = 0;
char *zDate = 0;
int isCsrfSafe;
int isDelete = 0;
login_check_credentials();
if( !g.perm.WrForum ){
login_needed(g.anon.WrForum);
return;
}
fpid = symbolic_name_to_rid(PD("fpid",""), "f");
if( fpid<=0 || (pPost = manifest_get(fpid, CFTYPE_FORUM, 0))==0 ){
webpage_error("Missing or invalid fpid query parameter");
}
froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
if( froot==0 || (pRootPost = manifest_get(froot, CFTYPE_FORUM, 0))==0 ){
webpage_error("fpid does not appear to be a forum post: \"%d\"", fpid);
}
if( P("cancel") ){
cgi_redirectf("%R/forumpost/%S",P("fpid"));
return;
}
isCsrfSafe = cgi_csrf_safe(1);
if( g.perm.ModForum && isCsrfSafe ){
if( P("approve") ){
const char *zUserToTrust;
moderation_approve('f', fpid);
if( g.perm.AdminForum
&& PB("trust")
&& (zUserToTrust = P("trustuser"))!=0
){
db_multi_exec("UPDATE user SET cap=cap||'4' "
"WHERE login=%Q AND cap NOT GLOB '*4*'",
zUserToTrust);
|
| ︙ | ︙ | |||
884 885 886 887 888 889 890 |
}else{
cgi_redirectf("%R/forum");
}
return;
}
}
isDelete = P("nullout")!=0;
| | > > > > < | | | | | | | | > > > > | > > > > > | | | | | | > | 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 |
}else{
cgi_redirectf("%R/forum");
}
return;
}
}
isDelete = P("nullout")!=0;
if( P("submit")
&& isCsrfSafe
&& (zContent = PDT("content",""))!=0
&& (!whitespace_only(zContent) || isDelete)
){
int done = 1;
const char *zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE);
if( P("reply") ){
done = forum_post(0, fpid, 0, 0, zMimetype, zContent);
}else if( P("edit") || isDelete ){
done = forum_post(P("title"), 0, fpid, 0, zMimetype, zContent);
}else{
webpage_error("Missing 'reply' query parameter");
}
if( done ) return;
}
if( isDelete ){
zMimetype = "text/x-fossil-wiki";
zContent = "";
if( pPost->zThreadTitle ) zTitle = "";
style_header("Delete %s", zTitle ? "Post" : "Reply");
@ <h1>Original Post:</h1>
forum_render(pPost->zThreadTitle, pPost->zMimetype, pPost->zWiki,
"forumEdit", 1);
@ <h1>Change Into:</h1>
forum_render(zTitle, zMimetype, zContent,"forumEdit", 1);
@ <form action="%R/forume2" method="POST">
@ <input type="hidden" name="fpid" value="%h(P("fpid"))">
@ <input type="hidden" name="nullout" value="1">
@ <input type="hidden" name="mimetype" value="%h(zMimetype)">
@ <input type="hidden" name="content" value="%h(zContent)">
if( zTitle ){
@ <input aria-label="Title" type="hidden" name="title" value="%h(zTitle)">
}
}else if( P("edit") ){
/* Provide an edit to the fpid post */
zMimetype = P("mimetype");
zContent = PT("content");
zTitle = P("title");
if( zContent==0 ) zContent = fossil_strdup(pPost->zWiki);
if( zMimetype==0 ) zMimetype = fossil_strdup(pPost->zMimetype);
if( zTitle==0 && pPost->zThreadTitle!=0 ){
zTitle = fossil_strdup(pPost->zThreadTitle);
}
style_header("Edit %s", zTitle ? "Post" : "Reply");
@ <h2>Original Post:</h2>
forum_render(pPost->zThreadTitle, pPost->zMimetype, pPost->zWiki,
"forumEdit", 1);
if( P("preview") ){
@ <h2>Preview of Edited Post:</h2>
forum_render(zTitle, zMimetype, zContent,"forumEdit", 1);
}
@ <h2>Revised Message:</h2>
@ <form action="%R/forume2" method="POST">
@ <input type="hidden" name="fpid" value="%h(P("fpid"))">
@ <input type="hidden" name="edit" value="1">
forum_from_line();
forum_entry_widget(zTitle, zMimetype, zContent);
}else{
/* Reply */
char *zDisplayName;
zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE);
zContent = PDT("content","");
style_header("Reply");
if( pRootPost->zThreadTitle ){
@ <h1>Thread: %h(pRootPost->zThreadTitle)</h1>
}
@ <h2>Replying To:</h2>
zDate = db_text(0, "SELECT datetime(%.17g)", pPost->rDate);
zDisplayName = display_name_from_login(pPost->zUser);
@ <h3 class='forumPostHdr'>By %h(zDisplayName) on %h(zDate)</h3>
fossil_free(zDisplayName);
fossil_free(zDate);
forum_render(0, pPost->zMimetype, pPost->zWiki, "forumEdit", 1);
if( P("preview") && !whitespace_only(zContent) ){
@ <h2>Preview:</h2>
forum_render(0, zMimetype,zContent, "forumEdit", 1);
}
@ <h2>Enter Reply:</h2>
@ <form action="%R/forume2" method="POST">
@ <input type="hidden" name="fpid" value="%h(P("fpid"))">
@ <input type="hidden" name="reply" value="1">
forum_from_line();
forum_entry_widget(0, zMimetype, zContent);
}
if( !isDelete ){
@ <input type="submit" name="preview" value="Preview">
}
@ <input type="submit" name="cancel" value="Cancel">
if( (P("preview") && !whitespace_only(zContent)) || isDelete ){
@ <input type="submit" name="submit" value="Submit">
}
if( g.perm.Debug ){
/* For the test-forumnew page add these extra debugging controls */
@ <div class="debug">
@ <label><input type="checkbox" name="dryrun" %s(PCK("dryrun"))> \
@ Dry run</label>
@ <br><label><input type="checkbox" name="domod" %s(PCK("domod"))> \
@ Require moderator approval</label>
@ <br><label><input type="checkbox" name="showqp" %s(PCK("showqp"))> \
@ Show query parameters</label>
@ </div>
}
@ </form>
style_footer();
}
/*
** WEBPAGE: forummain
** WEBPAGE: forum
**
** The main page for the forum feature. Show a list of recent forum
** threads. Also show a search box at the top if search is enabled,
** and a button for creating a new thread, if enabled.
**
** Query parameters:
|
| ︙ | ︙ |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
"use strict";
(function(global){
/* Bootstrapping bits for the global.fossil object. Must be
loaded after style.c:style_emit_script_tag() has initialized
that object.
*/
const F = global.fossil;
/**
Returns the current time in something approximating
ISO-8601 format.
*/
const timestring = function f(){
if(!f.rx1){
f.rx1 = /\.\d+Z$/;
}
const d = new Date();
return d.toISOString().replace(f.rx1,'').split('T').join(' ');
};
/*
** By default fossil.message() sends its arguments console.debug(). If
** fossil.message.targetElement is set, it is assumed to be a DOM
** element, its innerText gets assigned to the concatenation of all
** arguments (with a space between each), and the CSS 'error' class is
** removed from the object. Pass it a falsy value to clear the target
** element.
**
** Returns this object.
*/
F.message = function f(msg){
const args = Array.prototype.slice.call(arguments,0);
const tgt = f.targetElement;
if(args.length) args.unshift(timestring(),'UTC:');
if(tgt){
tgt.classList.remove('error');
tgt.innerText = args.join(' ');
}
else{
if(args.length){
args.unshift('Fossil status:');
console.debug.apply(console,args);
}
}
return this;
};
/*
** Set default message.targetElement to #fossil-status-bar, if found.
*/
F.message.targetElement =
document.querySelector('#fossil-status-bar');
if(F.message.targetElement){
F.message.targetElement.addEventListener(
'dblclick', ()=>F.message(), false
);
}
/*
** By default fossil.error() sends its first argument to
** console.error(). If fossil.message.targetElement (yes,
** fossil.message) is set, it adds the 'error' CSS class to
** that element and sets its content as defined for message().
**
** Returns this object.
*/
F.error = function f(msg){
const args = Array.prototype.slice.call(arguments,0);
const tgt = F.message.targetElement;
args.unshift(timestring(),'UTC:');
if(tgt){
tgt.classList.add('error');
tgt.innerText = args.join(' ');
}
else{
args.unshift('Fossil error:');
console.error.apply(console,args);
}
return this;
};
/**
For each property in the given object, its key/value are encoded
for use as URL parameters and the combined string is
returned. e.g. {a:1,b:2} encodes to "a=1&b=2".
If the 2nd argument is an array, each encoded element is appended
to that array and tgtArray is returned. The above object would be
appended as ['a','=','1','&','b','=','2']. This form is used for
building up parameter lists before join('')ing the array to create
the result string.
If passed a truthy 3rd argument, it does not really encode each
component - it simply concatenates them together.
*/
F.encodeUrlArgs = function(obj,tgtArray,fakeEncode){
if(!obj) return '';
const a = (tgtArray instanceof Array) ? tgtArray : [],
enc = fakeEncode ? (x)=>x : encodeURIComponent;
let k, i = 0;
for( k in obj ){
if(i++) a.push('&');
a.push(enc(k),'=',enc(obj[k]));
}
return a===tgtArray ? a : a.join('');
};
/**
repoUrl( repoRelativePath [,urlParams] )
Creates a URL by prepending this.rootPath to the given path
(which must be relative from the top of the site, without a
leading slash). If urlParams is a string, it must be
paramters encoded in the form "key=val&key2=val2...", WITHOUT
a leading '?'. If it's an object, all of its properties get
appended to the URL in that form.
*/
F.repoUrl = function(path,urlParams){
if(!urlParams) return this.rootPath+path;
const url=[this.rootPath,path];
url.push('?');
if('string'===typeof urlParams) url.push(urlParams);
else if('object'===typeof urlParams){
this.encodeUrlArgs(urlParams, url);
}
return url.join('');
};
/**
Returns true if v appears to be a plain object.
*/
F.isObject = function(v){
return v &&
(v instanceof Object) &&
('[object Object]' === Object.prototype.toString.apply(v) );
};
/**
For each object argument, this function combines their properties,
using a last-one-wins policy, and returns a new object with the
combined properties. If passed a single object, it effectively
shallowly clones that object.
*/
F.mergeLastWins = function(){
var k, o, i;
const n = arguments.length, rc={};
for(i = 0; i < n; ++i){
if(!F.isObject(o = arguments[i])) continue;
for( k in o ){
if(o.hasOwnProperty(k)) rc[k] = o[k];
}
}
return rc;
};
/**
Expects to be passed as hash code as its first argument. It
returns a "shortened" form of hash, with a length which depends
on the 2nd argument: truthy = fossil.config.hashDigitsUrl, falsy
= fossil.config.hashDigits, number == that many digits. The
fossil.config values are derived from the 'hash-digits'
repo-level config setting or the
FOSSIL_HASH_DIGITS_URL/FOSSIL_HASH_DIGITS compile-time options.
If its first arugment is a non-string, that value is returned
as-is.
*/
F.hashDigits = function(hash,forUrl){
const n = ('number'===typeof forUrl)
? forUrl : F.config[forUrl ? 'hashDigitsUrl' : 'hashDigits'];
return ('string'==typeof hash ? hash.substr(
0, n
) : hash);
};
/**
Sets up pseudo-automatic content preview handling between a
source element (typically a TEXTAREA) and a target rendering
element (typically a DIV). The selector argument must be one of:
- A single DOM element
- A collection of DOM elements with a forEach method.
- A CSS selector
Each element in the collection must have the following data
attributes:
- data-f-preview-from: is either a DOM element id, WITH a leading
'#' prefix, or the name of a method (see below). If it's an ID,
the DOM element must support .value to get the content.
- data-f-preview-to: the DOM element id of the target "previewer"
element, WITH a leading '#', or the name of a method (see below).
- data-f-preview-via: the name of a method (see below).
- OPTIONAL data-f-preview-as-text: a numeric value. Explained below.
Each element gets a click handler added to it which does the
following:
1) Reads the content from its data-f-preview-from element or, if
that property refers to a method, calls the method without
arguments and uses its result as the content.
2) Passes the content to
methodNamespace[f-data-post-via](content,callback). f-data-post-via
is responsible for submitting the preview HTTP request, including
any parameters the request might require. When the response
arrives, it must pass the content of the response to its 2nd
argument, an auto-generated callback installed by this mechanism
which...
3) Assigns the response text to the data-f-preview-to element or
passes it to the function methodNamespace[f-data-preview-to](content), as
appropriate. If data-f-preview-to is a DOM element and
data-f-preview-as-text is '0' (the default) then the content is
assigned to the target element's innerHTML property, else it is
assigned to the element's textContent property.
The methodNamespace (2nd argument) defaults to fossil.page, and
any method-name data properties, e.g. data-f-preview-via and
potentially data-f-preview-from/to, must be a single method name,
not a property-access-style string. e.g. "myPreview" is legal but
"foo.myPreview" is not (unless, of course, the method is actually
named "foo.myPreview" (which is legal but would be
unconventional)).
An example...
First an input button:
<button id='test-preview-connector'
data-f-preview-from='#fileedit-content-editor' // elem ID or method name
data-f-preview-via='myPreview' // method name
data-f-preview-to='#fileedit-tab-preview-wrapper' // elem ID or method name
>Preview update</button>
And a sample data-f-preview-via method:
fossil.page.myPreview = function(content,callback){
const fd = new FormData();
fd.append('foo', ...);
fossil.fetch('preview_forumpost',{
payload: fd,
onload: callback,
onerror: (e)=>{ // only if app-specific handling is needed
fossil.fetch.onerror(e); // default impl
... any app-specific error reporting ...
}
});
};
Then connect the parts with:
fossil.connectPagePreviewers('#test-preview-connector');
Note that the data-f-preview-from, data-f-preview-via, and
data-f-preview-to selector are not resolved until the button is
actually clicked, so they need not exist in the DOM at the
instant when the connection is set up, so long as they can be
resolved when the preview-refreshing element is clicked.
*/
F.connectPagePreviewers = function f(selector,methodNamespace){
if('string'===typeof selector){
selector = document.querySelectorAll(selector);
}else if(!selector.forEach){
selector = [selector];
}
if(!methodNamespace){
methodNamespace = F.page;
}
selector.forEach(function(e){
e.addEventListener(
'click', function(r){
const eTo = '#'===e.dataset.fPreviewTo[0]
? document.querySelector(e.dataset.fPreviewTo)
: methodNamespace[e.dataset.fPreviewTo],
eFrom = '#'===e.dataset.fPreviewFrom[0]
? document.querySelector(e.dataset.fPreviewFrom)
: methodNamespace[e.dataset.fPreviewFrom],
asText = +(e.dataset.fPreviewAsText || 0);
eTo.textContent = "Fetching preview...";
methodNamespace[e.dataset.fPreviewVia](
(eFrom instanceof Function ? eFrom() : eFrom.value),
(r)=>{
if(eTo instanceof Function) eTo(r||'');
else eTo[asText ? 'textContent' : 'innerHTML'] = r||'';
}
);
}, false
);
});
return this;
};
/**
Convenience wrapper which adds an onload event listener to the
window object. Returns this.
*/
F.onPageLoad = function(callback){
window.addEventListener('load', callback, false);
return this;
};
/**
Assuming name is a repo-style filename, this function returns
a shortened form of that name:
.../LastDirectoryPart/FilenamePart
If the name has 0-1 directory parts, it is returned as-is.
Design note: in practice it is generally not helpful to elide the
*last* directory part because embedded docs (in particular) often
include x/y/index.md and x/z/index.md, both of which would be
shortened to something like x/.../index.md.
*/
F.shortenFilename = function(name){
const a = name.split('/');
if(a.length<=2) return name;
while(a.length>2) a.shift();
return '.../'+a.join('/');
};
/**
Adds a listener for fossil-level custom events. Events are
delivered to their callbacks as CustomEvent objects with a
'detail' property holding the event's app-level data.
The exact events fired differ by page, and not all pages trigger
events.
Pedantic sidebar: the custom event's 'target' property is an
unspecified DOM element. Clients must not rely on its value being
anything specific or useful.
Returns this object.
*/
F.page.addEventListener = function f(eventName, callback){
if(!f.proxy){
f.proxy = document.createElement('span');
}
f.proxy.addEventListener(eventName, callback, false);
return this;
};
/**
Internal. Dispatches a new CustomEvent to all listeners
registered for the given eventName via
fossil.page.addEventListener(), passing on a new CustomEvent with
a 'detail' property equal to the 2nd argument. Returns this
object.
*/
F.page.dispatchEvent = function(eventName, eventDetail){
if(this.addEventListener.proxy){
try{
this.addEventListener.proxy.dispatchEvent(
new CustomEvent(eventName,{detail: eventDetail})
);
}catch(e){
console.error(eventName,"event listener threw:",e);
}
}
return this;
};
/**
Sets the innerText of the page's TITLE tag to
the given text and returns this object.
*/
F.page.setPageTitle = function(title){
const t = document.querySelector('title');
if(t) t.innerText = title;
return this;
};
})(window);
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
"use strict";
/**************************************************************
Confirmer is a utility which provides an alternative to confirmation
dialog boxes and "check this checkbox to confirm action" widgets. It
acts by modifying a button to require two clicks within a certain
time, with the second click acting as a confirmation of the first. If
the second click does not come within a specified timeout then the
action is not confirmed.
Usage:
fossil.confirmer(domElement, options);
Usually:
fossil.confirmer(element, {
onconfirm: function(){
// this === the element.
// Do whatever the element would normally do when
// clicked.
}
});
Options:
.initialText = initial text of the element. Defaults to the result
of the element's .value (for INPUT tags) or innerHTML (for
everything else). After the timeout/tick count expires, or if the
user confirms the operation, the element's text is re-set to this
value.
.confirmText = text to show when in "confirm mode".
Default=("Confirm: "+initialText), or something similar.
.timeout = Number of milliseconds to wait for confirmation.
Default=3000. Alternately, use a combination of .ticks and
.ticktime.
.onconfirm = function to call when clicked in confirm mode. Default
= undefined. The function's "this" is the the DOM element to which
the countdown applies.
.ontimeout = function to call when confirm is not issued. Default =
undefined. The function's "this" is the DOM element to which the
countdown applies.
.onactivate = function to call when item is clicked, but only if the
item is not currently in countdown mode. This is called (and must
return) before the countdown starts. The function's "this" is the
DOM element to which the countdown applies. This can be used, e.g.,
to change the element's text or CSS classes.
.classInitial = optional CSS class string (default='') which is
added to the element during its "initial" state (the state it is in
when it is not waiting on a timeout). When the target is activated
(waiting on a timeout) this class is removed. In the case of a
timeout, this class is added *before* the .ontimeout handler is
called.
.classWaiting = optional CSS class string (default='') which is
added to the target when it is waiting on a timeout. When the target
leaves timeout-wait mode, this class is removed. When timeout-wait
mode is entered, this class is added *before* the .onactivate
handler is called.
.ticktime = a number of ms to wait per tick (see the next item).
Default = 1000.
.ticks = a number of "ticks" to wait, as an alternative to .timeout.
When this mode is active, the ontick callback will be triggered
immediately before each tick, including the first one. If both
.ticks and .timeout are set, only one will be used, but which one is
unspecified. If passed a ticks value with a truncated integer value
of 0 or less, it will throw an exception (e.g. that also applies if
it's passed 0.5).
.ontick = when using .ticks, this callback is passed the current
tick number before each tick, and its "this" is the target
element. On each subsequent call, the tick count will be reduced by
1, and it is passed 0 after the final tick expires or when the
action has been confirmed, immediately before the onconfirm or
ontimeout callback. The intention of the callback is to update the
label of the target element. If .ticks is set but .ontick is not
then a default implementation is used which updates the element with
the .confirmText, prepending a countdown to it.
.debug = boolean. If truthy, it sends some debug output to the dev
console to track what it's doing.
Various notes:
- To change the default option values, modify the
fossil.confirmer.defaultOpts object.
- Exceptions triggered via the callbacks are caught and emitted to the
dev console if the debug option is enabled, but are otherwise
ignored.
- Due to the nature of multi-threaded code, it is potentially possible
that confirmation and timeout actions BOTH happen if the user
triggers the associated action at "just the right millisecond"
before the timeout is triggered.
TODO: add an invert option which activates if the timeout is reached
and "times out" if the element is clicked again. e.g. a button which
says "Saving..." and cancels the op if it's clicked again, else it
saves after X time/ticks.
Terse Change history:
- 20200507:
- Add a tick-based countdown in order to more easily support
updating the target element with the countdown.
- 20200506:
- Ported from jQuery to plain JS.
- 20181112:
- extended to support certain INPUT elements.
- made default opts configurable.
- 20070717: initial jQuery-based impl.
*/
(function(F/*the fossil object*/){
F.confirmer = function f(elem,opt){
const dbg = opt.debug
? function(){console.debug.apply(console,arguments)}
: function(){};
dbg("confirmer opt =",opt);
if(!f.Holder){
f.isInput = (e)=>/^(input|textarea)$/i.test(e.nodeName);
f.Holder = function(target,opt){
const self = this;
this.target = target;
this.opt = opt;
this.timerID = undefined;
this.state = this.states.initial;
const isInput = f.isInput(target);
const updateText = function(msg){
if(isInput) target.value = msg;
else target.innerHTML = msg;
}
updateText(this.opt.initialText);
if(this.opt.ticks && !this.opt.ontick){
this.opt.ontick = function(tick){
updateText("("+tick+") "+self.opt.confirmText);
};
}
this.setClasses(false);
this.doTimeout = function() {
if(this.timerID){
clearTimeout( this.timerID );
delete this.timerID;
}
if( this.state != this.states.waiting ) {
// it was already confirmed
return;
}
this.setClasses( false );
this.state = this.states.initial;
dbg("Timeout triggered.");
if( this.opt.ontick ){
try{this.opt.ontick.call(this.target, 0)}
catch(e){dbg("ontick EXCEPTION:",e)}
}
if( this.opt.ontimeout ) {
try{this.opt.ontimeout.call(this.target)}
catch(e){dbg("ontimeout EXCEPTION:",e)}
}
updateText(this.opt.initialText);
};
target.addEventListener(
'click', function(){
switch( self.state ) {
case( self.states.waiting ):
/* Cancel the wait on confirmation */
if( undefined !== self.timerID ){
clearTimeout( self.timerID );
delete self.timerID;
}
self.state = self.states.initial;
self.setClasses( false );
dbg("Confirmed");
if( self.opt.ontick ){
try{self.opt.ontick.call(self.target,0)}
catch(e){dbg("ontick EXCEPTION:",e)}
}
if( self.opt.onconfirm ){
try{self.opt.onconfirm.call(self.target)}
catch(e){dbg("onconfirm EXCEPTION:",e)}
}
updateText(self.opt.initialText);
break;
case( self.states.initial ):
/* Enter the waiting-on-confirmation state... */
if(self.opt.ticks) self.opt.currentTick = self.opt.ticks;
self.setClasses( true );
self.state = self.states.waiting;
updateText( self.opt.confirmText );
if( self.opt.onactivate ) self.opt.onactivate.call( self.target );
if( self.opt.ontick ) self.opt.ontick.call(self.target, self.opt.currentTick);
if(self.opt.timeout){
dbg("Waiting "+self.opt.timeout+"ms on confirmation...");
self.timerID =
setTimeout(()=>self.doTimeout(),self.opt.timeout );
}else if(self.opt.ticks){
dbg("Waiting on confirmation for "+self.opt.ticks
+" ticks of "+self.opt.ticktime+"ms each...");
self.timerID =
setInterval(function(){
if(0===--self.opt.currentTick) self.doTimeout();
else{
try{self.opt.ontick.call(self.target,
self.opt.currentTick)}
catch(e){dbg("ontick EXCEPTION:",e)}
}
},self.opt.ticktime);
}
break;
default: // can't happen.
break;
}
}, false
);
};
f.Holder.prototype = {
states:{initial: 0, waiting: 1},
setClasses: function(activated) {
if(activated) {
if( this.opt.classWaiting ) {
this.target.classList.add( this.opt.classWaiting );
}
if( this.opt.classInitial ) {
this.target.classList.remove( this.opt.classInitial );
}
}else{
if( this.opt.classInitial ) {
this.target.classList.add( this.opt.classInitial );
}
if( this.opt.classWaiting ) {
this.target.classList.remove( this.opt.classWaiting );
}
}
}
};
}/*static init*/
opt = F.mergeLastWins(f.defaultOpts,{
initialText: (
f.isInput(elem) ? elem.value : elem.innerHTML
) || "PLEASE SET .initialText"
},opt);
if(!opt.confirmText){
opt.confirmText = "Confirm: "+opt.initialText;
}
if(opt.ticks){
delete opt.timeout;
opt.ticks = 0 | opt.ticks /* ensure it's an integer */;
if(opt.ticks<=0){
throw new Error("ticks must be >0");
}
if(opt.ticktime <= 0) opt.ticktime = 1000;
}else{
delete opt.ontick;
delete opt.ticks;
}
new f.Holder(elem,opt);
return this;
};
/**
The default options for initConfirmer(). Tweak them to set the
defaults. A couple of them (initialText and confirmText) are
dynamically-generated, and can't reasonably be set in the
defaults.
*/
F.confirmer.defaultOpts = {
timeout:3000,
ticks: undefined,
ticktime: 998/*not *quite* 1000*/,
onconfirm: undefined,
ontimeout: undefined,
onactivate: undefined,
classInitial: '',
classWaiting: '',
debug: false
};
})(window.fossil);
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
"use strict";
(function(F/*fossil object*/){
/**
A collection of HTML DOM utilities to simplify, a bit, using the
DOM API. It is focused on manipulation of the DOM, but one of its
core mantras is "No innerHTML." Using innerHTML in this code, in
particular assigning to it, is absolutely verboten.
*/
const argsToArray = (a)=>Array.prototype.slice.call(a,0);
const isArray = (v)=>v instanceof Array;
const dom = {
create: function(elemType){
return document.createElement(elemType);
},
createElemFactory: function(eType){
return function(){
return document.createElement(eType);
};
},
remove: function(e){
if(e.forEach){
e.forEach(
(x)=>x.parentNode.removeChild(x)
);
}else{
e.parentNode.removeChild(e);
}
return e;
},
/**
Removes all child DOM elements from the given element
and returns that element.
If e has a forEach method (is an array or DOM element
collection), this function instead clears each element in the
collection. May be passed any number of arguments, each of
which must be a DOM element or a container of DOM elements with
a forEach() method. Returns its first argument.
*/
clearElement: function f(e){
if(!f.each){
f.each = function(e){
if(e.forEach){
e.forEach((x)=>f(x));
return e;
}
while(e.firstChild) e.removeChild(e.firstChild);
};
}
argsToArray(arguments).forEach(f.each);
return arguments[0];
},
}/* dom object */;
/**
Returns the result of splitting the given str on
a run of spaces of (\s*,\s*).
*/
dom.splitClassList = function f(str){
if(!f.rx){
f.rx = /(\s+|\s*,\s*)/;
}
return str ? str.split(f.rx) : [str];
};
dom.div = dom.createElemFactory('div');
dom.p = dom.createElemFactory('p');
dom.code = dom.createElemFactory('code');
dom.pre = dom.createElemFactory('pre');
dom.header = dom.createElemFactory('header');
dom.footer = dom.createElemFactory('footer');
dom.section = dom.createElemFactory('section');
dom.span = dom.createElemFactory('span');
dom.strong = dom.createElemFactory('strong');
dom.em = dom.createElemFactory('em');
dom.img = function(src){
const e = dom.create('img');
if(src) e.setAttribute('src',src);
return e;
};
/**
Creates and returns a new anchor element with the given
optional href and label. If label===true then href is used
as the label.
*/
dom.a = function(href,label){
const e = dom.create('a');
if(href) e.setAttribute('href',href);
if(label) e.appendChild(dom.text(true===label ? href : label));
return e;
};
dom.hr = dom.createElemFactory('hr');
dom.br = dom.createElemFactory('br');
dom.text = (t)=>document.createTextNode(t||'');
dom.button = function(label){
const b = this.create('button');
if(label) b.appendChild(this.text(label));
return b;
};
dom.select = dom.createElemFactory('select');
/**
Returns an OPTION element with the given value and label
text (which defaults to the value).
May be called as (value), (selectElement), (selectElement,
value), (value, label) or (selectElement, value,
label). The latter appends the new element to the given
SELECT element.
If the value has the undefined value then it is NOT
assigned as the option element's value.
*/
dom.option = function(value,label){
const a = arguments;
var sel;
if(1==a.length){
if(a[0] instanceof HTMLElement){
sel = a[0];
}else{
value = a[0];
}
}else if(2==a.length){
if(a[0] instanceof HTMLElement){
sel = a[0];
value = a[1];
}else{
value = a[0];
label = a[1];
}
}
else if(3===a.length){
sel = a[0];
value = a[1];
label = a[2];
}
const o = this.create('option');
if(undefined !== value){
o.value = value;
this.append(o, this.text(label || value));
}
if(sel) this.append(sel, o);
return o;
};
dom.h = function(level){
return this.create('h'+level);
};
dom.ul = dom.createElemFactory('ul');
/**
Creates and returns a new LI element, appending it to the
given parent argument if it is provided.
*/
dom.li = function(parent){
const li = this.create('li');
if(parent) parent.appendChild(li);
return li;
};
/**
Returns a function which creates a new DOM element of the
given type and accepts an optional parent DOM element
argument. If the function's argument is truthy, the new
child element is appended to the given parent element.
Returns the new child element.
*/
dom.createElemFactoryWithOptionalParent = function(childType){
return function(parent){
const e = this.create(childType);
if(parent) parent.appendChild(e);
return e;
};
};
dom.table = dom.createElemFactory('table');
dom.thead = dom.createElemFactoryWithOptionalParent('thead');
dom.tbody = dom.createElemFactoryWithOptionalParent('tbody');
dom.tfoot = dom.createElemFactoryWithOptionalParent('tfoot');
dom.tr = dom.createElemFactoryWithOptionalParent('tr');
dom.td = dom.createElemFactoryWithOptionalParent('td');
dom.th = dom.createElemFactoryWithOptionalParent('th');
/**
Creates and returns a FIELDSET element, optionaly with a
LEGEND element added to it.
*/
dom.fieldset = function(legendText){
const fs = this.create('fieldset');
if(legendText){
this.append(
fs,
this.append(
this.create('legend'),
legendText
)
);
}
return fs;
};
/**
Appends each argument after the first to the first argument
(a DOM node) and returns the first argument.
- If an argument is a string or number, it is transformed
into a text node.
- If an argument is an array or has a forEach member, this
function appends each element in that list to the target
by calling its forEach() method to pass it (recursively)
to this function.
- Else the argument assumed to be of a type legal
to pass to parent.appendChild().
*/
dom.append = function f(parent/*,...*/){
const a = argsToArray(arguments);
a.shift();
for(let i in a) {
var e = a[i];
if(isArray(e) || e.forEach){
e.forEach((x)=>f.call(this, parent,e));
continue;
}
if('string'===typeof e || 'number'===typeof e) e = this.text(e);
parent.appendChild(e);
}
return parent;
};
dom.input = function(type){
return this.attr(this.create('input'), 'type', type);
};
/**
Internal impl for addClass(), removeClass().
*/
const domAddRemoveClass = function f(action,e){
if(!f.rxSPlus){
f.rxSPlus = /\s+/;
f.applyAction = function(e,a,v){
if(!e || !v
/*silently skip empty strings/flasy
values, for user convenience*/) return;
else if(e.forEach){
e.forEach((E)=>E.classList[a](v));
}else{
e.classList[a](v);
}
};
}
var i = 2, n = arguments.length;
for( ; i < n; ++i ){
let c = arguments[i];
if(!c) continue;
else if(isArray(c) ||
('string'===typeof c
&& c.indexOf(' ')>=0
&& (c = c.split(f.rxSPlus)))
|| c.forEach
){
c.forEach((k)=>k ? f.applyAction(e, action, k) : false);
// ^^^ we could arguably call f(action,e,k) to recursively
// apply constructs like ['foo bar'] or [['foo'],['bar baz']].
}else if(c){
f.applyAction(e, action, c);
}
}
return e;
};
/**
Adds one or more CSS classes to one or more DOM elements.
The first argument is a target DOM element or a list type of such elements
which has a forEach() method. Each argument
after the first may be a string or array of strings. Each
string may contain spaces, in which case it is treated as a
list of CSS classes.
Returns e.
*/
dom.addClass = function(e,c){
const a = argsToArray(arguments);
a.unshift('add');
return domAddRemoveClass.apply(this, a);
};
/**
The 'remove' counterpart of the addClass() method, taking
the same arguments and returning the same thing.
*/
dom.removeClass = function(e,c){
const a = argsToArray(arguments);
a.unshift('remove');
return domAddRemoveClass.apply(this, a);
};
dom.hasClass = function(e,c){
return (e && e.classList) ? e.classList.contains(c) : false;
};
/**
Each argument after the first may be a single DOM element
or a container of them with a forEach() method. All such
elements are appended, in the given order, to the dest
element.
Returns dest.
*/
dom.moveTo = function(dest,e){
const n = arguments.length;
var i = 1;
for( ; i < n; ++i ){
e = arguments[i];
if(e.forEach){
e.forEach((x)=>dest.appendChild(x));
}else{
dest.appendChild(e);
}
}
return dest;
};
/**
Each argument after the first may be a single DOM element
or a container of them with a forEach() method. For each
DOM element argument, all children of that DOM element
are moved to dest (via appendChild()). For each list argument,
each entry in the list is assumed to be a DOM element and is
appended to dest.
dest may be an Array, in which case each child is pushed
into the array and removed from its current parent element.
All children are appended in the given order.
Returns dest.
*/
dom.moveChildrenTo = function f(dest,e){
if(!f.mv){
f.mv = function(d,v){
if(d instanceof Array){
d.push(v);
if(v.parentNode) v.parentNode.removeChild(v);
}
else d.appendChild(v);
};
}
const n = arguments.length;
var i = 1;
for( ; i < n; ++i ){
e = arguments[i];
if(!e){
console.warn("Achtung: dom.moveChildrenTo() passed a falsy value at argment",i,"of",
arguments,arguments[i]);
continue;
}
if(e.forEach){
e.forEach((x)=>f.mv(dest, x));
}else{
while(e.firstChild){
f.mv(dest, e.firstChild);
}
}
}
return dest;
};
/**
Adds each argument (DOM Elements) after the first to the
DOM immediately before the first argument (in the order
provided), then removes the first argument from the DOM.
Returns void.
If any argument beyond the first has a forEach method, that
method is used to recursively insert the collection's
contents before removing the first argument from the DOM.
*/
dom.replaceNode = function f(old,nu){
var i = 1, n = arguments.length;
++f.counter;
try {
for( ; i < n; ++i ){
const e = arguments[i];
if(e.forEach){
e.forEach((x)=>f.call(this,old,e));
continue;
}
old.parentNode.insertBefore(e, old);
}
}
finally{
--f.counter;
}
if(!f.counter){
old.parentNode.removeChild(old);
}
};
dom.replaceNode.counter = 0;
/**
Two args == getter: (e,key), returns value
Three == setter: (e,key,val), returns e. If val===null
or val===undefined then the attribute is removed. If (e)
has a forEach method then this routine is applied to each
element of that collection via that method.
*/
dom.attr = function f(e){
if(2===arguments.length) return e.getAttribute(arguments[1]);
if(e.forEach){
e.forEach((x)=>f(x,arguments[1],arguments[2]));
return e;
}
const key = arguments[1], val = arguments[2];
if(null===val || undefined===val){
e.removeAttribute(key);
}else{
e.setAttribute(key,val);
}
return e;
};
const enableDisable = function f(enable){
var i = 1, n = arguments.length;
for( ; i < n; ++i ){
let e = arguments[i];
if(e.forEach){
e.forEach((x)=>f(enable,x));
}else{
e.disabled = !enable;
}
}
return arguments[1];
};
/**
Enables (by removing the "disabled" attribute) each element
(HTML DOM element or a collection with a forEach method)
and returns the first argument.
*/
dom.enable = function(e){
const args = argsToArray(arguments);
args.unshift(true);
return enableDisable.apply(this,args);
};
/**
Disables (by setting the "disabled" attribute) each element
(HTML DOM element or a collection with a forEach method)
and returns the first argument.
*/
dom.disable = function(e){
const args = argsToArray(arguments);
args.unshift(false);
return enableDisable.apply(this,args);
};
/**
A proxy for document.querySelector() which throws if
selection x is not found. It may optionally be passed an
"origin" object as its 2nd argument, which restricts the
search to that branch of the tree.
*/
dom.selectOne = function(x,origin){
var src = origin || document,
e = src.querySelector(x);
if(!e){
e = new Error("Cannot find DOM element: "+x);
console.error(e, src);
throw e;
}
return e;
};
return F.dom = dom;
})(window.fossil);
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
"use strict";
/**
Requires that window.fossil has already been set up.
window.fossil.fetch() is an HTTP request/response mini-framework
similar (but not identical) to the not-quite-ubiquitous
window.fetch().
JS usages:
fossil.fetch( URI [, onLoadCallback] );
fossil.fetch( URI [, optionsObject = {}] );
Noting that URI must be relative to the top of the repository and
should not start with a slash (if it does, it is stripped). It gets
the equivalent of "%R/" prepended to it.
The optionsObject may be an onload callback or an object with any
of these properties:
- onload: callback(responseData) (default = output response to the
console). In the context of the callback, the options object is
"this", noting that this call may have amended the options object
with state other than what the caller provided.
- onerror: callback(Error object) (default = output error message
to console.error() and fossil.error()). Triggered if the request
generates any response other than HTTP 200 or suffers a connection
error or timeout while awaiting a response. In the context of the
callback, the options object is "this".
- method: 'POST' | 'GET' (default = 'GET'). CASE SENSITIVE!
- payload: anything acceptable by XHR2.send(ARG) (DOMString,
Document, FormData, Blob, File, ArrayBuffer), or a plain object or
array, either of which gets JSON.stringify()'d. If payload is set
then the method is automatically set to 'POST'. By default XHR2
will set the content type based on the payload type. If an
object/array is converted to JSON, the contentType option is
automatically set to 'application/json', and if JSON.stringify() of
that value fails then the exception is propagated to this
function's caller.
- contentType: Optional request content type when POSTing. Ignored
if the method is not 'POST'.
- responseType: optional string. One of ("text", "arraybuffer",
"blob", or "document") (as specified by XHR2). Default = "text".
As an extension, it supports "json", which tells it that the
response is expected to be text and that it should be JSON.parse()d
before passing it on to the onload() callback. If parsing of such
an object fails, the onload callback is not called, and the
onerror() callback is passed the exception from the parsing error.
- urlParams: string|object. If a string, it is assumed to be a
URI-encoded list of params in the form "key1=val1&key2=val2...",
with NO leading '?'. If it is an object, all of its properties get
converted to that form. Either way, the parameters get appended to
the URL before submitting the request.
- responseHeaders: If true, the onload() callback is passed an
additional argument: a map of all of the response headers. If it's
a string value, the 2nd argument passed to onload() is instead the
value of that single header. If it's an array, it's treated as a
list of headers to return, and the 2nd argument is a map of those
header values. When a map is passed on, all of its keys are
lower-cased. When a given header is requested and that header is
set multiple times, their values are (per the XHR docs)
concatenated together with ", " between them.
- beforesend/aftersend: optional callbacks which are called without
arguments immediately before the request is submitted and
immediately after it is received, regardless of success or
error. In the context of the callback, the options object is the
"this". These can be used to, e.g., keep track of in-flight
requests and update the UI accordingly, e.g. disabling/enabling DOM
elements. Any exceptions triggered by beforesend/aftersend are
caught and silently ignored.
- timeout: integer in milliseconds specifying the XHR timeout
duration. Default = fossil.fetch.timeout.
When an options object does not provide
onload/onerror/beforesend/aftersend handlers of its own, this
function falls to defaults which are member properties of this
function with the same name, e.g. fossil.fetch.onload(). The
default onload/onerror implementations route the data through the
dev console and (for onerror()) through fossil.error(). The default
beforesend/aftersend are no-ops. Individual pages may overwrite
those members to provide default implementations suitable for the
page's use, e.g. keeping track of how many in-flight
Note that this routine may add properties to the 2nd argument, so
that instance should not be kept around for later use.
Returns this object, noting that the XHR request is asynchronous,
and still in transit (or has yet to be sent) when that happens.
*/
window.fossil.fetch = function f(uri,opt){
const F = fossil;
if(!f.onload){
f.onload = (r)=>console.debug('fossil.fetch() XHR response:',r);
}
if(!f.onerror){
f.onerror = function(e/*exception*/){
console.error("fossil.fetch() XHR error:",e);
if(e instanceof Error) F.error('Exception:',e);
else F.error("Unknown error in handling of XHR request.");
};
}/*f.onerror()*/
if(!f.parseResponseHeaders){
f.parseResponseHeaders = function(h){
const rc = {};
if(!h) return rc;
const ar = h.trim().split(/[\r\n]+/);
ar.forEach(function(line) {
const parts = line.split(': ');
const header = parts.shift();
const value = parts.join(': ');
rc[header.toLowerCase()] = value;
});
return rc;
};
}
if('/'===uri[0]) uri = uri.substr(1);
if(!opt) opt = {};
else if('function'===typeof opt) opt={onload:opt};
if(!opt.onload) opt.onload = f.onload;
if(!opt.onerror) opt.onerror = f.onerror;
if(!opt.beforesend) opt.beforesend = f.beforesend;
if(!opt.aftersend) opt.aftersend = f.aftersend;
let payload = opt.payload, jsonResponse = false;
if(undefined!==payload){
opt.method = 'POST';
if(!(payload instanceof FormData)
&& !(payload instanceof Document)
&& !(payload instanceof Blob)
&& !(payload instanceof File)
&& !(payload instanceof ArrayBuffer)
&& ('object'===typeof payload
|| payload instanceof Array)){
payload = JSON.stringify(payload);
opt.contentType = 'application/json';
}
}
const url=[F.repoUrl(uri,opt.urlParams)],
x=new XMLHttpRequest();
if('POST'===opt.method && 'string'===typeof opt.contentType){
x.setRequestHeader('Content-Type',opt.contentType);
}
if('json'===opt.responseType){
/* 'json' is an extension to the supported XHR.responseType
list. We use it as a flag to tell us to JSON.parse()
the response. */
jsonResponse = true;
x.responseType = 'text';
}else{
x.responseType = opt.responseType||'text';
}
x.ontimeout = function(){
try{opt.aftersend()}catch(e){/*ignore*/}
opt.onerror(new Error("XHR timeout of "+x.timeout+"ms expired."));
};
x.onreadystatechange = function(){
if(XMLHttpRequest.DONE !== x.readyState) return;
try{opt.aftersend()}catch(e){/*ignore*/}
if(200!==x.status){
let err;
try{
const j = JSON.parse(x.response);
if(j.error) err = new Error(j.error);
}catch(ex){/*ignore*/}
opt.onerror(err || new Error("HTTP response status "+x.status+"."));
return;
}
const orh = opt.responseHeaders;
let head;
if(true===orh){
head = f.parseResponseHeaders(x.getAllResponseHeaders());
}else if('string'===typeof orh){
head = x.getResponseHeader(orh);
}else if(orh instanceof Array){
head = {};
orh.forEach((s)=>{
if('string' === typeof s) head[s.toLowerCase()] = x.getResponseHeader(s);
});
}
try{
const args = [(jsonResponse && x.response)
? JSON.parse(x.response) : x.response];
if(head) args.push(head);
opt.onload.apply(opt, args);
}catch(e){
opt.onerror(e);
}
};
try{opt.beforesend()}catch(e){/*ignore*/}
x.open(opt.method||'GET', url.join(''), true);
x.timeout = +opt.timeout || f.timeout;
if(undefined!==payload) x.send(payload);
else x.send();
return this;
};
window.fossil.fetch.beforesend = function(){};
window.fossil.fetch.aftersend = function(){};
window.fossil.fetch.timeout = 15000/* Default timeout, in ms. */;
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 |
(function(F/*the fossil object*/){
"use strict";
/**
Client-side implementation of the /filepage app. Requires that
the fossil JS bootstrapping is complete and that several fossil
JS APIs have been installed: fossil.fetch, fossil.dom,
fossil.tabs, fossil.storage, fossil.confirmer.
Custom events which can be listened for via
fossil.page.addEventListener():
- Event 'fileedit-file-loaded': passes on information when it
loads a file (whether from the network or its internal local-edit
cache), in the form of an "finfo" object:
{
filename: string,
checkin: UUID string,
branch: branch name of UUID,
isExe: bool, true only for executable files
mimetype: mimetype string, as determined by the fossil server.
}
The internal docs and code frequently use the term "finfo", and such
references refer to an object with that form.
The fossil.page.fileContent() method gets or sets the current file
content for the page.
- Event 'fileedit-committed': is fired when a commit completes,
passing on the same info as fileedit-file-loaded.
- Event 'fileedit-content-replaced': when the editor's content is
replaced, as opposed to it being edited via user
interaction. This normally happens via selecting a file to
load. The event detail is the fossil.page object, not the current
file content.
- Event 'fileedit-preview-updated': when the preview is refreshed
from the server, this event passes on information about the preview
change in the form of an object:
{
element: the DOM element which contains the content preview.
mimetype: the fossil-reported content mimetype.
previewMode: a string describing the preview mode: see
the fossil.page.previewModes map for the values. This can
be used to determine whether, e.g., the content is suitable
for applying a 3rd-party code highlighting API to.
}
Here's an example which can be used with the highlightjs code
highlighter to update the highlighting when the preview is
refreshed in "wiki" mode (which includes fossil-native wiki and
markdown):
fossil.page.addEventListener(
'fileedit-preview-updated',
(ev)=>{
if(ev.detail.previewMode==='wiki'){
ev.detail.element.querySelectorAll(
'code[class^=language-]'
).forEach((e)=>hljs.highlightBlock(e));
}
}
);
*/
const E = (s)=>document.querySelector(s),
D = F.dom,
P = F.page;
P.config = {
defaultMaxStashSize: 7
};
/**
$stash is an internal-use-only object for managing "stashed"
local edits, to help avoid that users accidentally lose content
by switching tabs or following links or some such. The basic
theory of operation is...
All "stashed" state is stored using fossil.storage.
- When the current file content is modified by the user, the
current stathe of the current P.finfo and its the content
is stashed. For the built-in editor widget, "changes" is
notified via a 'change' event. For a client-side custom
widget, the client needs to call P.stashContentChange() when
their widget triggers the equivalent of a 'change' event.
- For certain non-content updates (as of this writing, only the
is-executable checkbox), only the P.finfo stash entry is
updated, not the content (unless the content has not yet been
stashed, in which case it is also stashed so that the stash
always has matching pairs of finfo/content).
- When saving, the stashed entry for the previous version is removed
from the stash.
- When "loading", we use any stashed state for the given
checkin/file combination. When forcing a re-load of content,
any stashed entry for that combination is removed from the
stash.
- Every time P.stashContentChange() updates the stash, it is
pruned to $stash.prune.defaultMaxCount most-recently-updated
entries.
- This API often refers to "finfo objects." Those are objects
with a minimum of {checkin,filename} properties (which must be
valid), and a combination of those two properties is used as
basis for the stash keys for any given checkin/filename
combination.
The structure of the stash is a bit convoluted for efficiency's
sake: we store a map of file info (finfo) objects separately from
those files' contents because otherwise we would be required to
JSONize/de-JSONize the file content when stashing/restoring it,
and that would be horribly inefficient (meaning "battery-consuming"
on mobile devices).
*/
const $stash = {
keys: {
index: F.page.name+'/index'
},
/**
index: {
"CHECKIN_HASH:FILENAME": {file info w/o content}
...
}
In F.storage we...
- Store this.index under the key this.keys.index.
- Store each file's content under the key
(P.name+'/CHECKIN_HASH:FILENAME'). These are stored separately
from the index entries to avoid having to JSONize/de-JSONize
the content. The assumption/hope is that the browser can store
those records "directly," without any intermediary
encoding/decoding going on.
*/
indexKey: function(finfo){return finfo.checkin+':'+finfo.filename},
/** Returns the key for storing content for the given key suffix,
by prepending P.name to suffix. */
contentKey: function(suffix){return P.name+'/'+suffix},
/** Returns the index object, fetching it from the stash or creating
it anew on the first call. */
getIndex: function(){
if(!this.index){
this.index = F.storage.getJSON(
this.keys.index, undefined
);
if(!this.index){
/*check for and remove/replace older name. This whole block
can be removed once the test phase is done (don't want to
invalidate the testers' edits on the test server). When
doing so, be sure to replace undefined in the above
getJSON() call with {}. */
const oldName = F.page.name+':index';
this.index = F.storage.getJSON(oldName,undefined);
if(this.index){
F.storage.remove(oldName);
this.storeIndex();
}else{
this.index = {};
}
}
}
return this.index;
},
_fireStashEvent: function(){
if(this._disableNextEvent) delete this._disableNextEvent;
else F.page.dispatchEvent('fileedit-stash-updated', this);
},
/**
Returns the stashed version, if any, for the given finfo object.
*/
getFinfo: function(finfo){
const ndx = this.getIndex();
return ndx[this.indexKey(finfo)];
},
/** Serializes this object's index to F.storage. Returns this. */
storeIndex: function(){
if(this.index) F.storage.setJSON(this.keys.index,this.index);
return this;
},
/** Updates the stash record for the given finfo
and (optionally) content. If passed 1 arg, only
the finfo stash is updated, else both the finfo
and its contents are (re-)stashed. Returns this.
*/
updateFile: function(finfo,content){
const ndx = this.getIndex(),
key = this.indexKey(finfo),
old = ndx[key];
const record = old || (ndx[key]={
checkin: finfo.checkin,
filename: finfo.filename,
mimetype: finfo.mimetype
});
record.isExe = !!finfo.isExe;
record.stashTime = new Date().getTime();
if(!record.branch) record.branch=finfo.branch;
this.storeIndex();
if(arguments.length>1){
F.storage.set(this.contentKey(key), content);
}
this._fireStashEvent();
return this;
},
/**
Returns the stashed content, if any, for the given finfo
object.
*/
stashedContent: function(finfo){
return F.storage.get(this.contentKey(this.indexKey(finfo)));
},
/** Returns true if we have stashed content for the given finfo
record. */
hasStashedContent: function(finfo){
return F.storage.contains(this.contentKey(this.indexKey(finfo)));
},
/** Unstashes the given finfo record and its content.
Returns this. */
unstash: function(finfo){
const ndx = this.getIndex(),
key = this.indexKey(finfo);
delete finfo.stashTime;
delete ndx[key];
F.storage.remove(this.contentKey(key));
this.storeIndex();
this._fireStashEvent();
return this;
},
/**
Clears all $stash entries from F.storage. Returns this.
*/
clear: function(){
const ndx = this.getIndex(),
self = this;
let count = 0;
Object.keys(ndx).forEach(function(k){
++count;
const e = ndx[k];
delete ndx[k];
F.storage.remove(self.contentKey(k));
});
F.storage.remove(this.keys.index);
delete this.index;
if(count) this._fireStashEvent();
return this;
},
/**
Removes all but the maxCount most-recently-updated stash
entries, where maxCount defaults to this.prune.defaultMaxCount.
*/
prune: function f(maxCount){
const ndx = this.getIndex();
const li = [];
if(!maxCount || maxCount<0) maxCount = f.defaultMaxCount;
Object.keys(ndx).forEach((k)=>li.push(ndx[k]));
li.sort((l,r)=>l.stashTime - r.stashTime);
let n = 0;
while(li.length>maxCount){
++n;
const e = li.shift();
this._disableNextEvent = true;
this.unstash(e);
console.warn("Pruned oldest local file edit entry:",e);
}
if(n) this._fireStashEvent();
}
};
$stash.prune.defaultMaxCount = P.config.defaultMaxStashSize;
/**
Widget for the checkin/file selection list.
*/
P.fileSelectWidget = {
e:{
container: E('#fileedit-file-selector')
},
finfo: {},
cache: {
checkins: undefined,
files:{},
branchKey: 'fileedit/uuid-branches',
branchNames: {}
},
/**
Fetches the list of leaf checkins from the server and updates
the UI with that list.
*/
loadLeaves: function(){
D.append(D.clearElement(
this.e.ciListLabel,
this.e.selectCi,
this.e.selectFiles
),"Loading leaves...");
D.disable(this.e.btnLoadFile, this.e.selectFiles, this.e.selectCi);
const self = this;
F.fetch('fileedit/filelist',{
urlParams:'leaves',
responseType: 'json',
onload: function(list){
D.append(D.clearElement(self.e.ciListLabel),
"Open leaves (newest first):");
self.cache.checkins = list;
D.clearElement(D.enable(self.e.selectCi));
let loadThisOne;
list.forEach(function(o,n){
if(!n) loadThisOne = o;
self.cache.branchNames[F.hashDigits(o.checkin,true)] = o.branch;
D.option(self.e.selectCi, o.checkin,
o.timestamp+' ['+o.branch+']: '
+F.hashDigits(o.checkin));
});
F.storage.setJSON(self.cache.branchKey, self.cache.branchNames);
self.loadFiles(loadThisOne ? loadThisOne.checkin : false);
}
});
},
/**
Loads the file list for the given checkin UUID. It uses a
cached copy on subsequent calls for the same UUID. If passed a
falsy value, it instead clears and disables the file selection
list.
*/
loadFiles: function(ciUuid){
delete this.finfo.filename;
this.finfo.checkin = ciUuid;
const selFiles = this.e.selectFiles;
if(!ciUuid){
D.clearElement(D.disable(selFiles, this.e.btnLoadFile));
return this;
}
const onload = (response)=>{
D.clearElement(selFiles);
D.append(
D.clearElement(this.e.fileListLabel),
"Editable files for ",
D.append(
D.code(), "[",
D.a(F.repoUrl('timeline',{
c: ciUuid
}), F.hashDigits(ciUuid)),"]"
), ":"
);
this.cache.files[response.checkin] = response;
response.editableFiles.forEach(function(fn,n){
D.option(selFiles, fn);
});
if(selFiles.options.length){
D.enable(selFiles, this.e.btnLoadFile);
}
};
const got = this.cache.files[ciUuid];
if(got){
onload(got);
return this;
}
D.disable(selFiles,this.e.btnLoadFile);
D.clearElement(selFiles);
D.append(D.clearElement(this.e.fileListLabel),
"Loading files for "+F.hashDigits(ciUuid)+"...");
F.fetch('fileedit/filelist',{
urlParams:{checkin: ciUuid},
responseType: 'json',
onload
});
return this;
},
/**
If this object has ever loaded the given checkin version via
loadLeaves(), this returns the branch name associated with that
version, else returns undefined;
*/
checkinBranchName: function(uuid){
return this.cache.branchNames[F.hashDigits(uuid,true)];
},
/**
Initializes the checkin/file selector widget. Must only be
called once.
*/
init: function(){
this.cache.branchNames = F.storage.getJSON(this.cache.branchKey, {});
const selCi = this.e.selectCi = D.select(),
selFiles = this.e.selectFiles
= D.addClass(D.select(), 'file-list'),
btnLoad = this.e.btnLoadFile =
D.addClass(D.button("Load file"), "flex-shrink"),
filesLabel = this.e.fileListLabel =
D.addClass(D.div(),'flex-shrink','file-list-label'),
ciLabelWrapper = D.addClass(
D.div(), 'flex-container','flex-row', 'flex-shrink',
'stretch'
),
btnReload = D.addClass(
D.button('Reload'), 'flex-shrink'
),
ciLabel = this.e.ciListLabel =
D.addClass(D.span(),'flex-shrink','checkin-list-label')
;
D.attr(selCi, 'title',"The list of opened leaves.");
D.attr(selFiles, 'title',
"The list of editable files for the selected checkin.");
D.attr(btnLoad, 'title',
"Load the selected file into the editor.");
D.disable(selCi, selFiles, btnLoad);
D.attr(selFiles, 'size', 10);
D.append(
this.e.container,
D.append(ciLabelWrapper,
btnReload, ciLabel),
selCi,
filesLabel,
selFiles,
/* Use a wrapper for btnLoad so that the button itself does not
stretch to fill the parent width: */
D.append(D.addClass(D.div(), 'flex-shrink'), btnLoad)
);
this.loadLeaves();
selCi.addEventListener(
'change', (e)=>this.loadFiles(e.target.value), false
);
btnLoad.addEventListener(
'click', (e)=>{
this.finfo.filename = selFiles.value;
if(this.finfo.filename){
P.loadFile(this.finfo.filename, this.finfo.checkin);
}
}, false
);
btnReload.addEventListener(
'click', (e)=>this.loadLeaves(), false
);
delete this.init;
}
}/*P.fileSelectWidget*/;
/**
Widget for listing and selecting $stash entries.
*/
P.stashWidget = {
e:{/*DOM element(s)*/},
init: function(domInsertPoint/*insert widget BEFORE this element*/){
const wrapper = D.addClass(
D.attr(D.div(),'id','fileedit-stash-selector'),
'input-with-label'
);
const sel = this.e.select = D.select();
const btnClear = this.e.btnClear
= D.addClass(D.button("Clear"),'hidden');
D.append(wrapper, "Local edits (",
D.append(D.code(),
F.storage.storageImplName()),
"):",
sel, btnClear);
D.attr(wrapper, "title", [
'Locally-edited files. Timestamps are the last local edit time.',
'Only the',P.config.defaultMaxStashSize,'most recent checkin/file',
'combinations are retained.',
'Committing or reloading a file removes it from this list.'
].join(' '));
D.option(D.disable(sel), "(empty)");
F.page.addEventListener('fileedit-stash-updated',(e)=>this.updateList(e.detail));
F.page.addEventListener('fileedit-file-loaded',(e)=>this.updateList($stash, e.detail));
sel.addEventListener('change',function(e){
const opt = this.selectedOptions[0];
if(opt && opt._finfo) P.loadFile(opt._finfo);
});
F.confirmer(btnClear, {
confirmText: "REALLY delete ALL local edits?",
onconfirm: (e)=>P.clearStash().loadFile(/*in case P.finfo() was in the stash*/),
ticks: 3
});
if(F.storage.isTransient()){/*Warn if our storage is particularly transient...*/
D.append(wrapper, D.append(
D.addClass(D.span(),'warning'),
"Warning: persistent storage is not available, "+
"so uncomitted edits will not survive a page reload."
));
}
domInsertPoint.parentNode.insertBefore(wrapper, domInsertPoint);
$stash._fireStashEvent(/*read the page-load-time stash*/);
delete this.init;
},
/**
Regenerates the edit selection list.
*/
updateList: function f(stasher,theFinfo){
if(!f.compare){
const cmpBase = (l,r)=>l<r ? -1 : (l===r ? 0 : 1);
f.compare = function(l,r){
const cmp = cmpBase(l.filename, r.filename);
return cmp ? cmp : cmpBase(l.checkin, r.checkin);
};
f.rxZ = /\.\d+Z$/ /* ms and 'Z' part of date string */;
const pad=(x)=>(''+x).length>1 ? x : '0'+x;
f.timestring = function ff(d){
return [
d.getFullYear(),'-',pad(d.getMonth()+1/*sigh*/),'-',pad(d.getDate()),
'@',pad(d.getHours()),':',pad(d.getMinutes())
].join('');
};
}
const index = stasher.getIndex(), ilist = [];
Object.keys(index).forEach((finfo)=>{
ilist.push(index[finfo]);
});
const self = this;
D.clearElement(this.e.select);
if(0===ilist.length){
D.addClass(this.e.btnClear, 'hidden');
D.option(D.disable(this.e.select),"No local edits");
return;
}
D.enable(this.e.select);
D.removeClass(this.e.btnClear, 'hidden');
D.disable(D.option(this.e.select,0,"Select a local edit..."));
const currentFinfo = theFinfo || P.finfo || {};
ilist.sort(f.compare).forEach(function(finfo,n){
const key = stasher.indexKey(finfo),
branch = finfo.branch
|| P.fileSelectWidget.checkinBranchName(finfo.checkin)||'';
/* Remember that we don't know the branch name for non-leaf versions
which P.fileSelectWidget() has never seen/cached. */
const opt = D.option(
self.e.select, n+1/*value is (almost) irrelevant*/,
[F.hashDigits(finfo.checkin, 6), ' [',branch||'?branch?','] ',
f.timestring(new Date(finfo.stashTime)),' ',
false ? finfo.filename : F.shortenFilename(finfo.filename)
].join('')
);
opt._finfo = finfo;
if(0===f.compare(currentFinfo, finfo)){
D.attr(opt, 'selected', true);
}
});
}
}/*P.stashWidget*/;
/**
Internal workaround to select the current preview mode
and fire a change event if the value actually changes
or if forceEvent is truthy.
*/
P.selectPreviewMode = function(modeValue, forceEvent){
const s = this.e.selectPreviewMode;
if(!modeValue) modeValue = s.value;
else if(s.value != modeValue){
s.value = modeValue;
forceEvent = true;
}
if(forceEvent){
// Force UI update
s.dispatchEvent(new Event('change',{target:s}));
}
};
/**
Keep track of how many in-flight AJAX requests there are so we
can disable input elements while any are pending. For
simplicity's sake we simply disable ALL OF IT while any AJAX is
pending, rather than disabling operation-specific UI elements,
which would be a huge maintenance hassle.
Noting, however, that this global on/off is not *quite*
pedantically correct. Pedantically speaking. If an element is
disabled before an XHR starts, this code "should" notice that and
not include it in the to-re-enable list. That would be annoying
to do, and becomes impossible to do properly once multiple XHRs
are in transit and an element is disabled seprately between two
of those in-transit requests (that would be an unlikely, but
possible, corner case). As of this writing, the only elements
which are ever normally programmatically toggled between
enabled/disabled...
1) Belong to the file selection list and remain disabled until
the list of leaves and files are loaded. i.e. they would be
disabled *anyway* during their own XHR requests.
2) The stashWidget's SELECT list when no local edits are
stashed. Curiously, the all-or-nothing re-enabling implemented
here does not re-enable that particular selection list. That's
because of timing, though: that widget is "manually" disabled
when the list is empty, and that list is normally emptied in
conjunction with an XHR request.
*/
const ajaxState = {
count: 0 /* in-flight F.fetch() requests */,
toDisable: undefined /* elements to disable during ajax activity */
};
F.fetch.beforesend = function f(){
if(!ajaxState.toDisable){
ajaxState.toDisable = document.querySelectorAll(
'button, input, select, textarea'
);
}
if(1===++ajaxState.count){
D.addClass(document.body, 'waiting');
D.disable(ajaxState.toDisable);
}
};
F.fetch.aftersend = function(){
if(0===--ajaxState.count){
D.removeClass(document.body, 'waiting');
D.enable(ajaxState.toDisable);
}
};
F.onPageLoad(function() {
P.base = {tag: E('base')};
P.base.originalHref = P.base.tag.href;
P.tabs = new fossil.TabManager('#fileedit-tabs');
P.e = { /* various DOM elements we work with... */
taEditor: E('#fileedit-content-editor'),
taCommentSmall: E('#fileedit-comment'),
taCommentBig: E('#fileedit-comment-big'),
taComment: undefined/*gets set to one of taComment{Big,Small}*/,
ajaxContentTarget: E('#ajax-target'),
btnCommit: E("#fileedit-btn-commit"),
btnReload: E("#fileedit-tab-content button.fileedit-content-reload"),
selectPreviewMode: E('#select-preview-mode select'),
selectHtmlEmsWrap: E('#select-preview-html-ems'),
selectEolWrap: E('#select-eol-style'),
selectEol: E('#select-eol-style select[name=eol]'),
selectFontSizeWrap: E('#select-font-size'),
selectDiffWS: E('select[name=diff_ws]'),
cbLineNumbersWrap: E('#cb-line-numbers'),
cbAutoPreview: E('#cb-preview-autoupdate > input[type=checkbox]'),
previewTarget: E('#fileedit-tab-preview-wrapper'),
manifestTarget: E('#fileedit-manifest'),
diffTarget: E('#fileedit-tab-diff-wrapper'),
cbIsExe: E('input[type=checkbox][name=exec_bit]'),
cbManifest: E('input[type=checkbox][name=include_manifest]'),
fsFileVersionDetails: E('#file-version-details'),
tabs:{
content: E('#fileedit-tab-content'),
preview: E('#fileedit-tab-preview'),
diff: E('#fileedit-tab-diff'),
commit: E('#fileedit-tab-commit'),
fileSelect: E('#fileedit-tab-fileselect')
}
};
/* Figure out which comment editor to show by default and
hide the other one. By default we take the one which does
not have the 'hidden' CSS class. If neither do, we default
to single-line mode. */
if(D.hasClass(P.e.taCommentSmall, 'hidden')){
P.e.taComment = P.e.taCommentBig;
}else if(D.hasClass(P.e.taCommentBig,'hidden')){
P.e.taComment = P.e.taCommentSmall;
}else{
P.e.taComment = P.e.taCommentSmall;
D.addClass(P.e.taCommentBig, 'hidden');
}
D.removeClass(P.e.taComment, 'hidden');
P.tabs.e.container.insertBefore(
/* Move the status bar between the tab buttons and
tab panels. Seems to be the best fit in terms of
functionality and visibility. */
E('#fossil-status-bar'), P.tabs.e.tabs
);
P.tabs.addEventListener(
/* Set up auto-refresh of the preview tab... */
'before-switch-to', function(ev){
if(ev.detail===P.e.tabs.preview){
P.baseHrefForFile();
if(P.e.cbAutoPreview.checked) P.preview();
}else if(ev.detail===P.e.tabs.diff){
/* Work around a weird bug where the page gets wider than
the window when the diff tab is NOT in view and the
current SBS diff widget is wider than the window. When
the diff IS in view then CSS overflow magically reduces
the page size again. Weird. Maybe FF-specific. Note that
this weirdness happens even though P.e.diffTarget's parent
is hidden (and therefore P.e.diffTarget is also hidden).
*/
D.removeClass(P.e.diffTarget, 'hidden');
}
}
);
P.tabs.addEventListener(
/* Set up auto-refresh of the preview tab... */
'before-switch-from', function(ev){
if(ev.detail===P.e.tabs.preview){
P.baseHrefRestore();
}else if(ev.detail===P.e.tabs.diff){
/* See notes in the before-switch-to handler. */
D.addClass(P.e.diffTarget, 'hidden');
}
}
);
F.connectPagePreviewers(
P.e.tabs.preview.querySelector(
'#btn-preview-refresh'
)
);
const diffButtons = E('#fileedit-tab-diff-buttons');
diffButtons.querySelector('button.sbs').addEventListener(
"click",(e)=>P.diff(true), false
);
diffButtons.querySelector('button.unified').addEventListener(
"click",(e)=>P.diff(false), false
);
P.e.btnCommit.addEventListener(
"click",(e)=>P.commit(), false
);
F.confirmer(P.e.btnReload, {
confirmText: "Really reload, losing edits?",
onconfirm: (e)=>P.unstashContent().loadFile(),
ticks: 3
});
E('#comment-toggle').addEventListener(
"click",(e)=>P.toggleCommentMode(), false
);
P.e.taEditor.addEventListener(
'change', ()=>P.stashContentChange(), false
);
P.e.cbIsExe.addEventListener(
'change', ()=>P.stashContentChange(true), false
);
/**
Cosmetic: jump through some hoops to enable/disable
certain preview options depending on the current
preview mode...
*/
P.e.selectPreviewMode.addEventListener(
"change", function(e){
const mode = e.target.value,
name = P.previewModes[mode],
hide = [], unhide = [];
P.previewModes.current = name;
if('guess'===name){
unhide.push(P.e.cbLineNumbersWrap,
P.e.selectHtmlEmsWrap);
}else{
if('text'===name) unhide.push(P.e.cbLineNumbersWrap);
else hide.push(P.e.cbLineNumbersWrap);
if('htmlIframe'===name) unhide.push(P.e.selectHtmlEmsWrap);
else hide.push(P.e.selectHtmlEmsWrap);
}
hide.forEach((e)=>e.classList.add('hidden'));
unhide.forEach((e)=>e.classList.remove('hidden'));
}, false
);
P.selectPreviewMode(false, true);
const selectFontSize = E('select[name=editor_font_size]');
if(selectFontSize){
selectFontSize.addEventListener(
"change",function(e){
const ed = P.e.taEditor;
ed.className = ed.className.replace(
/\bfont-size-\d+/g, '' );
ed.classList.add('font-size-'+e.target.value);
}, false
);
selectFontSize.dispatchEvent(
// Force UI update
new Event('change',{target:selectFontSize})
);
}
P.addEventListener(
// Clear certain views when new content is loaded/set
'fileedit-content-replaced',
()=>D.clearElement(P.e.diffTarget, P.e.previewTarget, P.e.manifestTarget)
);
P.addEventListener(
// Clear certain views after a non-dry-run commit
'fileedit-committed',
(e)=>{
if(!e.detail.dryRun){
D.clearElement(P.e.diffTarget, P.e.previewTarget);
}
}
);
P.fileSelectWidget.init();
P.stashWidget.init(
P.e.tabs.content.lastElementChild
//P.e.tabs.fileSelect.querySelector("h1")
);
}/*F.onPageLoad()*/);
/**
Getter (if called with no args) or setter (if passed an arg) for
the current file content.
The setter form sets the content, dispatches a
'fileedit-content-replaced' event, and returns this object.
*/
P.fileContent = function f(){
if(0===arguments.length){
return f.get();
}else{
f.set(arguments[0] || '');
this.dispatchEvent('fileedit-content-replaced', this);
return this;
}
};
/* Default get/set impls for file content */
P.fileContent.get = function(){return P.e.taEditor.value};
P.fileContent.set = function(content){P.e.taEditor.value = content};
/**
For use when installing a custom editor widget. Pass it the
getter and setter callbacks to fetch resp. set the content of the
custom widget. They will be triggered via
P.fileContent(). Returns this object.
*/
P.setFileContentMethods = function(getter, setter){
this.fileContent.get = getter;
this.fileContent.set = setter;
return this;
};
/**
Removes the default editor widget (and any dependent elements)
from the DOM, adds the given element in its place, removes this
method from this object, and returns this object.
*/
P.replaceEditorElement = function(newEditor){
P.e.taEditor.parentNode.insertBefore(newEditor, P.e.taEditor);
P.e.taEditor.remove();
P.e.selectFontSizeWrap.remove();
delete this.replaceEditorElement;
return P;
};
/**
If either of...
- P.previewModes.current==='wiki'
- P.previewModes.current==='guess' AND the currently-loaded file
has a mimetype of "text/x-fossil-wiki" or "text/x-markdown".
... then this function updates the document's base.href to a
repo-relative /doc/{{this.finfo.checkin}}/{{directory part of
this.finfo.filename}}/
If neither of those conditions applies, this is a no-op.
*/
P.baseHrefForFile = function f(){
const fn = this.finfo ? this.finfo.filename : undefined;
if(!fn) return this;
if(!f.wikiMimeTypes){
f.wikiMimeTypes = ["text/x-fossil-wiki", "text/x-markdown"];
}
if('wiki'===P.previewModes.current
|| ('guess'===P.previewModes.current
&& f.wikiMimeTypes.indexOf(this.finfo.mimetype)>=0)){
const a = fn.split('/');
a.pop();
this.base.tag.href = F.repoUrl(
'doc/'+F.hashDigits(this.finfo.checkin)
+'/'+(a.length ? a.join('/')+'/' : '')
);
}
return this;
};
/**
Sets the document's base.href value to its page-load-time
setting.
*/
P.baseHrefRestore = function(){
P.base.tag.href = P.base.originalHref;
};
/**
Toggles between single- and multi-line comment
mode.
*/
P.toggleCommentMode = function(){
var s, h, c = this.e.taComment.value;
if(this.e.taComment === this.e.taCommentSmall){
s = this.e.taCommentBig;
h = this.e.taCommentSmall;
}else{
s = this.e.taCommentSmall;
h = this.e.taCommentBig;
/*
Doing (input[type=text].value = textarea.value) unfortunately
strips all newlines. To compensate we'll replace each EOL with
a space. Not ideal. If we were to instead escape them as \n,
and do the reverse when toggling again, then they would get
committed as escaped newlines if the user did not first switch
back to multi-line mode. We cannot blindly unescape the
newlines, in the off chance that the user actually enters \n
in the comment.
*/
c = c.replace(/\r?\n/g,' ');
}
s.value = c;
this.e.taComment = s;
D.addClass(h, 'hidden');
D.removeClass(s, 'hidden');
};
/**
Returns true if fossil.page.finfo is set, indicating that a file
has been loaded, else it reports an error and returns false.
If passed a truthy value any error message about not having
a file loaded is suppressed.
*/
const affirmHasFile = function(quiet){
if(!P.finfo){
if(!quiet) F.error("No file is loaded.");
}
return !!P.finfo;
};
/**
updateVersion() updates the filename and version in various UI
elements...
Returns this object.
*/
P.updateVersion = function(file,rev){
if(1===arguments.length){/*assume object*/
this.finfo = arguments[0];
file = this.finfo.filename;
rev = this.finfo.checkin;
}else if(0===arguments.length){
if(!affirmHasFile()) return this;
file = this.finfo.filename;
rev = this.finfo.checkin;
}else{
this.finfo = {filename:file,checkin:rev};
}
const eTgt = this.e.fsFileVersionDetails.querySelector('div'),
rHuman = F.hashDigits(rev),
rUrl = F.hashDigits(rev,true);
D.clearElement(eTgt);
D.append(
eTgt, "File: ",
D.append(D.code(),
D.a(F.repoUrl('finfo',{name:file, m:rUrl}), file)),
D.br()
);
D.append(
eTgt, "Checkin: ",
D.append(D.code(), D.a(F.repoUrl('info/'+rUrl), rHuman)),
" [",D.a(F.repoUrl('timeline',{m:rUrl}), "timeline"),"]",
D.br()
);
D.append(
eTgt, "Mimetype: ",
D.append(D.code(), this.finfo.mimetype||'???'),
D.br()
);
D.append(
eTgt,
D.append(D.code(), "[",
D.a(F.repoUrl('annotate',{filename:file, checkin:rUrl}),
'annotate'), "]"),
D.append(D.code(), "[",
D.a(F.repoUrl('blame',{filename:file, checkin:rUrl}),
'blame'), "]")
);
const purlArgs = F.encodeUrlArgs({
filename: this.finfo.filename,
checkin: rUrl
},false,true);
const purl = F.repoUrl('fileedit',purlArgs);
D.append(
eTgt,
D.append(D.code(),
"[",D.a(purl,"Editor permalink"),"]")
);
this.setPageTitle("Edit: "+this.finfo.filename);
return this;
};
/**
loadFile() loads (file,checkinVersion) and updates the relevant
UI elements to reflect the loaded state. If passed no arguments
then it re-uses the values from the currently-loaded file, reloading
it (emitting an error message if no file is loaded).
Returns this object, noting that the load is async. After loading
it triggers a 'fileedit-file-loaded' event, passing it
this.finfo.
If a locally-edited copy of the given file/rev is found, that
copy is used instead of one fetched from the server, but it is
still treated as a load event.
Alternate call forms:
- no arguments: re-loads from this.finfo.
- 1 argument: assumed to be an finfo-style object. Must have at
least {filename, checkin} properties, but need not have other
finfo state.
*/
P.loadFile = function(file,rev){
if(0===arguments.length){
/* Reload from this.finfo */
if(!affirmHasFile()) return this;
file = this.finfo.filename;
rev = this.finfo.checkin;
}else if(1===arguments.length){
/* Assume finfo-like object */
const arg = arguments[0];
file = arg.filename;
rev = arg.checkin;
}
const self = this;
const onload = (r,headers)=>{
delete self.finfo;
self.updateVersion({
filename: file,
checkin: rev,
branch: headers['x-fileedit-checkin-branch'],
isExe: ('x'===headers['x-fileedit-file-perm']),
mimetype: headers['content-type'].split(';').shift()
});
self.tabs.switchToTab(self.e.tabs.content);
self.e.cbIsExe.checked = self.finfo.isExe;
self.fileContent(r);
self.dispatchEvent('fileedit-file-loaded', self.finfo);
};
const semiFinfo = {filename: file, checkin: rev};
const stashFinfo = this.getStashedFinfo(semiFinfo);
if(stashFinfo){ // fake a response from the stash...
this.finfo = stashFinfo;
this.e.cbIsExe.checked = !!stashFinfo.isExe;
onload(this.contentFromStash()||'',{
'x-fileedit-file-perm': stashFinfo.isExe ? 'x' : undefined,
'content-type': stashFinfo.mimetype,
'x-fileedit-checkin-branch': stashFinfo.branch
});
F.message("Fetched from the local-edit storage:",
F.hashDigits(stashFinfo.checkin),
stashFinfo.filename);
return this;
}
F.message(
"Loading content..."
).fetch('fileedit/content',{
urlParams: {
filename:file,
checkin:rev
},
responseHeaders: [
'x-fileedit-file-perm',
'x-fileedit-checkin-branch',
'content-type'],
onload:(r,headers)=>{
onload(r,headers);
F.message('Loaded content for',
F.hashDigits(self.finfo.checkin),
self.finfo.filename);
}
});
return this;
};
/**
Fetches the page preview based on the contents and settings of
this page's input fields, and updates the UI with with the
preview.
Returns this object, noting that the operation is async.
*/
P.preview = function f(switchToTab){
if(!affirmHasFile()) return this;
const target = this.e.previewTarget,
self = this;
const updateView = function(c){
D.clearElement(target);
if('string'===typeof c) target.innerHTML = c;
if(switchToTab) self.tabs.switchToTab(self.e.tabs.preview);
};
return this._postPreview(this.fileContent(), updateView);
};
/**
Callback for use with F.connectPagePreviewers()
*/
P._postPreview = function(content,callback){
if(!affirmHasFile()) return this;
if(!content){
callback(content);
return this;
}
const fd = new FormData();
fd.append('render_mode',this.e.selectPreviewMode.value);
fd.append('filename',this.finfo.filename);
fd.append('ln',E('[name=preview_ln]').checked ? 1 : 0);
fd.append('iframe_height', E('[name=preview_html_ems]').value);
fd.append('content',content || '');
F.message(
"Fetching preview..."
).fetch('ajax/preview-text',{
payload: fd,
responseHeaders: 'x-ajax-render-mode',
onload: (r,header)=>{
P.selectPreviewMode(P.previewModes[header]);
if('wiki'===header) P.baseHrefForFile();
else P.baseHrefRestore();
callback(r);
F.message('Updated preview.');
P.dispatchEvent('fileedit-preview-updated',{
previewMode: P.previewModes.current,
mimetype: P.finfo.mimetype,
element: P.e.previewTarget
});
},
onerror: (e)=>{
fossil.fetch.onerror(e);
callback("Error fetching preview: "+e);
}
});
return this;
};
/**
Undo some of the SBS diff-rendering bits which hurt us more than
they help...
*/
P.tweakSbsDiffs2 = function(){
if(1){
const dt = this.e.diffTarget;
dt.querySelectorAll('.sbsdiffcols .difftxtcol').forEach(
(dtc)=>{
const pre = dtc.querySelector('pre');
pre.style.width = 'initial';
//pre.removeAttribute('style');
//console.debug("pre width =",pre.style.width);
}
);
}
this.tweakSbsDiffs();
};
/**
Fetches the content diff based on the contents and settings of
this page's input fields, and updates the UI with the diff view.
Returns this object, noting that the operation is async.
*/
P.diff = function f(sbs){
if(!affirmHasFile()) return this;
const content = this.fileContent(),
self = this,
target = this.e.diffTarget;
const fd = new FormData();
fd.append('filename',this.finfo.filename);
fd.append('checkin', this.finfo.checkin);
fd.append('sbs', sbs ? 1 : 0);
fd.append('content',content);
if(this.e.selectDiffWS) fd.append('ws',this.e.selectDiffWS.value);
F.message(
"Fetching diff..."
).fetch('fileedit/diff',{
payload: fd,
onload: function(c){
target.innerHTML = [
"<div>Diff <code>[",
self.finfo.checkin,
"]</code> → Local Edits</div>",
c||'No changes.'
].join('');
if(sbs) P.tweakSbsDiffs2();
F.message('Updated diff.');
self.tabs.switchToTab(self.e.tabs.diff);
}
});
return this;
};
/**
Performs an async commit based on the form contents and updates
the UI.
Returns this object.
*/
P.commit = function f(){
if(!affirmHasFile()) return this;
const self = this;
const content = this.fileContent(),
target = D.clearElement(P.e.manifestTarget),
cbDryRun = E('[name=dry_run]'),
isDryRun = cbDryRun.checked,
filename = this.finfo.filename;
if(!f.onload){
f.onload = function(c){
const oldFinfo = JSON.parse(JSON.stringify(self.finfo))
if(c.manifest){
target.innerHTML = [
"<h3>Manifest",
(c.dryRun?" (dry run)":""),
": ", F.hashDigits(c.checkin),"</h3>",
"<code class='fileedit-manifest'>",
c.manifest,
"</code></pre>"
].join('');
delete c.manifest/*so we don't stash this with finfo*/;
}
const msg = [
'Committed',
c.dryRun ? '(dry run)' : '',
'[', F.hashDigits(c.checkin) ,'].'
];
if(!c.dryRun){
self.unstashContent(oldFinfo);
self.finfo = c;
self.e.taComment.value = '';
self.updateVersion();
self.fileSelectWidget.loadLeaves();
}
self.dispatchEvent('fileedit-committed', c);
F.message.apply(F, msg);
self.tabs.switchToTab(self.e.tabs.commit);
};
}
const fd = new FormData();
fd.append('filename',filename);
fd.append('checkin', this.finfo.checkin);
fd.append('content',content);
fd.append('dry_run',isDryRun ? 1 : 0);
fd.append('eol', this.e.selectEol.value || 0);
/* Text fields or select lists... */
fd.append('comment', this.e.taComment.value);
if(0){
// Comment mimetype is currently not supported by the UI...
['comment_mimetype'
].forEach(function(name){
var e = E('[name='+name+']');
if(e) fd.append(name,e.value);
});
}
/* Checkboxes: */
['allow_fork',
'allow_older',
'exec_bit',
'allow_merge_conflict',
'include_manifest',
'prefer_delta'
].forEach(function(name){
var e = E('[name='+name+']');
if(e){
fd.append(name, e.checked ? 1 : 0);
}else{
console.error("Missing checkbox? name =",name);
}
});
F.message(
"Checking in..."
).fetch('fileedit/commit',{
payload: fd,
responseType: 'json',
onload: f.onload
});
return this;
};
/**
Updates P.finfo for certain state and stashes P.finfo, with the
current content fetched via P.fileContent().
If passed truthy AND the stash already has stashed content for
the current file, only the stashed finfo record is updated, else
both the finfo and content are updated.
*/
P.stashContentChange = function(onlyFinfo){
if(affirmHasFile(true)){
const fi = this.finfo;
fi.isExe = this.e.cbIsExe.checked;
if(!fi.branch) fi.branch = this.fileSelectWidget.checkinBranchName(fi.checkin);
if(onlyFinfo && $stash.hasStashedContent(fi)){
$stash.updateFile(fi);
}else{
$stash.updateFile(fi, P.fileContent());
}
F.message("Stashed change to",F.hashDigits(fi.checkin),fi.filename);
$stash.prune();
}
return this;
};
/**
Removes any stashed state for the current P.finfo (if set) from
F.storage. Returns this.
*/
P.unstashContent = function(){
const finfo = arguments[0] || this.finfo;
if(finfo){
$stash.unstash(finfo);
//console.debug("Unstashed",finfo);
F.message("Unstashed",F.hashDigits(finfo.checkin),finfo.filename);
}
return this;
};
/**
Clears all stashed file state from F.storage. Returns this.
*/
P.clearStash = function(){
$stash.clear();
return this;
};
/**
If stashed content for P.finfo exists, it is returned, else
undefined is returned.
*/
P.contentFromStash = function(){
return affirmHasFile(true) ? $stash.stashedContent(this.finfo) : undefined;
};
/**
If a stashed version of the given finfo object exists (same
filename/checkin values), return it, else return undefined.
*/
P.getStashedFinfo = function(finfo){
return $stash.getFinfo(finfo);
};
P.$stash = $stash /*only for testing/debugging - not part of the API.*/;
})(window.fossil);
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
(function(F){
/**
fossil.store is a basic wrapper around localStorage
or sessionStorage or a dummy proxy object if neither
of those are available.
*/
const tryStorage = function f(obj){
if(!f.key) f.key = 'fossil.access.check';
try{
obj.setItem(f.key, 'f');
const x = obj.getItem(f.key);
obj.removeItem(f.key);
if(x!=='f') throw new Error(f.key+" failed")
return obj;
}catch(e){
return undefined;
}
};
/** Internal storage impl for fossil.storage. */
const $storage =
tryStorage(window.localStorage)
|| tryStorage(window.sessionStorage)
|| tryStorage({
// A basic dummy xyzStorage stand-in
$:{},
setItem: function(k,v){this.$[k]=v},
getItem: function(k){
return this.$.hasOwnProperty(k) ? this.$[k] : undefined;
},
removeItem: function(k){delete this.$[k]},
clear: function(){this.$={}}
});
/**
For the dummy storage we need to differentiate between
$storage and its real property storage for hasOwnProperty()
to work properly...
*/
const $storageHolder = $storage.hasOwnProperty('$') ? $storage.$ : $storage;
/**
A proxy for localStorage or sessionStorage or a
page-instance-local proxy, if neither one is availble.
Which exact storage implementation is uses is unspecified, and
apps must not rely on it.
*/
fossil.storage = {
/** Sets the storage key k to value v, implicitly converting
it to a string. */
set: (k,v)=>$storage.setItem(k,v),
/** Sets storage key k to JSON.stringify(v). */
setJSON: (k,v)=>$storage.setItem(k,JSON.stringify(v)),
/** Returns the value for the given storage key, or
dflt if the key is not found in the storage. */
get: (k,dflt)=>$storageHolder.hasOwnProperty(k) ? $storage.getItem(k) : dflt,
/** Returns the JSON.parse()'d value of the given
storage key's value, or dflt is the key is not
found or JSON.parse() fails. */
getJSON: function f(k,dflt){
try {
const x = this.get(k,f);
return x===f ? dflt : JSON.parse(x);
}
catch(e){return dflt}
},
/** Returns true if the storage contains the given key,
else false. */
contains: (k)=>$storageHolder.hasOwnProperty(k),
/** Removes the given key from the storage. Returns this. */
remove: function(k){
$storage.removeItem(k);
return this;
},
/** Clears ALL keys from the storage. Returns this. */
clear: function(){
$storage.clear();
return this;
},
/** Returns an array of all keys currently in the storage. */
keys: ()=>Object.keys($storageHolder),
/** Returns true if this storage is transient (only available
until the page is reloaded), indicating that fileStorage
and sessionStorage are unavailable. */
isTransient: ()=>$storageHolder!==$storage,
/** Returns a symbolic name for the current storage mechanism. */
storageImplName: function(){
if($storage===window.localStorage) return 'localStorage';
else if($storage===window.sessionStorage) return 'sessionStorage';
else return 'transient';
}
};
})(window.fossil);
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
"use strict";
(function(F/*fossil object*/){
const E = (s)=>document.querySelector(s),
EA = (s)=>document.querySelectorAll(s),
D = F.dom;
/**
Creates a TabManager. If passed an argument, it is
passed to init().
*/
const TabManager = function(domElem){
this.e = {};
if(domElem) this.init(domElem);
};
/**
Internal helper to normalize a method argument
to a tab element.
*/
const tabArg = function(arg,tabMgr){
if('string'===typeof arg) arg = E(arg);
else if(tabMgr && 'number'===typeof arg && arg>=0){
arg = tabMgr.e.tabs.childNodes[arg];
}
return arg;
};
const setVisible = function(e,yes){
D[yes ? 'removeClass' : 'addClass'](e, 'hidden');
};
TabManager.prototype = {
/**
Initializes the tabs associated with the given tab container
(DOM element or selector for a single element). This must be
called once before using any other member functions of a given
instance, noting that the constructor will call this if it is
passed an argument.
The tab container must have an 'id' attribute. This function
looks through the DOM for all elements which have
data-tab-parent=thatId. For each one it creates a button to
switch to that tab and moves the element into this.e.tabs.
The label for each tab is set by the data-tab-label attribute
of each element, defaulting to something not terribly useful.
When it's done, it auto-selects the first tab unless a tab has
a truthy numeric value in its data-tab-select attribute, in
which case the last tab to have such a property is selected.
This method must only be called once per instance. TabManagers
may be nested but must not share any tabs instances.
Returns this object.
DOM elements of potential interest to users:
this.e.container = the outermost container element.
this.e.tabBar = the button bar. Each "button" (whether it's a
buttor not is unspecified) has a class of .tab-button.
this.e.tabs = the parent for all of the tab elements.
It is legal, within reason, to manipulate these a bit, in
particular this.e.container, e.g. by adding more children to
it. Do not remove elements from the tabs or tabBar, however, or
the tab state may get sorely out of sync.
CSS classes: the container element has whatever class(es) the
client sets on. this.e.tabBar gets the 'tab-bar' class and
this.e.tabs gets the 'tabs' class. It's hypothetically possible
to move the tabs to either side or the bottom using only CSS,
but it's never been tested.
*/
init: function(container){
container = tabArg(container);
const cID = container.getAttribute('id');
if(!cID){
throw new Error("Tab container element is missing 'id' attribute.");
}
const c = this.e.container = container;
this.e.tabBar = D.addClass(D.div(),'tab-bar');
this.e.tabs = D.addClass(D.div(),'tabs');
D.append(c, this.e.tabBar, this.e.tabs);
let selectIndex = 0;
EA('[data-tab-parent='+cID+']').forEach((c,n)=>{
if(+c.dataset.tabSelect) selectIndex=n;
this.addTab(c);
});
return this.switchToTab(selectIndex);
},
/**
For the given tab element, unique selector string, or integer
(0-based tab number), returns the button associated with that
tab, or undefined if the argument does not match any current
tab.
*/
getButtonForTab: function(tab){
tab = tabArg(tab,this);
var i = -1;
this.e.tabs.childNodes.forEach(function(e,n){
if(e===tab) i = n;
});
return i>=0 ? this.e.tabBar.childNodes[i] : undefined;
},
/**
Adds the given DOM element or unique selector as the next
tab in the tab container, adding a button to switch to
the tab. Returns this object.
*/
addTab: function f(tab){
if(!f.click){
f.click = function(e){
e.target.$manager.switchToTab(e.target.$tab);
};
}
tab = tabArg(tab);
tab.remove();
D.append(this.e.tabs, D.addClass(tab,'tab-panel'));
const lbl = tab.dataset.tabLabel || 'Tab #'+(this.e.tabs.childNodes.length-1);
const btn = D.addClass(D.append(D.span(), lbl), 'tab-button');
D.append(this.e.tabBar,btn);
btn.$manager = this;
btn.$tab = tab;
btn.addEventListener('click', f.click, false);
return this;
},
/**
Internal. Fires a new CustomEvent to all listeners which have
registered via this.addEventListener().
*/
_dispatchEvent: function(name, detail){
try{
this.e.container.dispatchEvent(
new CustomEvent(name, {detail: detail})
);
}catch(e){
/* ignore */
}
return this;
},
/**
Registers an event listener for this object's custom events.
The callback gets a CustomEvent object with a 'detail'
propertly holding any tab-related state for the event. The events
are:
- 'before-switch-from' is emitted immediately before a new tab
is switched away from. detail = the tab element being switched
away from.
- 'before-switch-to' is emitted immediately before a new tab is
switched to. detail = the tab element.
- 'after-switch-to' is emitted immediately after a new tab is
switched to. detail = the tab element.
Any exceptions thrown by listeners are caught and ignored, to
avoid that they knock the tab state out of sync.
Returns this object.
*/
addEventListener: function(eventName, callback){
this.e.container.addEventListener(eventName, callback, false);
return this;
},
/**
If the given DOM element, unique selector, or integer (0-based
tab number) is one of this object's tabs, the UI makes that tab
the currently-visible one, firing any relevant events. Returns
this object. If the argument is the current tab, this is a
no-op, and no events are fired.
*/
switchToTab: function(tab){
tab = tabArg(tab,this);
const self = this;
if(tab===this._currentTab) return this;
else if(this._currentTab){
this._dispatchEvent('before-switch-from', this._currentTab);
}
delete this._currentTab;
this.e.tabs.childNodes.forEach((e,ndx)=>{
const btn = this.e.tabBar.childNodes[ndx];
if(e===tab){
if(D.hasClass(e,'selected')){
return;
}
self._dispatchEvent('before-switch-to',tab);
setVisible(e, true);
this._currentTab = e;
D.addClass(btn,'selected');
self._dispatchEvent('after-switch-to',tab);
}else{
if(D.hasClass(e,'selected')){
return;
}
setVisible(e, false);
D.removeClass(btn,'selected');
}
});
return this;
}
};
F.TabManager = TabManager;
})(window.fossil);
|
| ︙ | ︙ | |||
55 56 57 58 59 60 61 62 63 64 65 66 |
int nArg;
int mxArg = 0;
int n, i;
char **azArg = 0;
int fDebug;
pid_t childPid;
char *zLine = 0;
fDebug = find_option("debug", 0, 0)!=0;
db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
db_close(0);
sqlite3_shutdown();
linenoiseSetMultiLine(1);
| > > > > > > | | 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 |
int nArg;
int mxArg = 0;
int n, i;
char **azArg = 0;
int fDebug;
pid_t childPid;
char *zLine = 0;
char *zPrompt = 0;
fDebug = find_option("debug", 0, 0)!=0;
db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
if(g.zRepositoryName!=0){
zPrompt = mprintf("fossil (%z)> ", db_get("project-name","unnamed"));
}else{
zPrompt = mprintf("fossil (no repo)> ");
}
db_close(0);
sqlite3_shutdown();
linenoiseSetMultiLine(1);
while( (free(zLine), zLine = linenoise(zPrompt)) ){
/* Remember shell history within the current session */
linenoiseHistoryAdd(zLine);
/* Parse the line of input */
n = (int)strlen(zLine);
for(i=0, nArg=1; i<n; i++){
while( fossil_isspace(zLine[i]) ){ i++; }
|
| ︙ | ︙ | |||
107 108 109 110 111 112 113 |
printf("could not fork a child process to handle the command\n");
fflush(stdout);
continue;
}
if( childPid==0 ){
/* This is the child process */
int main(int, char**);
| | > | 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
printf("could not fork a child process to handle the command\n");
fflush(stdout);
continue;
}
if( childPid==0 ){
/* This is the child process */
int main(int, char**);
fossil_main(nArg, azArg);
exit(0);
}else{
/* The parent process */
int status;
waitpid(childPid, &status, 0);
}
}
free(zPrompt);
#endif
}
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
/*
** Copyright (c) 2019 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
*
** 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.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code to connect Fossil to libFuzzer. Do a web search
** for "libfuzzer" for details about that fuzzing platform.
**
** To build on linux (the only platform for which this works at
** present) first do
**
** ./configure
**
** Then edit the Makefile as follows:
**
** (1) Change CC to be "clang-6.0" or some other compiler that
** supports libFuzzer
**
** (2) Chagne APPNAME to "fossil-fuzz"
**
** (3) Add "-fsanitize=fuzzer" and "-DFOSSIL_FUZZ" to TCCFLAGS. Perhaps
** make the first change "-fsanitize=fuzzer,undefined,address" for
** extra, but slower, testing.
**
** Then build the fuzzer using:
**
** make clean fossil-fuzz
**
** To run the fuzzer, create a working directory ("cases"):
**
** mkdir cases
**
** Then seed the working directory with example input files. For example,
** if fuzzing the wiki formatter, perhaps copy *.wiki into cases. Then
** run the fuzzer thusly:
**
** fossil-fuzz cases
**
** The default is to fuzz the Fossil-wiki translator. Use the --fuzztype TYPE
** option to fuzz different aspects of the system.
*/
#include "config.h"
#include "fuzz.h"
#if LOCAL_INTERFACE
/*
** Type of fuzzing:
*/
#define FUZZ_WIKI 0 /* The Fossil-Wiki formatter */
#define FUZZ_MARKDOWN 1 /* The Markdown formatter */
#define FUZZ_ARTIFACT 2 /* Fuzz the artifact parser */
#endif
/* The type of fuzzing to do */
static int eFuzzType = FUZZ_WIKI;
/* The fuzzer invokes this routine once for each fuzzer input
*/
int LLVMFuzzerTestOneInput(const uint8_t *aData, size_t nByte){
Blob in, out;
blob_init(&in, 0, 0);
blob_append(&in, (char*)aData, (int)nByte);
blob_zero(&out);
switch( eFuzzType ){
case FUZZ_WIKI: {
Blob title = BLOB_INITIALIZER;
wiki_convert(&in, &out, 0);
blob_reset(&out);
markdown_to_html(&in, &title, &out);
blob_reset(&title);
break;
}
}
blob_reset(&in);
blob_reset(&out);
return 0;
}
/*
** Check fuzzer command-line options.
*/
static void fuzzer_options(void){
const char *zType;
db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
db_multi_exec("PRAGMA query_only=1;");
zType = find_option("fuzztype",0,1);
if( zType==0 || fossil_strcmp(zType,"wiki")==0 ){
eFuzzType = FUZZ_WIKI;
}else if( fossil_strcmp(zType,"markdown")==0 ){
eFuzzType = FUZZ_MARKDOWN;
}else{
fossil_fatal("unknown fuzz type: \"%s\"", zType);
}
}
/* Libfuzzer invokes this routine once prior to start-up to
** process command-line options.
*/
int LLVMFuzzerInitialize(int *pArgc, char ***pArgv){
expand_args_option(*pArgc, *pArgv);
fuzzer_options();
*pArgc = g.argc;
*pArgv = g.argv;
return 0;
}
/*
** COMMAND: test-fuzz
**
** Usage: %fossil test-fuzz [-type TYPE] INPUTFILE...
**
** Run a fuzz test using INPUTFILE as the test data. TYPE can be one of:
**
** wiki Fuzz the Fossil-wiki translator
** markdown Fuzz the markdown translator
** artifact Fuzz the artifact parser
*/
void fuzz_command(void){
Blob in;
int i;
fuzzer_options();
verify_all_options();
for(i=2; i<g.argc; i++){
blob_read_from_file(&in, g.argv[i], ExtFILE);
LLVMFuzzerTestOneInput((const uint8_t*)in.aData, (size_t)in.nUsed);
fossil_print("%s\n", g.argv[i]);
blob_reset(&in);
}
}
|
| ︙ | ︙ | |||
54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
** but which are included just so that we can capture their background color.
*/
struct GraphRow {
int rid; /* The rid for the check-in */
i8 nParent; /* Number of parents. */
i8 nCherrypick; /* Subset of aParent that are cherrypicks */
i8 nNonCherrypick; /* Number of non-cherrypick parents */
int *aParent; /* Array of parents. 0 element is primary .*/
char *zBranch; /* Branch name */
char *zBgClr; /* Background Color */
char zUuid[HNAME_MAX+1]; /* Check-in for file ID */
GraphRow *pNext; /* Next row down in the list of all rows */
GraphRow *pPrev; /* Previous row */
| > | 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
** but which are included just so that we can capture their background color.
*/
struct GraphRow {
int rid; /* The rid for the check-in */
i8 nParent; /* Number of parents. */
i8 nCherrypick; /* Subset of aParent that are cherrypicks */
i8 nNonCherrypick; /* Number of non-cherrypick parents */
u8 nMergeChild; /* Number of merge children */
int *aParent; /* Array of parents. 0 element is primary .*/
char *zBranch; /* Branch name */
char *zBgClr; /* Background Color */
char zUuid[HNAME_MAX+1]; /* Check-in for file ID */
GraphRow *pNext; /* Next row down in the list of all rows */
GraphRow *pPrev; /* Previous row */
|
| ︙ | ︙ | |||
470 471 472 473 474 475 476 |
** the current check-in. If a merge parent is not in the visible section
** of this graph, then no arrows will be drawn for it, so remove it from
** the aParent[] array.
*/
if( (tmFlags & (TIMELINE_DISJOINT|TIMELINE_XMERGE))!=0 ){
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
for(i=1; i<pRow->nParent; i++){
| | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
** the current check-in. If a merge parent is not in the visible section
** of this graph, then no arrows will be drawn for it, so remove it from
** the aParent[] array.
*/
if( (tmFlags & (TIMELINE_DISJOINT|TIMELINE_XMERGE))!=0 ){
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
for(i=1; i<pRow->nParent; i++){
GraphRow *pParent = hashFind(p, pRow->aParent[i]);
if( pParent==0 ){
memmove(pRow->aParent+i, pRow->aParent+i+1,
sizeof(pRow->aParent[0])*(pRow->nParent-i-1));
pRow->nParent--;
if( i<pRow->nNonCherrypick ){
pRow->nNonCherrypick--;
}else{
pRow->nCherrypick--;
}
i--;
}
}
}
}
/* Put the deepest (earliest) merge parent first in the list.
** An off-screen merge parent is considered deepest.
*/
for(pRow=p->pFirst; pRow; pRow=pRow->pNext ){
if( pRow->nParent<=1 ) continue;
for(i=1; i<pRow->nParent; i++){
GraphRow *pParent = hashFind(p, pRow->aParent[i]);
if( pParent ) pParent->nMergeChild++;
}
if( pRow->nCherrypick>1 ){
int iBest = -1;
int iDeepest = -1;
for(i=pRow->nNonCherrypick; i<pRow->nParent; i++){
GraphRow *pParent = hashFind(p, pRow->aParent[i]);
if( pParent==0 ){
iBest = i;
break;
}
if( pParent->idx>iDeepest ){
iDeepest = pParent->idx;
iBest = i;
}
}
i = pRow->nNonCherrypick;
if( iBest>i ){
int x = pRow->aParent[i];
pRow->aParent[i] = pRow->aParent[iBest];
pRow->aParent[iBest] = x;
}
}
if( pRow->nNonCherrypick>2 ){
int iBest = -1;
int iDeepest = -1;
for(i=1; i<pRow->nNonCherrypick; i++){
GraphRow *pParent = hashFind(p, pRow->aParent[i]);
if( pParent==0 ){
iBest = i;
break;
}
if( pParent->idx>iDeepest ){
iDeepest = pParent->idx;
iBest = i;
}
}
if( iBest>1 ){
int x = pRow->aParent[1];
pRow->aParent[1] = pRow->aParent[iBest];
pRow->aParent[iBest] = x;
}
}
}
/* If the primary parent is in a different branch, but there are
** other parents in the same branch, reorder the parents to make
** the parent from the same branch the primary parent.
*/
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
if( pRow->isDup ) continue;
|
| ︙ | ︙ | |||
532 533 534 535 536 537 538 |
}else if( pRow->idxTop < pParent->idxTop ){
pParent->pChild = pRow;
pParent->idxTop = pRow->idxTop;
}
}
if( tmFlags & TIMELINE_FILLGAPS ){
| > | > | 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 |
}else if( pRow->idxTop < pParent->idxTop ){
pParent->pChild = pRow;
pParent->idxTop = pRow->idxTop;
}
}
if( tmFlags & TIMELINE_FILLGAPS ){
/* If a node has no pChild in the graph
** and there is a node higher up in the graph
** that is in the same branch and has no in-graph parent, then
** make the lower node a step-child of the upper node. This will
** be represented on the graph by a thick dotted line without an arrowhead.
*/
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
if( pRow->pChild ) continue;
if( pRow->isLeaf ) continue;
for(pLoop=pRow->pPrev; pLoop; pLoop=pLoop->pPrev){
if( pLoop->nParent>0
&& pLoop->zBranch==pRow->zBranch
&& hashFind(p,pLoop->aParent[0])==0
){
pRow->pChild = pLoop;
pRow->isStepParent = 1;
|
| ︙ | ︙ | |||
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 |
}
}
/*
** Insert merge rails and merge arrows
*/
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
for(i=1; i<pRow->nParent; i++){
int parentRid = pRow->aParent[i];
pDesc = hashFind(p, parentRid);
if( pDesc==0 ){
/* Merge from a node that is off-screen */
int iMrail = -1;
for(j=0; j<GR_MAX_RAIL; j++){
if( mergeRiserFrom[j]==parentRid ){
iMrail = j;
break;
}
}
if( iMrail==-1 ){
iMrail = findFreeRail(p, pRow->idx, p->pLast->idx, 0);
if( p->mxRail>=GR_MAX_RAIL ) return;
mergeRiserFrom[iMrail] = parentRid;
}
mask = BIT(iMrail);
if( i>=pRow->nNonCherrypick ){
pRow->mergeIn[iMrail] = 2;
pRow->cherrypickDown |= mask;
}else{
pRow->mergeIn[iMrail] = 1;
pRow->mergeDown |= mask;
}
for(pLoop=pRow->pNext; pLoop; pLoop=pLoop->pNext){
pLoop->railInUse |= mask;
}
}else{
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | > > > > > > > > | 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 |
}
}
/*
** Insert merge rails and merge arrows
*/
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
int iReuseIdx = -1;
int iReuseRail = -1;
int isCherrypick = 0;
for(i=1; i<pRow->nParent; i++){
int parentRid = pRow->aParent[i];
if( i==pRow->nNonCherrypick ){
/* Because full merges are laid out before cherrypicks,
** it is ok to use a full-merge raise for a cherrypick.
** See the graph on check-in 8ac66ef33b464d28 for example
** iReuseIdx = -1;
** iReuseRail = -1; */
isCherrypick = 1;
}
pDesc = hashFind(p, parentRid);
if( pDesc==0 ){
/* Merge from a node that is off-screen */
if( iReuseIdx>=p->nRow+1 ){
continue; /* Suppress multiple off-screen merges */
}
int iMrail = -1;
for(j=0; j<GR_MAX_RAIL; j++){
if( mergeRiserFrom[j]==parentRid ){
iMrail = j;
break;
}
}
if( iMrail==-1 ){
iMrail = findFreeRail(p, pRow->idx, p->pLast->idx, 0);
if( p->mxRail>=GR_MAX_RAIL ) return;
mergeRiserFrom[iMrail] = parentRid;
}
iReuseIdx = p->nRow+1;
iReuseRail = iMrail;
mask = BIT(iMrail);
if( i>=pRow->nNonCherrypick ){
pRow->mergeIn[iMrail] = 2;
pRow->cherrypickDown |= mask;
}else{
pRow->mergeIn[iMrail] = 1;
pRow->mergeDown |= mask;
}
for(pLoop=pRow->pNext; pLoop; pLoop=pLoop->pNext){
pLoop->railInUse |= mask;
}
}else{
/* The merge parent node does exist on this graph */
if( iReuseIdx>pDesc->idx
&& pDesc->nMergeChild==1
){
/* Reuse an existing merge riser */
pDesc->mergeOut = iReuseRail;
if( isCherrypick ){
pDesc->cherrypickUpto = pDesc->idx;
}else{
pDesc->hasNormalOutMerge = 1;
pDesc->mergeUpto = pDesc->idx;
}
}else{
/* Create a new merge for an on-screen node */
createMergeRiser(p, pDesc, pRow, isCherrypick);
if( p->mxRail>=GR_MAX_RAIL ) return;
if( iReuseIdx<0
&& pDesc->nMergeChild==1
&& (pDesc->iRail!=pDesc->mergeOut || pDesc->isLeaf)
){
iReuseIdx = pDesc->idx;
iReuseRail = pDesc->mergeOut;
}
}
}
}
}
/*
** Insert merge rails from primaries to duplicates.
*/
|
| ︙ | ︙ |
| ︙ | ︙ | |||
429 430 431 432 433 434 435 436 |
if( p.u==0 && p.mo==p.r ){
mergeLines[p.mo] = u.r<p.r ? -mergeOffset-mLine.w : mergeOffset;
}else{
mergeLines[p.mo] = -mLine.w/2;
}
x1 += mergeLines[p.mo]
var y0 = p.y+2;
if( p.mu==p.id ){
| > > > > > | > > > > > > > > > | | 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 |
if( p.u==0 && p.mo==p.r ){
mergeLines[p.mo] = u.r<p.r ? -mergeOffset-mLine.w : mergeOffset;
}else{
mergeLines[p.mo] = -mLine.w/2;
}
x1 += mergeLines[p.mo]
var y0 = p.y+2;
var isCP = p.hasOwnProperty('cu');
if( p.mu==p.id ){
/* Special case: The merge riser already exists. Only draw the
/* horizontal line or arrow going from the node out to the riser. */
var dx = x1<x0 ? mArrow.w : -mArrow.w;
if( isCP ){
drawCherrypickLine(x0,y0,x1+dx,null);
cls = "arrow cherrypick " + (x1<x0 ? "l" : "r");
}else{
drawMergeLine(x0,y0,x1+dx,null);
cls = "arrow merge " + (x1<x0 ? "l" : "r");
}
if( !isCP || p.mu==p.cu ){
dx = x1<x0 ? mLine.w : -(mArrow.w + mLine.w/2);
drawBox(cls,null,x1+dx,y0+(mLine.w-mArrow.h)/2);
}
y1 = y0;
}else{
drawMergeLine(x0,y0,x1+(x0<x1 ? mLine.w : 0),null);
drawMergeLine(x1,y0+mLine.w,null,y1);
}
if( isCP && p.cu!=p.id ){
var u2 = tx.rowinfo[p.cu-tx.iTopRow];
var y2 = miLineY(u2);
drawCherrypickLine(x1,y1,null,y2);
}
}else if( mergeOffset ){
mergeLines[p.mo] = u.r<p.r ? -mergeOffset-mLine.w : mergeOffset;
x1 += mergeLines[p.mo];
|
| ︙ | ︙ |
| ︙ | ︙ | |||
482 483 484 485 486 487 488 | ** Options: ** ** --compress Use ZLIB compression on the payload ** --mimetype TYPE Mimetype of the payload ** --out FILE Store the reply in FILE ** -v Verbose output */ | | > | 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 |
** Options:
**
** --compress Use ZLIB compression on the payload
** --mimetype TYPE Mimetype of the payload
** --out FILE Store the reply in FILE
** -v Verbose output
*/
void test_httpmsg_command(void){
const char *zMimetype;
const char *zInFile;
const char *zOutFile;
Blob in, out;
unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS;
zMimetype = find_option("mimetype",0,1);
zOutFile = find_option("out","o",1);
if( find_option("verbose","v",0)!=0 ) mHttpFlags |= HTTP_VERBOSE;
if( find_option("compress",0,0)!=0 ) mHttpFlags &= ~HTTP_NOCOMPRESS;
db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_ANY_SCHEMA, 0);
if( g.argc!=3 && g.argc!=4 ){
usage("URL ?PAYLOAD?");
}
zInFile = g.argc==4 ? g.argv[3] : 0;
url_parse(g.argv[2], 0);
if( g.url.protocol[0]!='h' ){
fossil_fatal("the %s command supports only http: and https:", g.argv[1]);
|
| ︙ | ︙ |
| ︙ | ︙ | |||
22 23 24 25 26 27 28 29 30 31 32 33 34 35 | ** of the server is held in global variables that are set by url_parse(). ** ** SSL support is abstracted out into this module because Fossil can ** be compiled without SSL support (which requires OpenSSL library) */ #include "config.h" #ifdef FOSSIL_ENABLE_SSL #include <openssl/bio.h> #include <openssl/ssl.h> #include <openssl/err.h> | > > < | > > > > | 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 |
** of the server is held in global variables that are set by url_parse().
**
** SSL support is abstracted out into this module because Fossil can
** be compiled without SSL support (which requires OpenSSL library)
*/
#include "config.h"
#include "http_ssl.h"
#ifdef FOSSIL_ENABLE_SSL
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <assert.h>
#include <sys/types.h>
/*
** There can only be a single OpenSSL IO connection open at a time.
** State information about that IO is stored in the following
** local variables:
*/
static int sslIsInit = 0; /* True after global initialization */
static BIO *iBio = 0; /* OpenSSL I/O abstraction */
static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */
static SSL_CTX *sslCtx; /* SSL context */
static SSL *ssl;
static struct { /* Accept this SSL cert for this session only */
char *zHost; /* Subject or host name */
char *zHash; /* SHA2-256 hash of the cert */
} sException;
static int sslNoCertVerify = 0; /* Do not verify SSL certs */
/*
** Clear the SSL error message
*/
static void ssl_clear_errmsg(void){
free(sslErrMsg);
sslErrMsg = 0;
|
| ︙ | ︙ | |||
182 183 184 185 186 187 188 |
int rc, httpVerMin;
char *bbuf;
Blob snd, reply;
int done=0,end=0;
blob_zero(&snd);
blob_appendf(&snd, "CONNECT %s:%d HTTP/1.1\r\n", pUrlData->hostname,
pUrlData->proxyOrigPort);
| | > | 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
int rc, httpVerMin;
char *bbuf;
Blob snd, reply;
int done=0,end=0;
blob_zero(&snd);
blob_appendf(&snd, "CONNECT %s:%d HTTP/1.1\r\n", pUrlData->hostname,
pUrlData->proxyOrigPort);
blob_appendf(&snd, "Host: %s:%d\r\n",
pUrlData->hostname, pUrlData->proxyOrigPort);
if( pUrlData->proxyAuth ){
blob_appendf(&snd, "Proxy-Authorization: %s\r\n", pUrlData->proxyAuth);
}
blob_append(&snd, "Proxy-Connection: keep-alive\r\n", -1);
blob_appendf(&snd, "User-Agent: %s\r\n", get_user_agent());
blob_append(&snd, "\r\n", 2);
BIO_write(bio, blob_buffer(&snd), blob_size(&snd));
|
| ︙ | ︙ | |||
220 221 222 223 224 225 226 227 228 229 230 231 |
end++;
}
}while(!done);
sscanf(bbuf, "HTTP/1.%d %d", &httpVerMin, &rc);
blob_reset(&reply);
return rc;
}
/*
** Open an SSL connection. The identify of the server is determined
** as follows:
**
| > > > > > > > > > > > > | < < < < < < < < < < < < < < | > | 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 |
end++;
}
}while(!done);
sscanf(bbuf, "HTTP/1.%d %d", &httpVerMin, &rc);
blob_reset(&reply);
return rc;
}
/*
** Invoke this routine to disable SSL cert verification. After
** this call is made, any SSL cert that the server provides will
** be accepted. Communication will still be encrypted, but the
** client has no way of knowing whether it is talking to the
** real server or a man-in-the-middle imposter.
*/
void ssl_disable_cert_verification(void){
sslNoCertVerify = 1;
}
/*
** Open an SSL connection. The identify of the server is determined
** as follows:
**
** pUrlData->name Name of the server. Ex: www.fossil-scm.org
** g.url.name Name of the proxy server, if proxying.
** pUrlData->port TCP/IP port to use. Ex: 80
**
** Return the number of errors.
*/
int ssl_open(UrlData *pUrlData){
X509 *cert;
ssl_global_init();
if( pUrlData->useProxy ){
int rc;
char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port);
BIO *sBio = BIO_new_connect(connStr);
free(connStr);
if( BIO_do_connect(sBio)<=0 ){
ssl_set_errmsg("SSL: cannot connect to proxy %s:%d (%s)",
pUrlData->name, pUrlData->port,
ERR_reason_error_string(ERR_get_error()));
ssl_close();
return 1;
}
rc = establish_proxy_tunnel(pUrlData, sBio);
if( rc<200||rc>299 ){
ssl_set_errmsg("SSL: proxy connect failed with HTTP status code %d", rc);
return 1;
|
| ︙ | ︙ | |||
280 281 282 283 284 285 286 |
ssl_set_errmsg("SSL: cannot open SSL (%s)",
ERR_reason_error_string(ERR_get_error()));
return 1;
}
BIO_get_ssl(iBio, &ssl);
#if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT)
| | > > | > | 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 |
ssl_set_errmsg("SSL: cannot open SSL (%s)",
ERR_reason_error_string(ERR_get_error()));
return 1;
}
BIO_get_ssl(iBio, &ssl);
#if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT)
if( !SSL_set_tlsext_host_name(ssl,
(pUrlData->useProxy?pUrlData->hostname:pUrlData->name))
){
fossil_warning("WARNING: failed to set server name indication (SNI), "
"continuing without it.\n");
}
#endif
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
if( !pUrlData->useProxy ){
char *connStr = mprintf("%s:%d", pUrlData->name, pUrlData->port);
BIO_set_conn_hostname(iBio, connStr);
free(connStr);
if( BIO_do_connect(iBio)<=0 ){
ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)",
pUrlData->name, pUrlData->port,
ERR_reason_error_string(ERR_get_error()));
ssl_close();
return 1;
}
}
if( BIO_do_handshake(iBio)<=0 ) {
ssl_set_errmsg("Error establishing SSL connection %s:%d (%s)",
|
| ︙ | ︙ | |||
317 318 319 320 321 322 323 |
if ( cert==NULL ){
ssl_set_errmsg("No SSL certificate was presented by the peer");
ssl_close();
return 1;
}
| | > < | > | > | < < | | > > | > > | | > > < < | | | > > > > > > > > > | | < < < < < < < < | < | | | | | | | | | | | | | | < | < | | < > < > | > | > | | > > | < | > > > > | | > > > > | < | > > > | < > > | > | | | < > | < > | | < < | < < < | < < | < < < < | < | < < < < < < | 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 |
if ( cert==NULL ){
ssl_set_errmsg("No SSL certificate was presented by the peer");
ssl_close();
return 1;
}
if( !sslNoCertVerify && SSL_get_verify_result(ssl)!=X509_V_OK ){
int x, desclen;
char *desc, *prompt;
Blob ans;
char cReply;
BIO *mem;
unsigned char md[EVP_MAX_MD_SIZE];
char zHash[EVP_MAX_MD_SIZE*2+1];
unsigned int mdLength = (int)sizeof(md);
memset(md, 0, sizeof(md));
zHash[0] = 0;
/* MMNNFFPPS */
#if OPENSSL_VERSION_NUMBER >= 0x010000000
x = X509_digest(cert, EVP_sha256(), md, &mdLength);
#else
x = X509_digest(cert, EVP_sha1(), md, &mdLength);
#endif
if( x ){
int j;
for(j=0; j<mdLength && j*2+1<sizeof(zHash); ++j){
zHash[j*2] = "0123456789abcdef"[md[j]>>4];
zHash[j*2+1] = "0123456789abcdef"[md[j]&0xf];
}
zHash[j*2] = 0;
}
if( ssl_certificate_exception_exists(pUrlData, zHash) ){
/* Ignore the failure because an exception exists */
ssl_one_time_exception(pUrlData, zHash);
}else{
/* Tell the user about the failure and ask what to do */
mem = BIO_new(BIO_s_mem());
BIO_puts(mem, " subject: ");
X509_NAME_print_ex(mem, X509_get_subject_name(cert), 0, XN_FLAG_ONELINE);
BIO_puts(mem, "\n issuer: ");
X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 0, XN_FLAG_ONELINE);
BIO_printf(mem, "\n sha256: %s", zHash);
desclen = BIO_get_mem_data(mem, &desc);
prompt = mprintf("Unable to verify SSL cert from %s\n%.*s\n"
"accept this cert and continue (y/N)? ",
pUrlData->name, desclen, desc);
BIO_free(mem);
prompt_user(prompt, &ans);
free(prompt);
cReply = blob_str(&ans)[0];
blob_reset(&ans);
if( cReply!='y' && cReply!='Y' ){
X509_free(cert);
ssl_set_errmsg("SSL cert declined");
ssl_close();
return 1;
}
ssl_one_time_exception(pUrlData, zHash);
prompt_user("remember this exception (y/N)? ", &ans);
cReply = blob_str(&ans)[0];
if( cReply=='y' || cReply=='Y') {
ssl_remember_certificate_exception(pUrlData, zHash);
}
blob_reset(&ans);
}
}
/* 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 server.
*/
{
/* As soon as libressl implements
** BIO_ADDR_hostname_string/BIO_get_conn_address.
** check here for the correct LIBRESSL_VERSION_NUMBER too. For now: disable
*/
#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000L \
&& !defined(LIBRESSL_VERSION_NUMBER)
char *ip = BIO_ADDR_hostname_string(BIO_get_conn_address(iBio),1);
g.zIpAddr = mprintf("%s", ip);
OPENSSL_free(ip);
#else
/* IPv4 only code */
const unsigned char *ip;
ip = (const unsigned char*)BIO_ptr_ctrl(iBio,BIO_C_GET_CONNECT,2);
g.zIpAddr = mprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
#endif
}
X509_free(cert);
return 0;
}
/*
** Remember that the cert with the given hash is a acceptable for
** use with pUrlData->name.
*/
LOCAL void ssl_remember_certificate_exception(
UrlData *pUrlData,
const char *zHash
){
char *zName = mprintf("cert:%s", pUrlData->name);
db_set(zName, zHash, 1);
fossil_free(zName);
}
/*
** Return true if the there exists a certificate exception for
** pUrlData->name that matches the hash.
*/
LOCAL int ssl_certificate_exception_exists(
UrlData *pUrlData,
const char *zHash
){
char *zName, *zValue;
if( fossil_strcmp(sException.zHost,pUrlData->name)==0
&& fossil_strcmp(sException.zHash,zHash)==0
){
return 1;
}
zName = mprintf("cert:%s", pUrlData->name);
zValue = db_get(zName,0);
fossil_free(zName);
return zValue!=0 && strcmp(zHash,zValue)==0;
}
/*
** Remember zHash as an acceptable certificate for this session only.
*/
LOCAL void ssl_one_time_exception(
UrlData *pUrlData,
const char *zHash
){
fossil_free(sException.zHost);
sException.zHost = fossil_strdup(pUrlData->name);
fossil_free(sException.zHash);
sException.zHash = fossil_strdup(zHash);
}
/*
** Send content out over the SSL connection.
*/
size_t ssl_send(void *NotUsed, void *pContent, size_t N){
size_t total = 0;
|
| ︙ | ︙ | |||
496 497 498 499 500 501 502 |
N -= got;
pContent = (void*)&((char*)pContent)[got];
}
return total;
}
#endif /* FOSSIL_ENABLE_SSL */
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
N -= got;
pContent = (void*)&((char*)pContent)[got];
}
return total;
}
#endif /* FOSSIL_ENABLE_SSL */
/*
** COMMAND: tls-config*
**
** Usage: %fossil tls-config [SUBCOMMAND] [OPTIONS...] [ARGS...]
**
** This command is used to view or modify the TLS (Transport Layer
** Security) configuration for Fossil. TLS (formerly SSL) is the
** encryption technology used for secure HTTPS transport.
**
** Sub-commands:
**
** show Show the TLS configuration
**
** remove-exception DOMAIN... Remove TLS cert exceptions
** for the domains listed. Or if
** the --all option is specified,
** remove all TLS cert exceptions.
*/
void test_tlsconfig_info(void){
#if !defined(FOSSIL_ENABLE_SSL)
fossil_print("TLS disabled in this build\n");
#else
const char *zCmd;
size_t nCmd;
int nHit = 0;
db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
db_open_config(1,0);
zCmd = g.argc>=3 ? g.argv[2] : "show";
nCmd = strlen(zCmd);
if( strncmp("show",zCmd,nCmd)==0 ){
const char *zName, *zValue;
size_t nName;
Stmt q;
fossil_print("OpenSSL-version: %s (0x%09x)\n",
SSLeay_version(SSLEAY_VERSION), OPENSSL_VERSION_NUMBER);
fossil_print("OpenSSL-cert-file: %s\n", X509_get_default_cert_file());
fossil_print("OpenSSL-cert-dir: %s\n", X509_get_default_cert_dir());
zName = X509_get_default_cert_file_env();
zValue = fossil_getenv(zName);
if( zValue==0 ) zValue = "";
nName = strlen(zName);
fossil_print("%s:%.*s%s\n", zName, 19-nName, "", zValue);
zName = X509_get_default_cert_dir_env();
zValue = fossil_getenv(zName);
if( zValue==0 ) zValue = "";
nName = strlen(zName);
fossil_print("%s:%.*s%s\n", zName, 19-nName, "", zValue);
nHit++;
fossil_print("ssl-ca-location: %s\n", db_get("ssl-ca-location",""));
fossil_print("ssl-identity: %s\n", db_get("ssl-identity",""));
db_prepare(&q,
"SELECT name FROM global_config"
" WHERE name GLOB 'cert:*'"
"UNION ALL "
"SELECT name FROM config"
" WHERE name GLOB 'cert:*'"
" ORDER BY name"
);
while( db_step(&q)==SQLITE_ROW ){
fossil_print("exception: %s\n", db_column_text(&q,0)+5);
}
db_finalize(&q);
}else
if( strncmp("remove-exception",zCmd,nCmd)==0 ){
int i;
Blob sql;
char *zSep = "(";
db_begin_transaction();
blob_init(&sql, 0, 0);
if( g.argc==4 && find_option("all",0,0)!=0 ){
blob_append_sql(&sql,
"DELETE FROM global_config WHERE name GLOB 'cert:*';\n"
"DELETE FROM global_config WHERE name GLOB 'trusted:*';\n"
"DELETE FROM config WHERE name GLOB 'cert:*';\n"
"DELETE FROM config WHERE name GLOB 'trusted:*';\n"
);
}else{
if( g.argc<4 ){
usage("remove-exception DOMAIN-NAME ...");
}
blob_append_sql(&sql,"DELETE FROM global_config WHERE name IN ");
for(i=3; i<g.argc; i++){
blob_append_sql(&sql,"%s'cert:%q','trust:%q'",
zSep/*safe-for-%s*/, g.argv[i], g.argv[i]);
zSep = ",";
}
blob_append_sql(&sql,");\n");
zSep = "(";
blob_append_sql(&sql,"DELETE FROM config WHERE name IN ");
for(i=3; i<g.argc; i++){
blob_append_sql(&sql,"%s'cert:%q','trusted:%q'",
zSep/*safe-for-%s*/, g.argv[i], g.argv[i]);
zSep = ",";
}
blob_append_sql(&sql,");");
}
db_exec_sql(blob_str(&sql));
db_commit_transaction();
blob_reset(&sql);
}else
/*default*/{
fossil_fatal("unknown sub-command \"%s\".\nshould be one of:"
" remove-exception show",
zCmd);
}
#endif
}
|
| ︙ | ︙ | |||
78 79 80 81 82 83 84 |
/*
** Check zFossil to see if it is a reasonable "fossil" command to
** run on the server. Do not allow an attacker to substitute something
** like "/bin/rm".
*/
static int is_safe_fossil_command(const char *zFossil){
| | | 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
/*
** Check zFossil to see if it is a reasonable "fossil" command to
** run on the server. Do not allow an attacker to substitute something
** like "/bin/rm".
*/
static int is_safe_fossil_command(const char *zFossil){
static const char *const azSafe[] = { "*/fossil", "*/fossil.exe", "*/echo" };
int i;
for(i=0; i<sizeof(azSafe)/sizeof(azSafe[0]); i++){
if( sqlite3_strglob(azSafe[i], zFossil)==0 ) return 1;
if( strcmp(azSafe[i]+2, zFossil)==0 ) return 1;
}
return 0;
}
|
| ︙ | ︙ | |||
261 262 263 264 265 266 267 |
n -= sent;
}
}
}
/*
** This routine is called when the outbound message is complete and
| | | | | 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
n -= sent;
}
}
}
/*
** This routine is called when the outbound message is complete and
** it is time to begin receiving a reply.
*/
void transport_flip(UrlData *pUrlData){
if( pUrlData->isFile ){
char *zCmd;
fclose(transport.pFile);
zCmd = mprintf("%$ http --in %$ --out %$ --ipaddr 127.0.0.1"
" %$ --localauth",
g.nameOfExe, transport.zOutFile, transport.zInFile, pUrlData->name
);
fossil_system(zCmd);
free(zCmd);
transport.pFile = fossil_fopen(transport.zInFile, "rb");
}
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
24 25 26 27 28 29 30 |
#if INTERFACE
/*
** A single file change record.
*/
struct ImportFile {
char *zName; /* Name of a file */
| | | 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#if INTERFACE
/*
** A single file change record.
*/
struct ImportFile {
char *zName; /* Name of a file */
char *zUuid; /* Hash of the file */
char *zPrior; /* Prior name if the name was changed */
char isFrom; /* True if obtained from the parent */
char isExe; /* True if executable */
char isLink; /* True if symlink */
};
#endif
|
| ︙ | ︙ | |||
57 58 59 60 61 62 63 | char *zBranch; /* Name of a branch for a commit */ char *zPrevBranch; /* The branch of the previous check-in */ char *aData; /* Data content */ char *zMark; /* The current mark */ char *zDate; /* Date/time stamp */ char *zUser; /* User name */ char *zComment; /* Comment of a commit */ | | | 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | char *zBranch; /* Name of a branch for a commit */ char *zPrevBranch; /* The branch of the previous check-in */ char *aData; /* Data content */ char *zMark; /* The current mark */ char *zDate; /* Date/time stamp */ char *zUser; /* User name */ char *zComment; /* Comment of a commit */ char *zFrom; /* from value as a hash */ char *zPrevCheckin; /* Name of the previous check-in */ char *zFromMark; /* The mark of the "from" field */ int nMerge; /* Number of merge values */ int nMergeAlloc; /* Number of slots in azMerge[] */ char **azMerge; /* Merge values */ int nFile; /* Number of aFile values */ int nFileAlloc; /* Number of slots in aFile[] */ |
| ︙ | ︙ | |||
141 142 143 144 145 146 147 | } /* ** Insert an artifact into the BLOB table if it isn't there already. ** If zMark is not zero, create a cross-reference from that mark back ** to the newly inserted artifact. ** | | | | | 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
}
/*
** Insert an artifact into the BLOB table if it isn't there already.
** If zMark is not zero, create a cross-reference from that mark back
** to the newly inserted artifact.
**
** If saveHash is true, then pContent is a commit record. Record its
** artifact hash in gg.zPrevCheckin.
*/
static int fast_insert_content(
Blob *pContent, /* Content to insert */
const char *zMark, /* Label using this mark, if not NULL */
int saveHash, /* Save artifact hash in gg.zPrevCheckin */
int doParse /* Invoke manifest_crosslink() */
){
Blob hash;
Blob cmpr;
int rid;
hname_hash(pContent, 0, &hash);
|
| ︙ | ︙ | |||
185 186 187 188 189 190 191 |
);
db_multi_exec(
"INSERT OR IGNORE INTO xmark(tname, trid, tuuid)"
"VALUES(%B,%d,%B)",
&hash, rid, &hash
);
}
| | | 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
);
db_multi_exec(
"INSERT OR IGNORE INTO xmark(tname, trid, tuuid)"
"VALUES(%B,%d,%B)",
&hash, rid, &hash
);
}
if( saveHash ){
fossil_free(gg.zPrevCheckin);
gg.zPrevCheckin = fossil_strdup(blob_str(&hash));
}
blob_reset(&hash);
return rid;
}
|
| ︙ | ︙ | |||
210 211 212 213 214 215 216 |
}
/*
** Use data accumulated in gg from a "tag" record to add a new
** control artifact to the BLOB table.
*/
static void finish_tag(void){
| < > > | 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 |
}
/*
** Use data accumulated in gg from a "tag" record to add a new
** control artifact to the BLOB table.
*/
static void finish_tag(void){
if( gg.zDate && gg.zTag && gg.zFrom && gg.zUser ){
Blob record, cksum;
blob_zero(&record);
blob_appendf(&record, "D %s\n", gg.zDate);
blob_appendf(&record, "T +sym-%F%F%F %s", gimport.zTagPre, gg.zTag,
gimport.zTagSuf, gg.zFrom);
if( gg.zComment ){
blob_appendf(&record, " %F", gg.zComment);
}
blob_appendf(&record, "\nU %F\n", gg.zUser);
md5sum_blob(&record, &cksum);
blob_appendf(&record, "Z %b\n", &cksum);
fast_insert_content(&record, 0, 0, 1);
blob_reset(&cksum);
blob_reset(&record);
}
import_reset(0);
}
/*
** Compare two ImportFile objects for sorting
*/
|
| ︙ | ︙ | |||
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 |
** tag or not. So make an entry in the XTAG table to record this tag
** but overwrite that entry if a later instance of the same tag appears.
**
** This behavior seems like a bug in git-fast-export, but it is easier
** to work around the problem than to fix git-fast-export.
*/
if( gg.tagCommit && gg.zDate && gg.zUser && gg.zFrom ){
blob_appendf(&record, "D %s\n", gg.zDate);
blob_appendf(&record, "T +sym-%F%F%F %s\n", gimport.zBranchPre, gg.zBranch,
gimport.zBranchSuf, gg.zPrevCheckin);
blob_appendf(&record, "U %F\n", gg.zUser);
md5sum_blob(&record, &cksum);
blob_appendf(&record, "Z %b\n", &cksum);
db_multi_exec(
"INSERT OR REPLACE INTO xtag(tname, tcontent)"
" VALUES(%Q,%Q)", gg.zBranch, blob_str(&record)
);
| > > < > | 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 |
** tag or not. So make an entry in the XTAG table to record this tag
** but overwrite that entry if a later instance of the same tag appears.
**
** This behavior seems like a bug in git-fast-export, but it is easier
** to work around the problem than to fix git-fast-export.
*/
if( gg.tagCommit && gg.zDate && gg.zUser && gg.zFrom ){
record.nUsed = 0
/*in case fast_insert_comment() did not indirectly blob_reset() it */;
blob_appendf(&record, "D %s\n", gg.zDate);
blob_appendf(&record, "T +sym-%F%F%F %s\n", gimport.zBranchPre, gg.zBranch,
gimport.zBranchSuf, gg.zPrevCheckin);
blob_appendf(&record, "U %F\n", gg.zUser);
md5sum_blob(&record, &cksum);
blob_appendf(&record, "Z %b\n", &cksum);
db_multi_exec(
"INSERT OR REPLACE INTO xtag(tname, tcontent)"
" VALUES(%Q,%Q)", gg.zBranch, blob_str(&record)
);
blob_reset(&cksum);
}
blob_reset(&record);
fossil_free(gg.zPrevBranch);
gg.zPrevBranch = gg.zBranch;
gg.zBranch = 0;
import_reset(0);
}
/*
|
| ︙ | ︙ | |||
406 407 408 409 410 411 412 |
}else{
*pzIn = &z[i];
}
return z;
}
/*
| | | 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
}else{
*pzIn = &z[i];
}
return z;
}
/*
** Convert a "mark" or "committish" into the artifact hash.
*/
static char *resolve_committish(const char *zCommittish){
char *zRes;
zRes = db_text(0, "SELECT tuuid FROM xmark WHERE tname=%Q", zCommittish);
return zRes;
}
|
| ︙ | ︙ | |||
609 610 611 612 613 614 615 |
got = fread(gg.aData, 1, gg.nData, pIn);
if( got!=gg.nData ){
fossil_panic("short read: got %d of %d bytes", got, gg.nData);
}
gg.aData[got] = '\0';
if( gg.zComment==0 &&
(gg.xFinish==finish_commit || gg.xFinish==finish_tag) ){
| | | | | 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 |
got = fread(gg.aData, 1, gg.nData, pIn);
if( got!=gg.nData ){
fossil_panic("short read: got %d of %d bytes", got, gg.nData);
}
gg.aData[got] = '\0';
if( gg.zComment==0 &&
(gg.xFinish==finish_commit || gg.xFinish==finish_tag) ){
/* Strip trailing newline, it's appended to the comment. */
if( gg.aData[got-1] == '\n' )
gg.aData[got-1] = '\0';
gg.zComment = gg.aData;
gg.aData = 0;
gg.nData = 0;
}
}
}else
if( (!ggit.authorFlag && strncmp(zLine, "author ", 7)==0)
|
| ︙ | ︙ | |||
641 642 643 644 645 646 647 |
if( (zTo=strchr(z, '>'))==NULL ) goto malformed_line;
*(++zTo) = '\0';
/* Lookup user by contact info. */
fossil_free(gg.zUser);
gg.zUser = db_text(0, "SELECT login FROM user WHERE info=%Q", z);
if( gg.zUser==NULL ){
/* If there is no user with this contact info,
| | | 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 |
if( (zTo=strchr(z, '>'))==NULL ) goto malformed_line;
*(++zTo) = '\0';
/* Lookup user by contact info. */
fossil_free(gg.zUser);
gg.zUser = db_text(0, "SELECT login FROM user WHERE info=%Q", z);
if( gg.zUser==NULL ){
/* If there is no user with this contact info,
* then use the email address as the username. */
if ( (z=strchr(z, '<'))==NULL ) goto malformed_line;
z++;
*(zTo-1) = '\0';
gg.zUser = fossil_strdup(z);
}
secSince1970 = 0;
for(zTo++; fossil_isdigit(*zTo); zTo++){
|
| ︙ | ︙ | |||
1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 | ** -f|--force overwrite repository if already exists ** -q|--quiet omit progress output ** --no-rebuild skip the "rebuilding metadata" step ** --no-vacuum skip the final VACUUM of the database file ** --rename-trunk NAME use NAME as name of imported trunk branch ** --rename-branch PAT rename all branch names using PAT pattern ** --rename-tag PAT rename all tag names using PAT pattern ** ** The --incremental option allows an existing repository to be extended ** with new content. The --rename-* options may be useful to avoid name | > | > > | 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 |
** -f|--force overwrite repository if already exists
** -q|--quiet omit progress output
** --no-rebuild skip the "rebuilding metadata" step
** --no-vacuum skip the final VACUUM of the database file
** --rename-trunk NAME use NAME as name of imported trunk branch
** --rename-branch PAT rename all branch names using PAT pattern
** --rename-tag PAT rename all tag names using PAT pattern
** --admin-user|-A NAME use NAME for the admin user
**
** The --incremental option allows an existing repository to be extended
** with new content. The --rename-* options may be useful to avoid name
** conflicts when using the --incremental option. The --admin-user
** option is ignored if --incremental is specified.
**
** The argument to --rename-* contains one "%" character to be replaced
** with the original name. For example, "--rename-tag svn-%-tag" renames
** the tag called "release" to "svn-release-tag".
**
** --ignore-tree is useful for importing Subversion repositories which
** move branches to subdirectories of "branches/deleted" instead of
** deleting them. It can be supplied multiple times if necessary.
**
** See also: export
*/
void import_cmd(void){
char *zPassword;
FILE *pIn;
Stmt q;
int forceFlag = find_option("force", "f", 0)!=0;
int svnFlag = find_option("svn", 0, 0)!=0;
int gitFlag = find_option("git", 0, 0)!=0;
int omitRebuild = find_option("no-rebuild",0,0)!=0;
int omitVacuum = find_option("no-vacuum",0,0)!=0;
const char *zDefaultUser = find_option("admin-user","A",1);
/* Options common to all input formats */
int incrFlag = find_option("incremental", "i", 0)!=0;
/* Options for --svn only */
const char *zBase = "";
int flatFlag = 0;
|
| ︙ | ︙ | |||
1754 1755 1756 1757 1758 1759 1760 |
db_create_repository(g.argv[2]);
}
db_open_repository(g.argv[2]);
db_open_config(0, 0);
db_begin_transaction();
if( !incrFlag ){
| | | 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 |
db_create_repository(g.argv[2]);
}
db_open_repository(g.argv[2]);
db_open_config(0, 0);
db_begin_transaction();
if( !incrFlag ){
db_initial_setup(0, 0, zDefaultUser);
db_set("main-branch", gimport.zTrunkName, 0);
}
if( svnFlag ){
db_multi_exec(
"CREATE TEMP TABLE xrevisions("
" trev INTEGER, tbranch INT, trid INT, tparent INT DEFAULT 0,"
|
| ︙ | ︙ | |||
1820 1821 1822 1823 1824 1825 1826 |
Bag blobs, vers;
bag_init(&blobs);
bag_init(&vers);
/* The following temp-tables are used to hold information needed for
** the import.
**
** The XMARK table provides a mapping from fast-import "marks" and symbols
| | > | | 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 |
Bag blobs, vers;
bag_init(&blobs);
bag_init(&vers);
/* The following temp-tables are used to hold information needed for
** the import.
**
** The XMARK table provides a mapping from fast-import "marks" and symbols
** into artifact hashes.
**
** Given any valid fast-import symbol, the corresponding fossil rid and
** hash can found by searching against the xmark.tname field.
**
** The XBRANCH table maps commit marks and symbols into the branch those
** commits belong to. If xbranch.tname is a fast-import symbol for a
** check-in then xbranch.brnm is the branch that check-in is part of.
**
** The XTAG table records information about tags that need to be applied
** to various branches after the import finishes. The xtag.tcontent field
|
| ︙ | ︙ |
| ︙ | ︙ | |||
42 43 44 45 46 47 48 | return zTags; } /* ** Print common information about a particular record. ** | | | | | 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 |
return zTags;
}
/*
** Print common information about a particular record.
**
** * The artifact hash
** * The record ID
** * mtime and ctime
** * who signed it
**
*/
void show_common_info(
int rid, /* The rid for the check-in to display info for */
const char *zRecDesc, /* Brief record description; e.g. "checkout:" */
int showComment, /* True to show the check-in comment */
int showFamily /* True to show parents and children */
){
Stmt q;
char *zComment = 0;
char *zTags;
char *zDate;
char *zUuid;
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
if( zUuid ){
zDate = db_text(0,
"SELECT datetime(mtime) || ' UTC' FROM event WHERE objid=%d",
rid
);
/* 01234567890123 */
fossil_print("%-13s %.40s %s\n", zRecDesc, zUuid, zDate ? zDate : "");
free(zDate);
if( showComment ){
zComment = db_text(0,
"SELECT coalesce(ecomment,comment) || "
" ' (user: ' || coalesce(euser,user,'?') || ')' "
" FROM event WHERE objid=%d",
rid
|
| ︙ | ︙ | |||
165 166 167 168 169 170 171 |
zParentCode = db_get("parent-project-code",0);
if( zParentCode ){
fossil_print("derived-from: %s %s\n", zParentCode,
db_get("parent-project-name",""));
}
}
| < | | 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 |
zParentCode = db_get("parent-project-code",0);
if( zParentCode ){
fossil_print("derived-from: %s %s\n", zParentCode,
db_get("parent-project-name",""));
}
}
/*
** COMMAND: info
**
** Usage: %fossil info ?VERSION | REPOSITORY_FILENAME? ?OPTIONS?
**
** With no arguments, provide information about the current tree.
** If an argument is specified, provide information about the object
** in the repository of the current tree that the argument refers
** to. Or if the argument is the name of a repository, show
** information about that repository.
**
** If the argument is a repository name, then the --verbose option shows
** all known check-out locations for that repository and all URLs used
** to access the repository. The --verbose is (currently) a no-op if
** the argument is the name of a object within the repository.
**
** Use the "finfo" command to get information about a specific
** file in a checkout.
**
** Options:
|
| ︙ | ︙ | |||
213 214 215 216 217 218 219 |
db_record_repository_filename(g.argv[2]);
fossil_print("project-name: %s\n", db_get("project-name", "<unnamed>"));
fossil_print("project-code: %s\n", db_get("project-code", "<none>"));
showParentProject();
extraRepoInfo();
return;
}
| | | | | > > > > | > > | | | | | | | > > > > > > > > > > > > > > > | | > > > > > > | | > > > | | | | > > | | | | | | | | | | | > | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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 |
db_record_repository_filename(g.argv[2]);
fossil_print("project-name: %s\n", db_get("project-name", "<unnamed>"));
fossil_print("project-code: %s\n", db_get("project-code", "<none>"));
showParentProject();
extraRepoInfo();
return;
}
db_find_and_open_repository(OPEN_OK_NOT_FOUND,0);
verify_all_options();
if( g.argc==2 ){
int vid;
if( g.repositoryOpen ){
db_record_repository_filename(0);
fossil_print("project-name: %s\n", db_get("project-name", "<unnamed>"));
}else{
db_open_config(0,1);
}
if( g.localOpen ){
fossil_print("repository: %s\n", db_repository_filename());
fossil_print("local-root: %s\n", g.zLocalRoot);
}
if( verboseFlag && g.repositoryOpen ){
extraRepoInfo();
}
if( g.zConfigDbName ){
fossil_print("config-db: %s\n", g.zConfigDbName);
}
if( g.repositoryOpen ){
fossil_print("project-code: %s\n", db_get("project-code", ""));
showParentProject();
vid = g.localOpen ? db_lget_int("checkout", 0) : 0;
if( vid ){
show_common_info(vid, "checkout:", 1, 1);
}
fossil_print("check-ins: %d\n",
db_int(-1, "SELECT count(*) FROM event WHERE type='ci' /*scan*/"));
}
if( verboseFlag || !g.repositoryOpen ){
Blob vx;
char *z;
fossil_version_blob(&vx, 0);
z = strstr(blob_str(&vx), "version");
if( z ){
z += 8;
}else{
z = blob_str(&vx);
}
fossil_print("fossil: %z\n", file_fullexename(g.nameOfExe));
fossil_print("version: %s", z);
blob_reset(&vx);
}
}else{
int rid;
rid = name_to_rid(g.argv[2]);
if( rid==0 ){
fossil_fatal("no such object: %s", g.argv[2]);
}
show_common_info(rid, "hash:", 1, 1);
}
}
/*
** Show the context graph (immediate parents and children) for
** check-in rid.
*/
void render_checkin_context(int rid, int rid2, int parentsOnly){
Blob sql;
Stmt q;
int rx[2];
int i, n;
rx[0] = rid;
rx[1] = rid2;
n = rid2 ? 2 : 1;
blob_zero(&sql);
blob_append(&sql, timeline_query_for_www(), -1);
db_multi_exec(
"CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
"DELETE FROM ok;"
);
for(i=0; i<n; i++){
db_multi_exec(
"INSERT OR IGNORE INTO ok VALUES(%d);"
"INSERT OR IGNORE INTO ok SELECT pid FROM plink WHERE cid=%d;",
rx[i], rx[i]
);
}
if( !parentsOnly ){
for(i=0; i<n; i++){
db_multi_exec(
"INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;", rx[i]
);
if( db_table_exists("repository","cherrypick") ){
db_multi_exec(
"INSERT OR IGNORE INTO ok "
" SELECT parentid FROM cherrypick WHERE childid=%d;"
"INSERT OR IGNORE INTO ok "
" SELECT childid FROM cherrypick WHERE parentid=%d;",
rx[i], rx[i]
);
}
}
}
blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC");
db_prepare(&q, "%s", blob_sql_text(&sql));
www_print_timeline(&q,
TIMELINE_GRAPH
|TIMELINE_FILLGAPS
|TIMELINE_NOSCROLL
|TIMELINE_XMERGE
|TIMELINE_CHPICK,
0, 0, 0, rid, rid2, 0);
db_finalize(&q);
}
/*
** Append the difference between artifacts to the output
*/
static void append_diff(
|
| ︙ | ︙ | |||
567 568 569 570 571 572 573 |
@ <span class="infoTag">%h(zTagname)=%h(zValue)</span>
}else {
@ <span class="infoTag">%h(zTagname)</span>
}
if( tagtype==2 ){
if( zOrigUuid && zOrigUuid[0] ){
@ inherited from
| | | | 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 |
@ <span class="infoTag">%h(zTagname)=%h(zValue)</span>
}else {
@ <span class="infoTag">%h(zTagname)</span>
}
if( tagtype==2 ){
if( zOrigUuid && zOrigUuid[0] ){
@ inherited from
hyperlink_to_version(zOrigUuid);
}else{
@ propagates to descendants
}
}
if( zSrcUuid && zSrcUuid[0] ){
if( tagtype==0 ){
@ by
}else{
@ added by
}
hyperlink_to_version(zSrcUuid);
@ on
hyperlink_to_date(zDate,0);
}
@ </li>
}
db_finalize(&q);
if( cnt ){
|
| ︙ | ︙ | |||
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 |
" WHERE tagxref.rid=%d;"
"INSERT OR IGNORE INTO ok "
" SELECT tagxref.origid"
" FROM tagxref JOIN tag ON tagxref.tagid=tag.tagid"
" WHERE tagxref.rid=%d;",
rid, rid, rid
);
db_multi_exec(
"SELECT tag.tagid, tagname, "
" (SELECT uuid FROM blob WHERE rid=tagxref.srcid AND rid!=%d),"
" value, datetime(tagxref.mtime,toLocal()), tagtype,"
" (SELECT uuid FROM blob WHERE rid=tagxref.origid AND rid!=%d)"
" FROM tagxref JOIN tag ON tagxref.tagid=tag.tagid"
" WHERE tagxref.rid=%d"
" ORDER BY tagname /*sort*/", rid, rid, rid
);
blob_zero(&sql);
blob_append(&sql, timeline_query_for_www(), -1);
blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC");
db_prepare(&q, "%s", blob_sql_text(&sql));
www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH|TIMELINE_NOSCROLL,
| > > | | 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 |
" WHERE tagxref.rid=%d;"
"INSERT OR IGNORE INTO ok "
" SELECT tagxref.origid"
" FROM tagxref JOIN tag ON tagxref.tagid=tag.tagid"
" WHERE tagxref.rid=%d;",
rid, rid, rid
);
#if 0
db_multi_exec(
"SELECT tag.tagid, tagname, "
" (SELECT uuid FROM blob WHERE rid=tagxref.srcid AND rid!=%d),"
" value, datetime(tagxref.mtime,toLocal()), tagtype,"
" (SELECT uuid FROM blob WHERE rid=tagxref.origid AND rid!=%d)"
" FROM tagxref JOIN tag ON tagxref.tagid=tag.tagid"
" WHERE tagxref.rid=%d"
" ORDER BY tagname /*sort*/", rid, rid, rid
);
#endif
blob_zero(&sql);
blob_append(&sql, timeline_query_for_www(), -1);
blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC");
db_prepare(&q, "%s", blob_sql_text(&sql));
www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH|TIMELINE_NOSCROLL,
0, 0, 0, rid, 0, 0);
db_finalize(&q);
style_footer();
}
/*
** WEBPAGE: vinfo
** WEBPAGE: ci
|
| ︙ | ︙ | |||
642 643 644 645 646 647 648 |
void ci_page(void){
Stmt q1, q2, q3;
int rid;
int isLeaf;
int diffType; /* 0: no diff, 1: unified, 2: side-by-side */
u64 diffFlags; /* Flag parameter for text_diff() */
const char *zName; /* Name of the check-in to be displayed */
| | | > | 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 |
void ci_page(void){
Stmt q1, q2, q3;
int rid;
int isLeaf;
int diffType; /* 0: no diff, 1: unified, 2: side-by-side */
u64 diffFlags; /* Flag parameter for text_diff() */
const char *zName; /* Name of the check-in to be displayed */
const char *zUuid; /* Hash of zName, found via blob.uuid */
const char *zParent; /* Hash of the parent check-in (if any) */
const char *zRe; /* regex parameter */
ReCompiled *pRe = 0; /* regex */
const char *zW; /* URL param for ignoring whitespace */
const char *zPage = "vinfo"; /* Page that shows diffs */
const char *zPageHide = "ci"; /* Page that hides diffs */
const char *zBrName; /* Branch name */
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
zName = P("name");
rid = name_to_rid_www("name");
if( rid==0 ){
style_header("Check-in Information Error");
|
| ︙ | ︙ | |||
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 |
"SELECT uuid, datetime(mtime,toLocal()), user, comment,"
" datetime(omtime,toLocal()), mtime"
" FROM blob, event"
" WHERE blob.rid=%d"
" AND event.objid=%d",
rid, rid
);
cookie_link_parameter("diff","diff","2");
diffType = atoi(PD("diff","2"));
if( db_step(&q1)==SQLITE_ROW ){
const char *zUuid = db_column_text(&q1, 0);
int nUuid = db_column_bytes(&q1, 0);
char *zEUser, *zEComment;
const char *zUser;
const char *zOrigUser;
const char *zComment;
const char *zDate;
const char *zOrigDate;
| > < > < < < | 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 |
"SELECT uuid, datetime(mtime,toLocal()), user, comment,"
" datetime(omtime,toLocal()), mtime"
" FROM blob, event"
" WHERE blob.rid=%d"
" AND event.objid=%d",
rid, rid
);
zBrName = branch_of_rid(rid);
cookie_link_parameter("diff","diff","2");
diffType = atoi(PD("diff","2"));
if( db_step(&q1)==SQLITE_ROW ){
const char *zUuid = db_column_text(&q1, 0);
int nUuid = db_column_bytes(&q1, 0);
char *zEUser, *zEComment;
const char *zUser;
const char *zOrigUser;
const char *zComment;
const char *zDate;
const char *zOrigDate;
int okWiki = 0;
Blob wiki_read_links = BLOB_INITIALIZER;
Blob wiki_add_links = BLOB_INITIALIZER;
Th_Store("current_checkin", zName);
style_header("Check-in [%S]", zUuid);
login_anonymous_available();
zEUser = db_text(0,
"SELECT value FROM tagxref"
" WHERE tagid=%d AND rid=%d AND tagtype>0",
TAG_USER, rid);
zEComment = db_text(0,
"SELECT value FROM tagxref WHERE tagid=%d AND rid=%d",
TAG_COMMENT, rid);
zOrigUser = db_column_text(&q1, 2);
zUser = zEUser ? zEUser : zOrigUser;
zComment = db_column_text(&q1, 3);
zDate = db_column_text(&q1,1);
zOrigDate = db_column_text(&q1, 4);
if( zOrigDate==0 ) zOrigDate = zDate;
@ <div class="section">Overview</div>
|
| ︙ | ︙ | |||
816 817 818 819 820 821 822 |
){
@ <tr><th>Original User & Date:</th><td>
hyperlink_to_user(zOrigUser,zOrigDate," on ");
hyperlink_to_date(zOrigDate, "</td></tr>");
}
if( g.perm.Admin ){
db_prepare(&q2,
| | > > | > | | > > | | | 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 |
){
@ <tr><th>Original User & Date:</th><td>
hyperlink_to_user(zOrigUser,zOrigDate," on ");
hyperlink_to_date(zOrigDate, "</td></tr>");
}
if( g.perm.Admin ){
db_prepare(&q2,
"SELECT rcvfrom.ipaddr, user.login, datetime(rcvfrom.mtime),"
" blob.rcvid"
" FROM blob JOIN rcvfrom USING(rcvid) LEFT JOIN user USING(uid)"
" WHERE blob.rid=%d",
rid
);
if( db_step(&q2)==SQLITE_ROW ){
const char *zIpAddr = db_column_text(&q2, 0);
const char *zUser = db_column_text(&q2, 1);
const char *zDate = db_column_text(&q2, 2);
int rcvid = db_column_int(&q2,3);
if( zUser==0 || zUser[0]==0 ) zUser = "unknown";
@ <tr><th>Received From:</th>
@ <td>%h(zUser) @ %h(zIpAddr) on %s(zDate) \
@ (<a href="%R/rcvfrom?rcvid=%d(rcvid)">Rcvid %d(rcvid)</a>)</td></tr>
}
db_finalize(&q2);
}
/* Only show links to edit wiki pages if the users can read wiki
** and if the wiki pages already exist */
if( g.perm.WrWiki
&& g.perm.RdWiki
&& g.perm.Write
&& ((okWiki = wiki_tagid2("checkin",zUuid))!=0 ||
blob_size(&wiki_read_links)>0)
&& db_get_boolean("wiki-about",1)
){
const char *zLinks = blob_str(&wiki_read_links);
@ <tr><th>Edit Wiki:</th><td>\
if( okWiki ){
@ %z(href("%R/wikiedit?name=checkin/%s",zUuid))this checkin</a>\
}else if( zLinks[0] ){
zLinks += 3;
}
@ %s(zLinks)</td></tr>
}
/* Only show links to create new wiki pages if the users can write wiki
|
| ︙ | ︙ | |||
870 871 872 873 874 875 876 |
}
@ %s(zLinks)</td></tr>
}
if( g.perm.Hyperlink ){
@ <tr><th>Other Links:</th>
@ <td>
| > > > | | 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 |
}
@ %s(zLinks)</td></tr>
}
if( g.perm.Hyperlink ){
@ <tr><th>Other Links:</th>
@ <td>
if( fossil_strcmp(zBrName, db_get("main-branch",0))!=0 ){
@ %z(href("%R/vdiff?branch=%!S", zUuid))branch diff</a> |
}
@ %z(href("%R/artifact/%!S",zUuid))manifest</a>
@ | %z(href("%R/ci_tags/%!S",zUuid))tags</a>
if( g.perm.Admin ){
@ | %z(href("%R/mlink?ci=%!S",zUuid))mlink table</a>
}
if( g.anon.Write ){
@ | %z(href("%R/ci_edit?r=%!S",zUuid))edit</a>
}
|
| ︙ | ︙ | |||
894 895 896 897 898 899 900 |
}
db_finalize(&q1);
if( !PB("nowiki") ){
wiki_render_associated("checkin", zUuid, 0);
}
render_backlink_graph(zUuid, "<div class=\"section\">References</div>\n");
@ <div class="section">Context</div>
| | | 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 |
}
db_finalize(&q1);
if( !PB("nowiki") ){
wiki_render_associated("checkin", zUuid, 0);
}
render_backlink_graph(zUuid, "<div class=\"section\">References</div>\n");
@ <div class="section">Context</div>
render_checkin_context(rid, 0, 0);
@ <div class="section">Changes</div>
@ <div class="sectionmenu">
diffFlags = construct_diff_flags(diffType);
zW = (diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
if( diffType!=0 ){
@ %z(chref("button","%R/%s/%T?diff=0",zPageHide,zName))\
@ Hide Diffs</a>
|
| ︙ | ︙ | |||
961 962 963 964 965 966 967 | append_diff_javascript(diffType==2); cookie_render(); style_footer(); } /* ** WEBPAGE: winfo | | | 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 |
append_diff_javascript(diffType==2);
cookie_render();
style_footer();
}
/*
** WEBPAGE: winfo
** URL: /winfo?name=HASH
**
** Display information about a wiki page.
*/
void winfo_page(void){
int rid;
Manifest *pWiki;
char *zUuid;
|
| ︙ | ︙ | |||
1002 1003 1004 1005 1006 1007 1008 |
/*NOTREACHED*/
}else{
cgi_redirectf("%R/modreq");
/*NOTREACHED*/
}
}
if( strcmp(zModAction,"approve")==0 ){
| | | 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 |
/*NOTREACHED*/
}else{
cgi_redirectf("%R/modreq");
/*NOTREACHED*/
}
}
if( strcmp(zModAction,"approve")==0 ){
moderation_approve('w', rid);
}
}
style_header("Update of \"%h\"", pWiki->zWikiTitle);
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
zDate = db_text(0, "SELECT datetime(%.17g)", pWiki->rDate);
style_submenu_element("Raw", "artifact/%s", zUuid);
style_submenu_element("History", "whistory?name=%t", pWiki->zWikiTitle);
|
| ︙ | ︙ | |||
1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 |
@ </form>
@ </blockquote>
}
@ <div class="section">Content</div>
blob_init(&wiki, pWiki->zWiki, -1);
wiki_render_by_mimetype(&wiki, pWiki->zMimetype);
blob_reset(&wiki);
manifest_destroy(pWiki);
style_footer();
}
/*
| > | 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 |
@ </form>
@ </blockquote>
}
@ <div class="section">Content</div>
blob_init(&wiki, pWiki->zWiki, -1);
safe_html_context(DOCSRC_WIKI);
wiki_render_by_mimetype(&wiki, pWiki->zMimetype);
blob_reset(&wiki);
manifest_destroy(pWiki);
style_footer();
}
/*
|
| ︙ | ︙ | |||
1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 |
if( !is_a_version(rid) ){
webpage_error("Artifact %s is not a check-in.", P(zParam));
return 0;
}
return manifest_get(rid, CFTYPE_MANIFEST, 0);
}
/*
** Output a description of a check-in
*/
static void checkin_description(int rid){
Stmt q;
db_prepare(&q,
"SELECT datetime(mtime), coalesce(euser,user),"
| > | 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 |
if( !is_a_version(rid) ){
webpage_error("Artifact %s is not a check-in.", P(zParam));
return 0;
}
return manifest_get(rid, CFTYPE_MANIFEST, 0);
}
#if 0 /* not used */
/*
** Output a description of a check-in
*/
static void checkin_description(int rid){
Stmt q;
db_prepare(&q,
"SELECT datetime(mtime), coalesce(euser,user),"
|
| ︙ | ︙ | |||
1121 1122 1123 1124 1125 1126 1127 |
const char *zUuid = db_column_text(&q, 3);
const char *zTagList = db_column_text(&q, 4);
Blob comment;
int wikiFlags = WIKI_INLINE|WIKI_NOBADLINKS;
if( db_get_boolean("timeline-block-markup", 0)==0 ){
wikiFlags |= WIKI_NOBLOCK;
}
| | | 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 |
const char *zUuid = db_column_text(&q, 3);
const char *zTagList = db_column_text(&q, 4);
Blob comment;
int wikiFlags = WIKI_INLINE|WIKI_NOBADLINKS;
if( db_get_boolean("timeline-block-markup", 0)==0 ){
wikiFlags |= WIKI_NOBLOCK;
}
hyperlink_to_version(zUuid);
blob_zero(&comment);
db_column_blob(&q, 2, &comment);
wiki_convert(&comment, 0, wikiFlags);
blob_reset(&comment);
@ (user:
hyperlink_to_user(zUser,zDate,",");
if( zTagList && zTagList[0] && g.perm.Hyperlink ){
|
| ︙ | ︙ | |||
1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 |
}
@ date:
hyperlink_to_date(zDate, ")");
tag_private_status(rid);
}
db_finalize(&q);
}
/*
** WEBPAGE: vdiff
** URL: /vdiff?from=TAG&to=TAG
**
** Show the difference between two check-ins identified by the from= and
| > | 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 |
}
@ date:
hyperlink_to_date(zDate, ")");
tag_private_status(rid);
}
db_finalize(&q);
}
#endif /* not used */
/*
** WEBPAGE: vdiff
** URL: /vdiff?from=TAG&to=TAG
**
** Show the difference between two check-ins identified by the from= and
|
| ︙ | ︙ | |||
1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 |
ManifestFile *pFileFrom, *pFileTo;
const char *zBranch;
const char *zFrom;
const char *zTo;
const char *zRe;
const char *zW;
const char *zGlob;
ReCompiled *pRe = 0;
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
login_anonymous_available();
load_control();
cookie_link_parameter("diff","diff","2");
diffType = atoi(PD("diff","2"));
cookie_render();
zRe = P("regex");
if( zRe ) re_compile(&pRe, zRe, 0);
zBranch = P("branch");
| > > | > > > | > > > | > | | | | | | > | | | > | > > > | > > > > > > > > > > > > > > > > > | | > | > | < > | 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 |
ManifestFile *pFileFrom, *pFileTo;
const char *zBranch;
const char *zFrom;
const char *zTo;
const char *zRe;
const char *zW;
const char *zGlob;
char *zQuery;
char *zMergeOrigin = 0;
ReCompiled *pRe = 0;
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
login_anonymous_available();
load_control();
cookie_link_parameter("diff","diff","2");
diffType = atoi(PD("diff","2"));
cookie_render();
zRe = P("regex");
if( zRe ) re_compile(&pRe, zRe, 0);
zBranch = P("branch");
if( zBranch && zBranch[0]==0 ) zBranch = 0;
if( zBranch ){
zQuery = mprintf("branch=%T", zBranch);
zMergeOrigin = mprintf("merge-in:%s", zBranch);
cgi_replace_parameter("from", zMergeOrigin);
cgi_replace_parameter("to", zBranch);
}else{
zQuery = mprintf("from=%T&to=%T",PD("from",""),PD("to",""));
}
pTo = vdiff_parse_manifest("to", &ridTo);
if( pTo==0 ) return;
pFrom = vdiff_parse_manifest("from", &ridFrom);
if( pFrom==0 ) return;
zGlob = P("glob");
zFrom = P("from");
zTo = P("to");
if(zGlob && !*zGlob){
zGlob = NULL;
}
diffFlags = construct_diff_flags(diffType);
zW = (diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
if( zBranch==0 ){
style_submenu_element("Path", "%R/timeline?me=%T&you=%T", zFrom, zTo);
}
if( diffType!=0 ){
style_submenu_element("Hide Diff", "%R/vdiff?%s&diff=0%s%T%s",
zQuery,
zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW);
}
if( diffType!=2 ){
style_submenu_element("Side-by-Side Diff",
"%R/vdiff?%s&diff=2%s%T%s",
zQuery,
zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW);
}
if( diffType!=1 ) {
style_submenu_element("Unified Diff",
"%R/vdiff?%s&diff=1%s%T%s",
zQuery,
zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW);
}
if( zBranch==0 ){
style_submenu_element("Invert",
"%R/vdiff?from=%T&to=%T&%s%T%s", zTo, zFrom,
zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW);
}
if( zGlob ){
style_submenu_element("Clear glob",
"%R/vdiff?%s&%s", zQuery, zW);
}else{
style_submenu_element("Patch", "%R/vpatch?from=%T&to=%T%s", zFrom, zTo, zW);
}
if( diffType!=0 ){
style_submenu_checkbox("w", "Ignore Whitespace", 0, 0);
}
if( zBranch ){
style_header("Changes On Branch %h", zBranch);
}else{
style_header("Check-in Differences");
}
if( P("nohdr")==0 ){
if( zBranch ){
char *zRealBranch = branch_of_rid(ridTo);
char *zToUuid = rid_to_uuid(ridTo);
char *zFromUuid = rid_to_uuid(ridFrom);
@ <h2>Changes In Branch \
@ %z(href("%R/timeline?r=%T",zRealBranch))%h(zRealBranch)</a>
if( ridTo != symbolic_name_to_rid(zRealBranch,"ci") ){
@ Through %z(href("%R/info/%!S",zToUuid))[%S(zToUuid)]</a>
}
@ Excluding Merge-Ins</h2>
@ <p>This is equivalent to a diff from
@ <span class='timelineSelected'>\
@ %z(href("%R/info/%!S",zFromUuid))%S(zFromUuid)</a></span>
@ to <span class='timelineSelected timelineSecondary'>\
@ %z(href("%R/info/%!S",zToUuid))%S(zToUuid)</a></span></p>
}else{
@ <h2>Difference From <span class='timelineSelected'>\
@ %z(href("%R/info/%h",zFrom))%h(zFrom)</a></span>
@ To <span class='timelineSelected timelineSecondary'>\
@ %z(href("%R/info/%h",zTo))%h(zTo)</a></span></h2>
}
render_checkin_context(ridFrom, ridTo, 0);
if( pRe ){
@ <p><b>Only differences that match regular expression "%h(zRe)"
@ are shown.</b></p>
}
if( zGlob ){
@ <p><b>Only files matching the glob "%h(zGlob)" are shown.</b></p>
}
@<hr /><p>
}
fossil_free(zQuery);
manifest_file_rewind(pFrom);
pFileFrom = manifest_file_next(pFrom, 0);
manifest_file_rewind(pTo);
pFileTo = manifest_file_next(pTo, 0);
while( pFileFrom || pFileTo ){
int cmp;
|
| ︙ | ︙ | |||
1328 1329 1330 1331 1332 1333 1334 | #define OBJTYPE_EXE 0x0100 #define OBJTYPE_FORUM 0x0200 /* ** Possible flags for the second parameter to ** object_description() */ | | < < < < < < < < < < < < | > | | 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 |
#define OBJTYPE_EXE 0x0100
#define OBJTYPE_FORUM 0x0200
/*
** Possible flags for the second parameter to
** object_description()
*/
#define OBJDESC_DETAIL 0x0001 /* Show more detail */
#define OBJDESC_BASE 0x0002 /* Set <base> using this object */
#endif
/*
** Write a description of an object to the www reply.
*/
int object_description(
int rid, /* The artifact ID for the object to describe */
u32 objdescFlags, /* Flags to control display */
const char *zFileName, /* For file objects, use this name. Can be NULL */
Blob *pDownloadName /* Fill with a good download name. Can be NULL */
){
Stmt q;
int cnt = 0;
int nWiki = 0;
int objType = 0;
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
int showDetail = (objdescFlags & OBJDESC_DETAIL)!=0;
|
| ︙ | ︙ | |||
1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 |
const char *zCom = db_column_text(&q, 2);
const char *zUser = db_column_text(&q, 3);
const char *zVers = db_column_text(&q, 4);
int mPerm = db_column_int(&q, 5);
const char *zBr = db_column_text(&q, 6);
int szFile = db_column_int(&q,7);
int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0;
if( sameFilename && !showDetail ){
if( cnt==1 ){
@ %z(href("%R/whatis/%!S",zUuid))[more...]</a>
}
cnt++;
continue;
}
| > | 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 |
const char *zCom = db_column_text(&q, 2);
const char *zUser = db_column_text(&q, 3);
const char *zVers = db_column_text(&q, 4);
int mPerm = db_column_int(&q, 5);
const char *zBr = db_column_text(&q, 6);
int szFile = db_column_int(&q,7);
int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0;
if( zFileName && fossil_strcmp(zName,zFileName)!=0 ) continue;
if( sameFilename && !showDetail ){
if( cnt==1 ){
@ %z(href("%R/whatis/%!S",zUuid))[more...]</a>
}
cnt++;
continue;
}
|
| ︙ | ︙ | |||
1425 1426 1427 1428 1429 1430 1431 |
}
prevName = fossil_strdup(zName);
}
if( showDetail ){
@ <li>
hyperlink_to_date(zDate,"");
@ — part of check-in
| | | > > > | 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 |
}
prevName = fossil_strdup(zName);
}
if( showDetail ){
@ <li>
hyperlink_to_date(zDate,"");
@ — part of check-in
hyperlink_to_version(zVers);
}else{
@ — part of check-in
hyperlink_to_version(zVers);
@ at
hyperlink_to_date(zDate,"");
}
if( zBr && zBr[0] ){
@ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a>
}
@ — %!W(zCom) (user:
hyperlink_to_user(zUser,zDate,",");
@ size: %d(szFile))
if( g.perm.Hyperlink ){
@ %z(href("%R/annotate?filename=%T&checkin=%!S",zName,zVers))
@ [annotate]</a>
@ %z(href("%R/blame?filename=%T&checkin=%!S",zName,zVers))
@ [blame]</a>
@ %z(href("%R/timeline?n=all&uf=%!S",zUuid))[check-ins using]</a>
if( fileedit_is_editable(zName) ){
@ %z(href("%R/fileedit?filename=%T&checkin=%!S",zName,zVers))[edit]</a>
}
}
cnt++;
if( pDownloadName && blob_size(pDownloadName)==0 ){
blob_append(pDownloadName, zName, -1);
}
}
if( prevName && showDetail ){
|
| ︙ | ︙ | |||
1528 1529 1530 1531 1532 1533 1534 |
}else if( zType[0]=='f' ){
objType |= OBJTYPE_FORUM;
@ Forum post
}else{
@ Tag referencing
}
if( zType[0]!='e' || eventTagId == 0){
| | | 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 |
}else if( zType[0]=='f' ){
objType |= OBJTYPE_FORUM;
@ Forum post
}else{
@ Tag referencing
}
if( zType[0]!='e' || eventTagId == 0){
hyperlink_to_version(zUuid);
}
@ - %!W(zCom) by
hyperlink_to_user(zUser,zDate," on");
hyperlink_to_date(zDate, ".");
if( pDownloadName && blob_size(pDownloadName)==0 ){
blob_appendf(pDownloadName, "%S.txt", zUuid);
}
|
| ︙ | ︙ | |||
1560 1561 1562 1563 1564 1565 1566 |
/* const char *zSrc = db_column_text(&q, 4); */
if( cnt>0 ){
@ Also attachment "%h(zFilename)" to
}else{
@ Attachment "%h(zFilename)" to
}
objType |= OBJTYPE_ATTACHMENT;
| | | 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 |
/* const char *zSrc = db_column_text(&q, 4); */
if( cnt>0 ){
@ Also attachment "%h(zFilename)" to
}else{
@ Attachment "%h(zFilename)" to
}
objType |= OBJTYPE_ATTACHMENT;
if( fossil_is_artifact_hash(zTarget) ){
if ( db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'",
zTarget)
){
if( g.perm.Hyperlink && g.anon.RdTkt ){
@ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>]
}else{
@ ticket [%S(zTarget)]
|
| ︙ | ︙ | |||
1619 1620 1621 1622 1623 1624 1625 | } return objType; } /* ** WEBPAGE: fdiff | | | 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 | } return objType; } /* ** WEBPAGE: fdiff ** URL: fdiff?v1=HASH&v2=HASH ** ** Two arguments, v1 and v2, identify the artifacts to be diffed. ** Show diff side by side unless sbs is 0. Generate plain text if ** "patch" is present, otherwise generate "pretty" HTML. ** ** Alternative URL: fdiff?from=filename1&to=filename2&ci=checkin ** |
| ︙ | ︙ | |||
1659 1660 1661 1662 1663 1664 1665 |
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
cookie_link_parameter("diff","diff","2");
diffType = atoi(PD("diff","2"));
cookie_render();
if( P("from") && P("to") ){
| | | | 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 |
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
cookie_link_parameter("diff","diff","2");
diffType = atoi(PD("diff","2"));
cookie_render();
if( P("from") && P("to") ){
v1 = artifact_from_ci_and_filename("from");
v2 = artifact_from_ci_and_filename("to");
}else{
Stmt q;
v1 = name_to_rid_www("v1");
v2 = name_to_rid_www("v2");
/* If the two file versions being compared both have the same
** filename, then offer an "Annotate" link that constructs an
|
| ︙ | ︙ | |||
1735 1736 1737 1738 1739 1740 1741 |
if( P("smhdr")!=0 ){
@ <h2>Differences From Artifact
@ %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a> To
@ %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>.</h2>
}else{
@ <h2>Differences From
@ Artifact %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a>:</h2>
| | | | > > > > > | | | 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 |
if( P("smhdr")!=0 ){
@ <h2>Differences From Artifact
@ %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a> To
@ %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>.</h2>
}else{
@ <h2>Differences From
@ Artifact %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a>:</h2>
object_description(v1, objdescFlags,0, 0);
@ <h2>To Artifact %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>:</h2>
object_description(v2, objdescFlags,0, 0);
}
if( pRe ){
@ <b>Only differences that match regular expression "%h(zRe)"
@ are shown.</b>
}
@ <hr />
append_diff(zV1, zV2, diffFlags, pRe);
append_diff_javascript(diffType);
style_footer();
}
/*
** WEBPAGE: raw
** URL: /raw/ARTIFACTID
** URL: /raw?ci=BRANCH&filename=NAME
**
** Additional query parameters:
**
** m=MIMETYPE The mimetype is MIMETYPE
** at=FILENAME Content-disposition; attachment; filename=FILENAME;
**
** Return the uninterpreted content of an artifact. Used primarily
** to view artifacts that are images.
*/
void rawartifact_page(void){
int rid = 0;
char *zUuid;
if( P("ci") ){
rid = artifact_from_ci_and_filename(0);
}
if( rid==0 ){
rid = name_to_rid_www("name");
}
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
if( rid==0 ) fossil_redirect_home();
|
| ︙ | ︙ | |||
1790 1791 1792 1793 1794 1795 1796 |
** Return the uninterpreted content of an artifact. This is similar
** to /raw except in this case the only way to specify the artifact
** is by the full-length SHA1 or SHA3 hash. Abbreviations are not
** accepted.
*/
void secure_rawartifact_page(void){
int rid = 0;
| | | | > > > | | | > | | | | > | > > | | > > > > > | 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 |
** Return the uninterpreted content of an artifact. This is similar
** to /raw except in this case the only way to specify the artifact
** is by the full-length SHA1 or SHA3 hash. Abbreviations are not
** accepted.
*/
void secure_rawartifact_page(void){
int rid = 0;
const char *zName = PD("name", "");
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
if( rid==0 ){
cgi_set_status(404, "Not Found");
@ Unknown artifact: "%h(zName)"
return;
}
g.isConst = 1;
deliver_artifact(rid, P("m"));
}
/*
** Generate a verbatim artifact as the result of an HTTP request.
** If zMime is not NULL, use it as the MIME-type. If zMime is
** NULL, guess at the MIME-type based on the filename
** associated with the artifact.
*/
void deliver_artifact(int rid, const char *zMime){
Blob content;
const char *zAttachName = P("at");
if( zMime==0 ){
char *zFN = (char*)zAttachName;
if( zFN==0 ){
zFN = db_text(0, "SELECT filename.name FROM mlink, filename"
" WHERE mlink.fid=%d"
" AND filename.fnid=mlink.fnid", rid);
}
if( zFN==0 ){
/* Look also at the attachment table */
zFN = db_text(0, "SELECT attachment.filename FROM attachment, blob"
" WHERE blob.rid=%d"
" AND attachment.src=blob.uuid", rid);
}
if( zFN ){
zMime = mimetype_from_name(zFN);
}
if( zMime==0 ){
zMime = "application/x-fossil-artifact";
}
}
content_get(rid, &content);
fossil_free(style_csp(1));
cgi_set_content_type(zMime);
if( zAttachName ){
cgi_content_disposition_filename(zAttachName);
}
cgi_set_content(&content);
}
/*
** Render a hex dump of a file.
*/
static void hexdump(Blob *pBlob){
|
| ︙ | ︙ | |||
1924 1925 1926 1927 1928 1929 1930 |
if( g.perm.Setup ){
@ (%d(rid)):</h2>
}else{
@ :</h2>
}
blob_zero(&downloadName);
if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL;
| | | | | | | | | < < | < < | > > > > | | | < < < < < < | | | 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 |
if( g.perm.Setup ){
@ (%d(rid)):</h2>
}else{
@ :</h2>
}
blob_zero(&downloadName);
if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL;
object_description(rid, objdescFlags, 0, &downloadName);
style_submenu_element("Download", "%R/raw/%s?at=%T",
zUuid, file_tail(blob_str(&downloadName)));
@ <hr />
content_get(rid, &content);
@ <blockquote><pre>
hexdump(&content);
@ </pre></blockquote>
style_footer();
}
/*
** Look for "ci" and "filename" query parameters. If found, try to
** use them to extract the record ID of an artifact for the file.
**
** Also look for "fn" and "name" as an aliases for "filename". If any
** "filename" or "fn" or "name" are present but "ci" is missing, then
** use "tip" as the default value for "ci".
**
** If zNameParam is not NULL, then use that parameter as the filename
** rather than "fn" or "filename" or "name". the zNameParam is used
** for the from= and to= query parameters of /fdiff.
*/
int artifact_from_ci_and_filename(const char *zNameParam){
const char *zFilename;
const char *zCI;
int cirid;
Manifest *pManifest;
ManifestFile *pFile;
int rid = 0;
if( zNameParam ){
zFilename = P(zNameParam);
}else{
zFilename = P("filename");
if( zFilename==0 ){
zFilename = P("fn");
}
if( zFilename==0 ){
zFilename = P("name");
}
}
if( zFilename==0 ) return 0;
zCI = PD("ci", "tip");
cirid = name_to_typed_rid(zCI, "ci");
if( cirid<=0 ) return 0;
pManifest = manifest_get(cirid, CFTYPE_MANIFEST, 0);
if( pManifest==0 ) return 0;
manifest_file_rewind(pManifest);
while( (pFile = manifest_file_next(pManifest,0))!=0 ){
if( fossil_strcmp(zFilename, pFile->zName)==0 ){
rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid);
break;
}
}
manifest_destroy(pManifest);
return rid;
}
/*
** The "z" argument is a string that contains the text of a source code
** file. This routine appends that text to the HTTP reply with line numbering.
**
** zLn is the ?ln= parameter for the HTTP query. If there is an argument,
|
| ︙ | ︙ | |||
2093 2094 2095 2096 2097 2098 2099 | ** ** ln - show line numbers ** ln=N - highlight line number N ** ln=M-N - highlight lines M through N inclusive ** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive) ** verbose - show more detail in the description ** download - redirect to the download (artifact page only) | | | | | > < | > | | | > > > > > > > > > | > > > > > > > | > > | | | > > > | > > > > > > > > > | | | > | | | | | < < | > | > | > > > | > > > > > | > | > > | < | > > > | | | | | | | | | | | < < < | | < < < < < < < < < < | | > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > < | < | | > > | | > > > > > > > > > > > > > > > | < > > | | < | 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 |
**
** ln - show line numbers
** ln=N - highlight line number N
** ln=M-N - highlight lines M through N inclusive
** ln=M-N+Y-Z - highlight lines M through N and Y through Z (inclusive)
** verbose - show more detail in the description
** download - redirect to the download (artifact page only)
** name=NAME - filename or hash as a query parameter
** filename=NAME - alternative spelling for "name="
** fn=NAME - alternative spelling for "name="
** ci=VERSION - The specific check-in to use with "name=" to
** identify the file.
**
** The /artifact page show the complete content of a file
** identified by HASH. The /whatis page shows only a description
** of how the artifact is used. The /file page shows the most recent
** version of the file or directory called NAME, or a list of the
** top-level directory if NAME is omitted.
**
** For /artifact and /whatis, the name= query parameter can refer to
** either the name of a file, or an artifact hash. If the ci= query
** parameter is also present, then name= must refer to a file name.
** If ci= is omitted, then the hash interpretation is preferred but
** if name= cannot be understood as a hash, a default "tip" value is
** used for ci=.
**
** For /file, name= can only be interpreted as a filename. As before,
** a default value of "tip" is used for ci= if ci= is omitted.
*/
void artifact_page(void){
int rid = 0;
Blob content;
const char *zMime;
Blob downloadName;
int renderAsWiki = 0;
int renderAsHtml = 0;
int objType;
int asText;
const char *zUuid = 0;
u32 objdescFlags = OBJDESC_BASE;
int descOnly = fossil_strcmp(g.zPath,"whatis")==0;
int isFile = fossil_strcmp(g.zPath,"file")==0;
const char *zLn = P("ln");
const char *zName = P("name");
const char *zCI = P("ci");
HQuery url;
char *zCIUuid = 0;
int isSymbolicCI = 0; /* ci= exists and is a symbolic name, not a hash */
int isBranchCI = 0; /* ci= refers to a branch name */
char *zHeader = 0;
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
/* Capture and normalize the name= and ci= query parameters */
if( zName==0 ){
zName = P("filename");
if( zName==0 ){
zName = P("fn");
}
}
if( zCI && strlen(zCI)==0 ){ zCI = 0; }
if( zCI
&& name_to_uuid2(zCI, "ci", &zCIUuid)
&& sqlite3_strnicmp(zCIUuid, zCI, strlen(zCI))!=0
){
isSymbolicCI = 1;
isBranchCI = branch_includes_uuid(zCI, zCIUuid);
}
/* The name= query parameter (or at least one of its alternative
** spellings) is required. Except for /file, show a top-level
** directory listing if name= is omitted.
*/
if( zName==0 ){
if( isFile ){
if( P("ci")==0 ) cgi_set_query_parameter("ci","tip");
page_tree();
return;
}
style_header("Missing name= query parameter");
@ The name= query parameter is missing
style_footer();
return;
}
url_initialize(&url, g.zPath);
url_add_parameter(&url, "name", zName);
url_add_parameter(&url, "ci", zCI);
if( zCI==0 && !isFile ){
/* If there is no ci= query parameter, then prefer to interpret
** name= as a hash for /artifact and /whatis. But for not for /file.
** For /file, a name= without a ci= while prefer to use the default
** "tip" value for ci=. */
rid = name_to_rid(zName);
}
if( rid==0 ){
rid = artifact_from_ci_and_filename(0);
}
if( rid==0 ){ /* Artifact not found */
if( isFile ){
/* For /file, also check to see if name= refers to a directory,
** and if so, do a listing for that directory */
int nName = (int)strlen(zName);
if( nName && zName[nName-1]=='/' ) nName--;
if( db_exists(
"SELECT 1 FROM filename"
" WHERE name GLOB '%.*q/*' AND substr(name,1,%d)=='%.*q/';",
nName, zName, nName+1, nName, zName
) ){
if( P("ci")==0 ) cgi_set_query_parameter("ci","tip");
page_tree();
return;
}
style_header("No such file");
@ File '%h(zName)' does not exist in this repository.
}else{
style_header("No such artifact");
@ Artifact '%h(zName)' does not exist in this repository.
}
style_footer();
return;
}
if( descOnly || P("verbose")!=0 ){
url_add_parameter(&url, "verbose", "1");
objdescFlags |= OBJDESC_DETAIL;
}
zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
asText = P("txt")!=0;
if( isFile ){
if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){
zCI = "tip";
@ <h2>File %z(href("%R/finfo?name=%T&m=tip",zName))%h(zName)</a>
@ from the %z(href("%R/info/tip"))latest check-in</a></h2>
}else{
const char *zPath;
Blob path;
blob_zero(&path);
hyperlinked_path(zName, &path, zCI, "dir", "", LINKPATH_FINFO);
zPath = blob_str(&path);
@ <h2>File %s(zPath) \
if( isBranchCI ){
@ on branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2>
}else if( isSymbolicCI ){
@ as of check-in %z(href("%R/info/%!S",zCIUuid))%s(zCI)</a></h2>
}else{
@ as of check-in [%z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a>]</h2>
}
blob_reset(&path);
}
style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T",
zName, zCI);
style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T",
zName, zCI);
blob_init(&downloadName, zName, -1);
objType = OBJTYPE_CONTENT;
}else{
@ <h2>Artifact
style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid);
if( g.perm.Setup ){
@ (%d(rid)):</h2>
}else{
@ :</h2>
}
blob_zero(&downloadName);
if( asText ) objdescFlags &= ~OBJDESC_BASE;
objType = object_description(rid, objdescFlags,
(isFile?zName:0), &downloadName);
}
if( !descOnly && P("download")!=0 ){
cgi_redirectf("%R/raw/%s?at=%T",
db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid),
file_tail(blob_str(&downloadName)));
/*NOTREACHED*/
}
if( g.perm.Admin ){
const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
style_submenu_element("Unshun", "%s/shun?accept=%s&sub=1#accshun",
g.zTop, zUuid);
}else{
style_submenu_element("Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid);
}
}
if( isFile ){
if( isSymbolicCI ){
zHeader = mprintf("%s at %s", file_tail(zName), zCI);
}else if( zCI ){
zHeader = mprintf("%s at [%S]", file_tail(zName), zCIUuid);
}else{
zHeader = mprintf("%s", file_tail(zName));
}
}else if( descOnly ){
zHeader = mprintf("Artifact Description [%S]", zUuid);
}else{
zHeader = mprintf("Artifact [%S]", zUuid);
}
style_header("%s", zHeader);
fossil_free(zCIUuid);
fossil_free(zHeader);
if( !isFile && g.perm.Admin ){
Stmt q;
db_prepare(&q,
"SELECT coalesce(user.login,rcvfrom.uid),"
" datetime(rcvfrom.mtime,toLocal()), rcvfrom.ipaddr"
" FROM blob, rcvfrom LEFT JOIN user ON user.uid=rcvfrom.uid"
" WHERE blob.rid=%d"
" AND rcvfrom.rcvid=blob.rcvid;", rid);
while( db_step(&q)==SQLITE_ROW ){
const char *zUser = db_column_text(&q,0);
const char *zDate = db_column_text(&q,1);
const char *zIp = db_column_text(&q,2);
@ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p>
}
db_finalize(&q);
}
style_submenu_element("Download", "%R/raw/%s?at=%T", zUuid, file_tail(zName));
if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){
style_submenu_element("Check-ins Using", "%R/timeline?n=200&uf=%s", zUuid);
}
zMime = mimetype_from_name(blob_str(&downloadName));
if( zMime ){
if( fossil_strcmp(zMime, "text/html")==0 ){
if( asText ){
|
| ︙ | ︙ | |||
2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 |
if( asText ){
style_submenu_element("Wiki", "%s", url_render(&url, "txt", 0, 0, 0));
}else{
renderAsWiki = 1;
style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0));
}
}
}
if( (objType & (OBJTYPE_WIKI|OBJTYPE_TICKET))!=0 ){
style_submenu_element("Parsed", "%R/info/%s", zUuid);
}
if( descOnly ){
style_submenu_element("Content", "%R/artifact/%s", zUuid);
}else{
@ <hr />
content_get(rid, &content);
if( renderAsWiki ){
wiki_render_by_mimetype(&content, zMime);
}else if( renderAsHtml ){
| > > > > > > | | > > > > > > > > > > | 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 |
if( asText ){
style_submenu_element("Wiki", "%s", url_render(&url, "txt", 0, 0, 0));
}else{
renderAsWiki = 1;
style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0));
}
}
if( fileedit_is_editable(zName) ){
style_submenu_element("Edit",
"%R/fileedit?filename=%T&checkin=%!S",
zName, zCI);
}
}
if( (objType & (OBJTYPE_WIKI|OBJTYPE_TICKET))!=0 ){
style_submenu_element("Parsed", "%R/info/%s", zUuid);
}
if( descOnly ){
style_submenu_element("Content", "%R/artifact/%s", zUuid);
}else{
@ <hr />
content_get(rid, &content);
if( renderAsWiki ){
safe_html_context(DOCSRC_FILE);
wiki_render_by_mimetype(&content, zMime);
}else if( renderAsHtml ){
@ <iframe src="%R/raw/%s(zUuid)"
@ width="100%%" frameborder="0" marginwidth="0" marginheight="0"
@ sandbox="allow-same-origin" id="ifm1">
@ </iframe>
@ <script nonce="%h(style_nonce())">
@ document.getElementById("ifm1").addEventListener("load",
@ function(){
@ this.height=this.contentDocument.documentElement.scrollHeight + 75;
@ }
@ );
@ </script>
}else{
style_submenu_element("Hex", "%s/hexdump?name=%s", g.zTop, zUuid);
if( zLn==0 || atoi(zLn)==0 ){
style_submenu_checkbox("ln", "Line Numbers", 0, 0);
}
blob_to_utf8_no_bom(&content, 0);
zMime = mimetype_from_content(&content);
@ <blockquote>
if( zMime==0 ){
const char *z, *zFileName, *zExt;
z = blob_str(&content);
zFileName = db_text(0,
"SELECT name FROM mlink, filename"
" WHERE filename.fnid=mlink.fnid"
" AND mlink.fid=%d",
rid);
zExt = zFileName ? strrchr(zFileName, '.') : 0;
if( zLn ){
output_text_with_line_numbers(z, zLn);
}else if( zExt && zExt[1] ){
@ <pre>
@ <code class="language-%s(zExt+1)">%h(z)</code>
@ </pre>
}else{
@ <pre>
@ %h(z)
@ </pre>
}
}else if( strncmp(zMime, "image/", 6)==0 ){
@ <p>(file is %d(blob_size(&content)) bytes of image data)</i></p>
|
| ︙ | ︙ | |||
2357 2358 2359 2360 2361 2362 2363 |
/*NOTREACHED*/
}else{
cgi_redirectf("%R/modreq");
/*NOTREACHED*/
}
}
if( strcmp(zModAction,"approve")==0 ){
| | | 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 |
/*NOTREACHED*/
}else{
cgi_redirectf("%R/modreq");
/*NOTREACHED*/
}
}
if( strcmp(zModAction,"approve")==0 ){
moderation_approve('t', rid);
}
}
zTktTitle = db_table_has_column("repository", "ticket", "title" )
? db_text("(No title)",
"SELECT title FROM ticket WHERE tkt_uuid=%Q", zTktName)
: 0;
style_header("Ticket Change Details");
|
| ︙ | ︙ | |||
2411 2412 2413 2414 2415 2416 2417 |
@ <input type="submit" value="Submit">
@ </form>
@ </blockquote>
}
@ <div class="section">Changes</div>
@ <p>
| | | | | > > | | 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 |
@ <input type="submit" value="Submit">
@ </form>
@ </blockquote>
}
@ <div class="section">Changes</div>
@ <p>
ticket_output_change_artifact(pTktChng, 0, 1);
manifest_destroy(pTktChng);
style_footer();
}
/*
** WEBPAGE: info
** URL: info/NAME
**
** The NAME argument is any valid artifact name: an artifact hash,
** a timestamp, a tag name, etc.
**
** Because NAME can match so many different things (commit artifacts,
** wiki pages, ticket comments, forum posts...) the format of the output
** page depends on the type of artifact that NAME matches.
*/
void info_page(void){
const char *zName;
Blob uuid;
int rid;
int rc;
int nLen;
|
| ︙ | ︙ | |||
2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 |
ambiguous_page();
return;
}
rc = name_to_uuid(&uuid, -1, "*");
if( rc==1 ){
if( validate16(zName, nLen) ){
if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){
tktview_page();
return;
}
if( db_exists("SELECT 1 FROM tag"
" WHERE tagname GLOB 'event-%q*'", zName) ){
event_page();
return;
| > | 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 |
ambiguous_page();
return;
}
rc = name_to_uuid(&uuid, -1, "*");
if( rc==1 ){
if( validate16(zName, nLen) ){
if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){
cgi_set_parameter_nocopy("tl","1",0);
tktview_page();
return;
}
if( db_exists("SELECT 1 FROM tag"
" WHERE tagname GLOB 'event-%q*'", zName) ){
event_page();
return;
|
| ︙ | ︙ | |||
2684 2685 2686 2687 2688 2689 2690 | /* ** WEBPAGE: ci_edit ** ** Edit a check-in. (Check-ins are immutable and do not really change. ** This page really creates supplemental tags that affect the display ** of the check-in.) ** | | | | 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 | /* ** WEBPAGE: ci_edit ** ** Edit a check-in. (Check-ins are immutable and do not really change. ** This page really creates supplemental tags that affect the display ** of the check-in.) ** ** Query parameters: ** ** rid=INTEGER Record ID of the check-in to edit (REQUIRED) ** ** POST parameters after pressing "Preview", "Cancel", or "Apply": ** ** c=TEXT New check-in comment ** u=TEXT New user name ** newclr Apply a background color ** clr=TEXT New background color (only if newclr) ** pclr Propagate new background color (only if newclr) ** dt=TEXT New check-in date/time (ISO8610 format) |
| ︙ | ︙ | |||
2946 2947 2948 2949 2950 2951 2952 |
@ Cancel tag <b>%h(&zTagName[4])</b></label>
}
}
db_finalize(&q);
@ </td></tr>
if( !zBranchName ){
| | | 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 |
@ Cancel tag <b>%h(&zTagName[4])</b></label>
}
}
db_finalize(&q);
@ </td></tr>
if( !zBranchName ){
zBranchName = db_get("main-branch", 0);
}
if( !zNewBranch || !zNewBranch[0]){
zNewBranch = zBranchName;
}
@ <tr><th align="right" valign="top">Branching:</th>
@ <td valign="top">
@ <label><input id="newbr" type="checkbox" name="newbr" \
|
| ︙ | ︙ | |||
3037 3038 3039 3040 3041 3042 3043 |
blob_append(&prompt, zUuid, -1);
}
blob_append(&prompt, ".\n# Lines beginning with a # are ignored.\n", -1);
prompt_for_user_comment(pComment, &prompt);
blob_reset(&prompt);
}
| | | | | 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 |
blob_append(&prompt, zUuid, -1);
}
blob_append(&prompt, ".\n# Lines beginning with a # are ignored.\n", -1);
prompt_for_user_comment(pComment, &prompt);
blob_reset(&prompt);
}
#define AMEND_USAGE_STMT "HASH OPTION ?OPTION ...?"
/*
** COMMAND: amend
**
** Usage: %fossil amend HASH OPTION ?OPTION ...?
**
** Amend the tags on check-in HASH to change how it displays in the timeline.
**
** Options:
**
** --author USER Make USER the author for check-in
** -m|--comment COMMENT Make COMMENT the check-in comment
** -M|--message-file FILE Read the amended comment from FILE
** -e|--edit-comment Launch editor to revise comment
|
| ︙ | ︙ | |||
3131 3132 3133 3134 3135 3136 3137 |
db_find_and_open_repository(0,0);
user_select();
verify_all_options();
if( g.argc<3 || g.argc>=4 ) usage(AMEND_USAGE_STMT);
rid = name_to_typed_rid(g.argv[2], "ci");
if( rid==0 && !is_a_version(rid) ) fossil_fatal("no such check-in");
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
| | | 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 |
db_find_and_open_repository(0,0);
user_select();
verify_all_options();
if( g.argc<3 || g.argc>=4 ) usage(AMEND_USAGE_STMT);
rid = name_to_typed_rid(g.argv[2], "ci");
if( rid==0 && !is_a_version(rid) ) fossil_fatal("no such check-in");
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
if( zUuid==0 ) fossil_fatal("Unable to find artifact hash");
zComment = db_text(0, "SELECT coalesce(ecomment,comment)"
" FROM event WHERE objid=%d", rid);
zUser = db_text(0, "SELECT coalesce(euser,user)"
" FROM event WHERE objid=%d", rid);
zDate = db_text(0, "SELECT datetime(mtime)"
" FROM event WHERE objid=%d", rid);
zColor = db_text("", "SELECT bgcolor"
|
| ︙ | ︙ | |||
3221 3222 3223 3224 3225 3226 3227 |
fossil_free((void *)pzCancelTags);
}
if( fHide && !fHasHidden ) hide_branch();
if( fClose && !fHasClosed ) close_leaf(rid);
if( zNewBranch && zNewBranch[0] ) change_branch(rid,zNewBranch);
apply_newtags(&ctrl, rid, zUuid, zUserOvrd, fDryRun);
if( fDryRun==0 ){
| | | 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 |
fossil_free((void *)pzCancelTags);
}
if( fHide && !fHasHidden ) hide_branch();
if( fClose && !fHasClosed ) close_leaf(rid);
if( zNewBranch && zNewBranch[0] ) change_branch(rid,zNewBranch);
apply_newtags(&ctrl, rid, zUuid, zUserOvrd, fDryRun);
if( fDryRun==0 ){
show_common_info(rid, "hash:", 1, 0);
}
if( g.localOpen ){
manifest_to_disk(rid);
}
}
|
| ︙ | ︙ | |||
50 51 52 53 54 55 56 | "payload" /* payload */, "requestId" /*requestId*/, "resultCode" /*resultCode*/, "resultText" /*resultText*/, "timestamp" /*timestamp*/ }; | < < > > > | 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
"payload" /* payload */,
"requestId" /*requestId*/,
"resultCode" /*resultCode*/,
"resultText" /*resultText*/,
"timestamp" /*timestamp*/
};
/*
** Returns true (non-0) if fossil appears to be running in JSON mode.
** and either has JSON POSTed input awaiting consumption or fossil is
** running in HTTP mode (in which case certain JSON data *might* be
** available via GET parameters).
*/
int fossil_has_json(){
return g.json.isJsonMode && (g.isHTTP || g.json.post.o);
}
/*
** Placeholder /json/XXX page impl for NYI (Not Yet Implemented)
|
| ︙ | ︙ | |||
280 281 282 283 284 285 286 | ** NULL if no match is found. ** ** ENV means the system environment (getenv()). ** ** Precedence: POST.payload, GET/COOKIE/non-JSON POST, JSON POST, ENV. ** ** FIXME: the precedence SHOULD be: GET, POST.payload, POST, COOKIE, | | | | 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
** NULL if no match is found.
**
** ENV means the system environment (getenv()).
**
** Precedence: POST.payload, GET/COOKIE/non-JSON POST, JSON POST, ENV.
**
** FIXME: the precedence SHOULD be: GET, POST.payload, POST, COOKIE,
** ENV, but the amalgamation of the GET/POST vars makes it effectively
** impossible to do that. Since fossil only uses one cookie, cookie
** precedence isn't a real/high-priority problem.
*/
cson_value * json_getenv( char const * zKey ){
cson_value * rc;
rc = g.json.reqPayload.o
? cson_object_get( g.json.reqPayload.o, zKey )
: NULL;
|
| ︙ | ︙ | |||
574 575 576 577 578 579 580 581 582 583 584 585 586 587 |
: "application/json";
}else{
return "text/plain";
}
}
}
}
/*
** Sends pResponse to the output stream as the response object. This
** function does no validation of pResponse except to assert() that it
** is not NULL. The caller is responsible for ensuring that it meets
** API response envelope conventions.
**
| > > > > > > > > > > > > > > > | 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 |
: "application/json";
}else{
return "text/plain";
}
}
}
}
/*
** Given a request CONTENT_TYPE value, this function returns true
** if it is of a type which the JSON API can ostensibly read.
**
** It accepts any of application/json, text/plain, or
** application/javascript. The former is preferred, but was not
** widespread when this API was initially built, so the latter forms
** are permitted as fallbacks.
*/
int json_can_consume_content_type(const char * zType){
return fossil_strcmp(zType, "application/json")==0
|| fossil_strcmp(zType,"text/plain")==0/*assume this MIGHT be JSON*/
|| fossil_strcmp(zType,"application/javascript")==0;
}
/*
** Sends pResponse to the output stream as the response object. This
** function does no validation of pResponse except to assert() that it
** is not NULL. The caller is responsible for ensuring that it meets
** API response envelope conventions.
**
|
| ︙ | ︙ | |||
628 629 630 631 632 633 634 635 636 |
**
** Must be called once before login_check_credentials() is called or
** we will not be able to replace fossil's internal idea of the auth
** info in time (and future changes to that state may cause unexpected
** results).
**
** The result of this call are cached for future calls.
*/
cson_value * json_auth_token(){
| > > > > > > | | > > > | | | | | | | 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 |
**
** Must be called once before login_check_credentials() is called or
** we will not be able to replace fossil's internal idea of the auth
** info in time (and future changes to that state may cause unexpected
** results).
**
** The result of this call are cached for future calls.
**
** Special case: if g.useLocalauth is true (i.e. the --localauth flag
** was used to start the fossil server instance) and the current
** connection is "local", any authToken provided by the user is
** ignored and no new token is created: localauth mode trumps the
** authToken.
*/
cson_value * json_auth_token(){
assert(g.json.gc.a && "json_main_bootstrap() was not called!");
if( g.json.authToken==0 && g.noPswd==0
/* g.noPswd!=0 means the user was logged in via a local
connection using --localauth. */
){
/* Try to get an authorization token from GET parameter, POSTed
JSON, or fossil cookie (in that order). */
g.json.authToken = json_getenv(FossilJsonKeys.authToken);
if(g.json.authToken
&& cson_value_is_string(g.json.authToken)
&& !PD(login_cookie_name(),NULL)){
/* tell fossil to use this login info.
FIXME?: because the JSON bits don't carry around
login_cookie_name(), there is(?) a potential(?) login hijacking
window here. We may need to change the JSON auth token to be in
the form: login_cookie_name()=...
Then again, the hardened cookie value helps ensure that
only a proper key/value match is valid.
*/
cgi_replace_parameter( login_cookie_name(), cson_value_get_cstr(g.json.authToken) );
}else if( g.isHTTP ){
/* try fossil's conventional cookie. */
/* Reminder: chicken/egg scenario regarding db access in CLI
mode because login_cookie_name() needs the db. CLI
mode does not use any authentication, so we don't need
|
| ︙ | ︙ | |||
680 681 682 683 684 685 686 687 688 689 690 691 692 693 |
** caller.
*/
cson_value * json_req_payload_get(char const *pKey){
return g.json.reqPayload.o
? cson_object_get(g.json.reqPayload.o,pKey)
: NULL;
}
/*
** Initializes some JSON bits which need to be initialized relatively
** early on. It should only be called from cgi_init() or
** json_cmd_top() (early on in those functions).
**
** Initializes g.json.gc and g.json.param. This code does not (and
| > > > > > > > > > > > | 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 |
** caller.
*/
cson_value * json_req_payload_get(char const *pKey){
return g.json.reqPayload.o
? cson_object_get(g.json.reqPayload.o,pKey)
: NULL;
}
/*
** Returns non-zero if the json_main_bootstrap() function has already
** been called. In general, this function should be used sparingly,
** e.g. from low-level support functions like fossil_warning() where
** there is genuine uncertainty about whether (or not) the JSON setup
** has already been called.
*/
int json_is_main_boostrapped(){
return ((g.json.gc.v != NULL) && (g.json.gc.a != NULL));
}
/*
** Initializes some JSON bits which need to be initialized relatively
** early on. It should only be called from cgi_init() or
** json_cmd_top() (early on in those functions).
**
** Initializes g.json.gc and g.json.param. This code does not (and
|
| ︙ | ︙ | |||
904 905 906 907 908 909 910 | ** tested this) die with an error if an auth cookie is malformed. ** ** This must be called by the top-level JSON command dispatching code ** before they do any work. ** ** This must only be called once, or an assertion may be triggered. */ | | | > | 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 |
** tested this) die with an error if an auth cookie is malformed.
**
** This must be called by the top-level JSON command dispatching code
** before they do any work.
**
** This must only be called once, or an assertion may be triggered.
*/
void json_mode_bootstrap(){
static char once = 0 /* guard against multiple runs */;
char const * zPath = P("PATH_INFO");
assert(g.json.gc.a && "json_main_bootstrap() was not called!");
assert( (0==once) && "json_mode_bootstrap() called too many times!");
if( once ){
return;
}else{
once = 1;
}
assert(g.json.isJsonMode
&& "g.json.isJsonMode should have been set up by now.");
g.json.resultCode = 0;
g.json.cmd.offset = -1;
g.json.jsonp = PD("jsonp",NULL)
/* FIXME: do some sanity checking on g.json.jsonp and ignore it
if it is not halfway reasonable.
*/
;
|
| ︙ | ︙ | |||
994 995 996 997 998 999 1000 |
break;
}
inFile = (0==strcmp("-",jfile))
? stdin
: fossil_fopen(jfile,"rb");
if(!inFile){
g.json.resultCode = FSL_JSON_E_FILE_OPEN_FAILED;
| | < | < | 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 |
break;
}
inFile = (0==strcmp("-",jfile))
? stdin
: fossil_fopen(jfile,"rb");
if(!inFile){
g.json.resultCode = FSL_JSON_E_FILE_OPEN_FAILED;
fossil_fatal("Could not open JSON file [%s].",jfile)
/* Does not return. */
;
}
cgi_parse_POST_JSON(inFile, 0);
fossil_fclose(inFile);
break;
}
/* g.json.reqPayload exists only to simplify some of our access to
the request payload. We currently only use this in the context of
Object payloads, not Arrays, strings, etc.
*/
|
| ︙ | ︙ | |||
1099 1100 1101 1102 1103 1104 1105 |
short i = 0;
#define NEXT cson_string_cstr( \
cson_value_get_string( \
cson_array_get(ar,i) \
))
char const * tok = NEXT;
while( tok ){
| | < | > | 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 |
short i = 0;
#define NEXT cson_string_cstr( \
cson_value_get_string( \
cson_array_get(ar,i) \
))
char const * tok = NEXT;
while( tok ){
if( g.isHTTP/*workaround for "abbreviated name" in CLI mode*/
? (0==strncmp("json",tok,4))
: (0==strcmp(g.argv[1],tok))
){
g.json.cmd.offset = i;
break;
}
++i;
tok = NEXT;
}
|
| ︙ | ︙ | |||
1372 1373 1374 1375 1376 1377 1378 | cson_object * o = NULL; int rc; resultCode = json_dumbdown_rc(resultCode ? resultCode : g.json.resultCode); o = cson_new_object(); v = cson_object_value(o); if( ! o ) return NULL; #define SET(K) if(!tmp) goto cleanup; \ | > | < | > | | < | 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 |
cson_object * o = NULL;
int rc;
resultCode = json_dumbdown_rc(resultCode ? resultCode : g.json.resultCode);
o = cson_new_object();
v = cson_object_value(o);
if( ! o ) return NULL;
#define SET(K) if(!tmp) goto cleanup; \
cson_value_add_reference(tmp); \
rc = cson_object_set( o, K, tmp ); \
cson_value_free(tmp); \
if(rc) do{ \
tmp = NULL; \
goto cleanup; \
}while(0)
tmp = json_new_string(MANIFEST_UUID);
SET("fossil");
tmp = json_new_timestamp(-1);
SET(FossilJsonKeys.timestamp);
|
| ︙ | ︙ | |||
1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 |
}
if(g.json.cmd.commandStr){
tmp = json_new_string(g.json.cmd.commandStr);
}else{
tmp = json_response_command_path();
}
SET("command");
tmp = json_getenv(FossilJsonKeys.requestId);
if( tmp ) cson_object_set( o, FossilJsonKeys.requestId, tmp );
if(0){/* these are only intended for my own testing...*/
if(g.json.cmd.v){
tmp = g.json.cmd.v;
SET("$commandPath");
}
if(g.json.param.v){
tmp = g.json.param.v;
SET("$params");
}
if(0){/*Only for debugging, add some info to the response.*/
tmp = cson_value_new_integer( g.json.cmd.offset );
| > > > | | > < > | < > | 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 |
}
if(g.json.cmd.commandStr){
tmp = json_new_string(g.json.cmd.commandStr);
}else{
tmp = json_response_command_path();
}
if(!tmp){
tmp = json_new_string("???");
}
SET("command");
tmp = json_getenv(FossilJsonKeys.requestId);
if( tmp ) cson_object_set( o, FossilJsonKeys.requestId, tmp );
if(0){/* these are only intended for my own testing...*/
if(g.json.cmd.v){
tmp = g.json.cmd.v;
SET("$commandPath");
}
if(g.json.param.v){
tmp = g.json.param.v;
SET("$params");
}
if(0){/*Only for debugging, add some info to the response.*/
tmp = cson_value_new_integer( g.json.cmd.offset );
SET("cmd.offset");
tmp = cson_value_new_bool( g.isHTTP );
SET("isCGI");
}
}
if(fossil_timer_is_active(g.json.timerId)){
/* This is, philosophically speaking, not quite the right place
for ending the timer, but this is the one function which all of
the JSON exit paths use (and they call it after processing,
just before they end).
*/
sqlite3_uint64 span = fossil_timer_stop(g.json.timerId);
/* I'm actually seeing sub-uSec runtimes in some tests, but a time of
0 is "just kinda wrong".
*/
cson_object_set(o,"procTimeUs", cson_value_new_integer((cson_int_t)span));
span /= 1000/*for milliseconds */;
cson_object_set(o,"procTimeMs", cson_value_new_integer((cson_int_t)span));
assert(!fossil_timer_is_active(g.json.timerId));
g.json.timerId = -1;
}
if(g.json.warnings){
tmp = cson_array_value(g.json.warnings);
SET("warnings");
}
/* Only add the payload to SUCCESS responses. Else delete it. */
if( NULL != payload ){
if( resultCode ){
cson_value_free(payload);
payload = NULL;
}else{
tmp = payload;
SET(FossilJsonKeys.payload);
}
}
if((g.perm.Admin||g.perm.Setup)
&& json_find_option_bool("debugFossilG","json-debug-g",NULL,0)
){
tmp = json_g_to_json();
SET("g");
}
#undef SET
goto ok;
cleanup:
|
| ︙ | ︙ | |||
1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 |
** is NULL then json_err_cstr(code) is used.
*/
void json_err( int code, char const * msg, int alsoOutput ){
int rc = code ? code : (g.json.resultCode
? g.json.resultCode
: FSL_JSON_E_UNKNOWN);
cson_value * resp = NULL;
rc = json_dumbdown_rc(rc);
if( rc && !msg ){
msg = g.zErrMsg;
if(!msg){
msg = json_err_cstr(rc);
}
}
resp = json_create_response(rc, msg, NULL);
if(!resp){
/* about the only error case here is out-of-memory. DO NOT
| > | > | 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 |
** is NULL then json_err_cstr(code) is used.
*/
void json_err( int code, char const * msg, int alsoOutput ){
int rc = code ? code : (g.json.resultCode
? g.json.resultCode
: FSL_JSON_E_UNKNOWN);
cson_value * resp = NULL;
if(g.json.isJsonMode==0) return;
rc = json_dumbdown_rc(rc);
if( rc && !msg ){
msg = g.zErrMsg;
if(!msg){
msg = json_err_cstr(rc);
}
}
resp = json_create_response(rc, msg, NULL);
if(!resp){
/* about the only error case here is out-of-memory. DO NOT
call fossil_panic() or fossil_fatal() here because those
allocate.
*/
fprintf(stderr, "%s: Fatal error: could not allocate "
"response object.\n", g.argv[0]);
fossil_exit(1);
}
if( g.isHTTP ){
if(alsoOutput){
|
| ︙ | ︙ | |||
1866 1867 1868 1869 1870 1871 1872 | db_finalize(&q); cson_object_set( obj, "permissionFlags", sub ); obj = cson_value_get_object(sub); #define ADD(X,K) cson_object_set(obj, K, cson_value_new_bool(g.perm.X)) ADD(Setup,"setup"); ADD(Admin,"admin"); | < | 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 | db_finalize(&q); cson_object_set( obj, "permissionFlags", sub ); obj = cson_value_get_object(sub); #define ADD(X,K) cson_object_set(obj, K, cson_value_new_bool(g.perm.X)) ADD(Setup,"setup"); ADD(Admin,"admin"); ADD(Password,"password"); ADD(Query,"query"); /* don't think this one is actually used */ ADD(Write,"checkin"); ADD(Read,"checkout"); ADD(Hyperlink,"history"); ADD(Clone,"clone"); ADD(RdWiki,"readWiki"); |
| ︙ | ︙ | |||
2231 2232 2233 2234 2235 2236 2237 |
** Pages under /json/... must be entered into JsonPageDefs.
** This function dispatches them, and is the HTTP equivalent of
** json_cmd_top().
*/
void json_page_top(void){
char const * zCommand;
assert(g.json.gc.a && "json_main_bootstrap() was not called!");
| | | 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 |
** Pages under /json/... must be entered into JsonPageDefs.
** This function dispatches them, and is the HTTP equivalent of
** json_cmd_top().
*/
void json_page_top(void){
char const * zCommand;
assert(g.json.gc.a && "json_main_bootstrap() was not called!");
assert(g.json.cmd.a && "json_mode_bootstrap() was not called!");
zCommand = json_command_arg(1);
if(!zCommand || !*zCommand){
json_dispatch_missing_args_err( JsonPageDefs,
"No command (sub-path) specified."
" Try one of: ");
return;
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
228 229 230 231 232 233 234 |
/*
** Internal mapping of /json/artifact/FOO commands/callbacks.
*/
static ArtifactDispatchEntry ArtifactDispatchList[] = {
{"checkin", json_artifact_ci},
{"file", json_artifact_file},
| | > | 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
/*
** Internal mapping of /json/artifact/FOO commands/callbacks.
*/
static ArtifactDispatchEntry ArtifactDispatchList[] = {
{"checkin", json_artifact_ci},
{"file", json_artifact_file},
/*{"tag", NULL}, //impl missing */
/*{"technote", NULL}, //impl missing */
{"ticket", json_artifact_ticket},
{"wiki", json_artifact_wiki},
/* Final entry MUST have a NULL name. */
{NULL,NULL}
};
/*
|
| ︙ | ︙ | |||
474 475 476 477 478 479 480 481 482 483 484 485 486 487 |
for( ; dispatcher->name; ++dispatcher ){
if(0!=fossil_strcmp(dispatcher->name, zType)){
continue;
}else{
entry = (*dispatcher->func)(pay, rid);
break;
}
}
if(!g.json.resultCode){
assert( NULL != entry );
assert( NULL != zType );
cson_object_set( pay, "type", json_new_string(zType) );
cson_object_set( pay, "uuid", json_new_string(zUuid) );
/*cson_object_set( pay, "name", json_new_string(zName ? zName : zUuid) );*/
| > > > > > > > > | 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 |
for( ; dispatcher->name; ++dispatcher ){
if(0!=fossil_strcmp(dispatcher->name, zType)){
continue;
}else{
entry = (*dispatcher->func)(pay, rid);
break;
}
}
if(entry==0){
g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND
/* This is not quite right. We need a new result code
for this case. */;
g.zErrMsg = mprintf("Missing implementation for "
"artifacts of this type.");
goto error;
}
if(!g.json.resultCode){
assert( NULL != entry );
assert( NULL != zType );
cson_object_set( pay, "type", json_new_string(zType) );
cson_object_set( pay, "uuid", json_new_string(zUuid) );
/*cson_object_set( pay, "name", json_new_string(zName ? zName : zUuid) );*/
|
| ︙ | ︙ |
| ︙ | ︙ | |||
77 78 79 80 81 82 83 |
return NULL;
}
payV = cson_value_new_object();
pay = cson_value_get_object(payV);
listV = cson_value_new_array();
list = cson_value_get_array(listV);
if(fossil_has_json()){
| | | 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
return NULL;
}
payV = cson_value_new_object();
pay = cson_value_get_object(payV);
listV = cson_value_new_array();
list = cson_value_get_array(listV);
if(fossil_has_json()){
range = json_getenv_cstr("range");
}
range = json_find_option_cstr("range",NULL,"r");
if((!range||!*range) && !g.isHTTP){
range = find_option("all","a",0);
if(range && *range){
range = "a";
|
| ︙ | ︙ | |||
264 265 266 267 268 269 270 |
if( content_is_private(rootid) ) zOpt->isPrivate = 1;
if( zOpt->isPrivate && zColor==0 ) zColor = "#fec084";
if( zColor!=0 ){
blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
}
blob_appendf(&branch, "T *branch * %F\n", zBranch);
blob_appendf(&branch, "T *sym-%F *\n", zBranch);
| < < < | | 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 |
if( content_is_private(rootid) ) zOpt->isPrivate = 1;
if( zOpt->isPrivate && zColor==0 ) zColor = "#fec084";
if( zColor!=0 ){
blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
}
blob_appendf(&branch, "T *branch * %F\n", zBranch);
blob_appendf(&branch, "T *sym-%F *\n", zBranch);
/* Cancel all other symbolic tags */
db_prepare(&q,
"SELECT tagname FROM tagxref, tag"
" WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
" AND tagtype>0 AND tagname GLOB 'sym-*'"
" ORDER BY tagname",
rootid);
while( db_step(&q)==SQLITE_ROW ){
const char *zTag = db_column_text(&q, 0);
blob_appendf(&branch, "T -%F *\n", zTag);
}
db_finalize(&q);
blob_appendf(&branch, "U %F\n", g.zLogin);
md5sum_blob(&branch, &mcksum);
blob_appendf(&branch, "Z %b\n", &mcksum);
brid = content_put_ex(&branch, 0, 0, 0, zOpt->isPrivate);
if( brid==0 ){
fossil_panic("Problem committing manifest: %s", g.zErrMsg);
}
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
if( manifest_crosslink(brid, &branch, MC_PERMIT_HOOKS)==0 ){
fossil_panic("%s", g.zErrMsg);
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
87 88 89 90 91 92 93 |
if( zCheckin && *zCheckin ){
char * zU = NULL;
int rc = name_to_uuid2( zCheckin, "ci", &zU );
/*printf("zCheckin=[%s], zU=[%s]", zCheckin, zU);*/
if(rc<=0){
json_set_err((rc<0) ? FSL_JSON_E_AMBIGUOUS_UUID : FSL_JSON_E_RESOURCE_NOT_FOUND,
| | | 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
if( zCheckin && *zCheckin ){
char * zU = NULL;
int rc = name_to_uuid2( zCheckin, "ci", &zU );
/*printf("zCheckin=[%s], zU=[%s]", zCheckin, zU);*/
if(rc<=0){
json_set_err((rc<0) ? FSL_JSON_E_AMBIGUOUS_UUID : FSL_JSON_E_RESOURCE_NOT_FOUND,
"Check-in hash %s.", (rc<0) ? "is ambiguous" : "not found");
blob_reset(&sql);
return NULL;
}
blob_append_sql(&sql, " AND ci.uuid='%q'", zU);
free(zU);
}else{
if( zAfter && *zAfter ){
|
| ︙ | ︙ |
| ︙ | ︙ | |||
41 42 43 44 45 46 47 |
;
/*
FIXME: we want to check the GET/POST args in this order:
- GET: name, n, password, p
- POST: name, password
| | | | | < | | | 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
;
/*
FIXME: we want to check the GET/POST args in this order:
- GET: name, n, password, p
- POST: name, password
but fossil's age-old behaviour of treating the last element of
PATH_INFO as the value for the name parameter breaks that.
Summary: If we check for P("name") first, then P("n"), then ONLY a
GET param of "name" will match ("n" is not recognized). If we
reverse the order of the checks then both forms work. The
"p"/"password" check is not affected by this.
*/
char const * name = cson_value_get_cstr(json_req_payload_get("name"));
char const * pw = NULL;
char const * anonSeed = NULL;
cson_value * payload = NULL;
int uid = 0;
/* reminder to self: Fossil internally (for the sake of /wiki)
interprets paths in the form /foo/bar/baz such that P("name") ==
|
| ︙ | ︙ | |||
229 230 231 232 233 234 235 |
/*
** Implements the /json/whoami page/command.
*/
cson_value * json_page_whoami(){
cson_value * payload = NULL;
cson_object * obj = NULL;
Stmt q;
| | | 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
/*
** Implements the /json/whoami page/command.
*/
cson_value * json_page_whoami(){
cson_value * payload = NULL;
cson_object * obj = NULL;
Stmt q;
if(!g.json.authToken && g.userUid==0){
/* assume we just logged out. */
db_prepare(&q, "SELECT login, cap FROM user WHERE login='nobody'");
}
else{
db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d",
g.userUid);
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
115 116 117 118 119 120 121 |
cson_object_set(pay, "raw", cson_value_new_bool(fRaw));
{
Blob uu = empty_blob;
int rc;
blob_append(&uu, zName, -1);
rc = name_to_uuid(&uu, 9, "*");
if(0!=rc){
| | | 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
cson_object_set(pay, "raw", cson_value_new_bool(fRaw));
{
Blob uu = empty_blob;
int rc;
blob_append(&uu, zName, -1);
rc = name_to_uuid(&uu, 9, "*");
if(0!=rc){
json_set_err(FSL_JSON_E_UNKNOWN,"Could not convert name back to artifact hash!");
blob_reset(&uu);
goto error;
}
cson_object_set(pay, "appliedTo", json_new_string(blob_buffer(&uu)));
blob_reset(&uu);
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
33 34 35 36 37 38 39 |
static const JsonPageDef JsonPageDefs_Timeline[] = {
/* the short forms are only enabled in CLI mode, to avoid
that we end up with HTTP clients using 3 different names
for the same requests.
*/
{"branch", json_timeline_branch, 0},
{"checkin", json_timeline_ci, 0},
| > | | 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
static const JsonPageDef JsonPageDefs_Timeline[] = {
/* the short forms are only enabled in CLI mode, to avoid
that we end up with HTTP clients using 3 different names
for the same requests.
*/
{"branch", json_timeline_branch, 0},
{"checkin", json_timeline_ci, 0},
{"event" /* old name for technotes */, json_timeline_event, 0},
{"technote", json_timeline_event, 0},
{"ticket", json_timeline_ticket, 0},
{"wiki", json_timeline_wiki, 0},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};
|
| ︙ | ︙ |
| ︙ | ︙ | |||
150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
"EXISTS(SELECT 1 FROM tagxref AS tx"
" WHERE tx.rid=%s"
" AND tx.tagid=%d"
" AND tx.tagtype>0)",
zVar, TAG_CLOSED
);
}
/*
** Schedule a leaf check for "rid" and its parents.
*/
void leaf_eventually_check(int rid){
static Stmt parentsOf;
| > > > > > > > > > > > | 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 |
"EXISTS(SELECT 1 FROM tagxref AS tx"
" WHERE tx.rid=%s"
" AND tx.tagid=%d"
" AND tx.tagtype>0)",
zVar, TAG_CLOSED
);
}
/*
** Returns true if vid refers to a closed leaf, else false. vid is
** assumed to refer to a manifest, but this function does not verify
** that.
*/
int leaf_is_closed(int vid){
return db_exists("SELECT 1 FROM tagxref"
" WHERE tagid=%d AND rid=%d AND tagtype>0",
TAG_CLOSED, vid);
}
/*
** Schedule a leaf check for "rid" and its parents.
*/
void leaf_eventually_check(int rid){
static Stmt parentsOf;
|
| ︙ | ︙ |
| ︙ | ︙ | |||
47 48 49 50 51 52 53 |
}
/*
** Abort the current operation of the load average of the host computer
** is too high.
*/
void load_control(void){
| | | 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
}
/*
** Abort the current operation of the load average of the host computer
** is too high.
*/
void load_control(void){
double mxLoad = atof(db_get("max-loadavg", 0));
if( mxLoad<=0.0 || mxLoad>=load_average() ) return;
style_header("Server Overload");
@ <h2>The server load is currently too high.
@ Please try again later.</h2>
@ <p>Current load average: %f(load_average()).<br />
@ Load average limit: %f(mxLoad)</p>
|
| ︙ | ︙ |
| ︙ | ︙ | |||
109 110 111 112 113 114 115 |
if( zGoto ){
cgi_redirect(zGoto);
}else{
fossil_redirect_home();
}
}
| < < < < < < < < < < < < < < < < < < < < < < | 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
if( zGoto ){
cgi_redirect(zGoto);
}else{
fossil_redirect_home();
}
}
/*
** Return an abbreviated project code. The abbreviation is the first
** 16 characters of the project code.
**
** Memory is obtained from malloc.
*/
static char *abbreviated_project_code(const char *zFullCode){
|
| ︙ | ︙ | |||
293 294 295 296 297 298 299 |
){
const char *zCookieName = login_cookie_name();
const char *zExpire = db_get("cookie-expire","8766");
int expires = atoi(zExpire)*3600;
char *zHash;
char *zCookie;
const char *zIpAddr = PD("REMOTE_ADDR","nil"); /* IP address of user */
| < < | | | < | < < < < < < < < < | | | 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 |
){
const char *zCookieName = login_cookie_name();
const char *zExpire = db_get("cookie-expire","8766");
int expires = atoi(zExpire)*3600;
char *zHash;
char *zCookie;
const char *zIpAddr = PD("REMOTE_ADDR","nil"); /* IP address of user */
assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data.");
zHash = db_text(0,
"SELECT cookie FROM user"
" WHERE uid=%d"
" AND cexpire>julianday('now')"
" AND length(cookie)>30",
uid);
if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))");
zCookie = login_gen_user_cookie_value(zUsername, zHash);
cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
record_login_attempt(zUsername, zIpAddr, 1);
db_multi_exec(
"UPDATE user SET cookie=%Q,"
" cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
zHash, expires, uid
);
free(zHash);
if( zDest ){
*zDest = zCookie;
}else{
free(zCookie);
}
}
/* Sets a cookie for an anonymous user login, which looks like this:
**
** HASH/TIME/anonymous
**
** Where HASH is the sha1sum of TIME/SECRET, in which SECRET is captcha-secret.
**
** If zCookieDest is not NULL then the generated cookie is assigned to
** *zCookieDest and the caller must eventually free() it.
*/
void login_set_anon_cookie(const char *zIpAddr, char **zCookieDest ){
const char *zNow; /* Current time (julian day number) */
char *zCookie; /* The login cookie */
const char *zCookieName; /* Name of the login cookie */
Blob b; /* Blob used during cookie construction */
zCookieName = login_cookie_name();
zNow = db_text("0", "SELECT julianday('now')");
assert( zCookieName && zNow );
blob_init(&b, zNow, -1);
blob_appendf(&b, "/%s", db_get("captcha-secret",""));
sha1sum_blob(&b, &b);
zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
blob_reset(&b);
cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), 6*3600);
if( zCookieDest ){
*zCookieDest = zCookie;
}else{
|
| ︙ | ︙ | |||
436 437 438 439 440 441 442 443 444 445 446 447 448 449 |
if( sqlite3_strglob("*Firefox/[1-9]*", zAgent)==0 ) return 1;
if( sqlite3_strglob("*Chrome/[1-9]*", zAgent)==0 ) return 1;
if( sqlite3_strglob("*(compatible;?MSIE?[1789]*", zAgent)==0 ) return 1;
if( sqlite3_strglob("*Trident/[1-9]*;?rv:[1-9]*", zAgent)==0 ){
return 1; /* IE11+ */
}
if( sqlite3_strglob("*AppleWebKit/[1-9]*(KHTML*", zAgent)==0 ) return 1;
return 0;
}
if( strncmp(zAgent, "Opera/", 6)==0 ) return 1;
if( strncmp(zAgent, "Safari/", 7)==0 ) return 1;
if( strncmp(zAgent, "Lynx/", 5)==0 ) return 1;
if( strncmp(zAgent, "NetSurf/", 8)==0 ) return 1;
return 0;
| > | 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 |
if( sqlite3_strglob("*Firefox/[1-9]*", zAgent)==0 ) return 1;
if( sqlite3_strglob("*Chrome/[1-9]*", zAgent)==0 ) return 1;
if( sqlite3_strglob("*(compatible;?MSIE?[1789]*", zAgent)==0 ) return 1;
if( sqlite3_strglob("*Trident/[1-9]*;?rv:[1-9]*", zAgent)==0 ){
return 1; /* IE11+ */
}
if( sqlite3_strglob("*AppleWebKit/[1-9]*(KHTML*", zAgent)==0 ) return 1;
if( sqlite3_strglob("*PaleMoon/[1-9]*", zAgent)==0 ) return 1;
return 0;
}
if( strncmp(zAgent, "Opera/", 6)==0 ) return 1;
if( strncmp(zAgent, "Safari/", 7)==0 ) return 1;
if( strncmp(zAgent, "Lynx/", 5)==0 ) return 1;
if( strncmp(zAgent, "NetSurf/", 8)==0 ) return 1;
return 0;
|
| ︙ | ︙ | |||
512 513 514 515 516 517 518 |
** to self-registered users.
*/
int login_self_register_available(const char *zNeeded){
CapabilityString *pCap;
int rc;
if( !db_get_boolean("self-register",0) ) return 0;
if( zNeeded==0 ) return 1;
| | | 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 |
** to self-registered users.
*/
int login_self_register_available(const char *zNeeded){
CapabilityString *pCap;
int rc;
if( !db_get_boolean("self-register",0) ) return 0;
if( zNeeded==0 ) return 1;
pCap = capability_add(0, db_get("default-perms", "u"));
capability_expand(pCap);
rc = capability_has_any(pCap, zNeeded);
capability_free(pCap);
return rc;
}
/*
|
| ︙ | ︙ | |||
699 700 701 702 703 704 705 706 707 |
@ <input type="hidden" name="anon" value="1" />
}
if( g.zLogin ){
@ <p>Currently logged in as <b>%h(g.zLogin)</b>.
@ <input type="submit" name="out" value="Logout"></p>
@ </form>
}else{
@ <table class="login_out">
@ <tr>
| > > > > > > > > | < | < | < | > | > > > > < < < < < < | 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 |
@ <input type="hidden" name="anon" value="1" />
}
if( g.zLogin ){
@ <p>Currently logged in as <b>%h(g.zLogin)</b>.
@ <input type="submit" name="out" value="Logout"></p>
@ </form>
}else{
unsigned int uSeed = captcha_seed();
if( g.zLogin==0 && (anonFlag || zGoto==0) ){
zAnonPw = db_text(0, "SELECT pw FROM user"
" WHERE login='anonymous'"
" AND cap!=''");
}else{
zAnonPw = 0;
}
@ <table class="login_out">
@ <tr>
@ <td class="form_label" id="userlabel1">User ID:</td>
@ <td><input type="text" id="u" aria-labelledby="userlabel1" name="u" \
@ size="30" value="%s(anonFlag?"anonymous":"")"></td>
@ </tr>
@ <tr>
@ <td class="form_label" id="pswdlabel">Password:</td>
@ <td><input aria-labelledby="pswdlabel" type="password" id="p" \
@ name="p" value="" size="30" />\
if( zAnonPw && !noAnon ){
captcha_speakit_button(uSeed, "Speak password for \"anonymous\"");
}
@ </td>
@ </tr>
if( P("HTTPS")==0 ){
@ <tr><td class="form_label">Warning:</td>
@ <td><span class='securityWarning'>
@ Your password will be sent in the clear over an
@ unencrypted connection.
if( g.sslNotAvailable ){
@ No encrypted connection is available on this server.
}else{
@ Consider logging in at
@ <a href='%s(g.zHttpsURL)'>%h(g.zHttpsURL)</a> instead.
}
@ </span></td></tr>
}
@ <tr>
@ <td></td>
@ <td><input type="submit" name="in" value="Login"></td>
@ </tr>
if( !noAnon && login_self_register_available(0) ){
@ <tr>
@ <td></td>
@ <td><input type="submit" name="self" value="Create A New Account">
@ </tr>
}
@ </table>
if( zAnonPw && !noAnon ){
const char *zDecoded = captcha_decode(uSeed);
int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
char *zCaptcha = captcha_render(zDecoded);
@ <p><input type="hidden" name="cs" value="%u(uSeed)" />
@ Visitors may enter <b>anonymous</b> as the user-ID with
@ the 8-character hexadecimal password shown below:</p>
|
| ︙ | ︙ | |||
775 776 777 778 779 780 781 |
@ for user <b>%h(g.zLogin)</b></p>
}
if( g.perm.Password ){
@ <hr>
@ <p>Change Password for user <b>%h(g.zLogin)</b>:</p>
form_begin(0, "%R/login");
@ <table>
| | > | | > | | > | | < | 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 |
@ for user <b>%h(g.zLogin)</b></p>
}
if( g.perm.Password ){
@ <hr>
@ <p>Change Password for user <b>%h(g.zLogin)</b>:</p>
form_begin(0, "%R/login");
@ <table>
@ <tr><td class="form_label" id="oldpw">Old Password:</td>
@ <td><input aria-labelledby="oldpw" type="password" name="p" \
@ size="30"/></td></tr>
@ <tr><td class="form_label" id="newpw">New Password:</td>
@ <td><input aria-labelledby="newpw" type="password" name="n1" \
@ size="30" /></td></tr>
@ <tr><td class="form_label" id="reppw">Repeat New Password:</td>
@ <td><input aria-labledby="reppw" type="password" name="n2" \
@ size="30" /></td></tr>
@ <tr><td></td>
@ <td><input type="submit" value="Change Password" /></td></tr>
@ </table>
@ </form>
}
}
style_footer();
}
/*
** Attempt to find login credentials for user zLogin on a peer repository
** with project code zCode. Transfer those credentials to the local
** repository.
**
** Return true if a transfer was made and false if not.
*/
static int login_transfer_credentials(
const char *zLogin, /* Login we are looking for */
const char *zCode, /* Project code of peer repository */
const char *zHash /* HASH from login cookie HASH/CODE/LOGIN */
){
sqlite3 *pOther = 0; /* The other repository */
sqlite3_stmt *pStmt; /* Query against the other repository */
char *zSQL; /* SQL of the query against other repo */
char *zOtherRepo; /* Filename of the other repository */
int rc; /* Result code from SQLite library functions */
int nXfer = 0; /* Number of credentials transferred */
|
| ︙ | ︙ | |||
829 830 831 832 833 834 835 |
sqlite3_create_function(pOther,"now",0,SQLITE_UTF8,0,db_now_function,0,0);
sqlite3_create_function(pOther, "constant_time_cmp", 2, SQLITE_UTF8, 0,
constant_time_cmp_function, 0, 0);
sqlite3_busy_timeout(pOther, 5000);
zSQL = mprintf(
"SELECT cexpire FROM user"
" WHERE login=%Q"
| < | | | | 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 |
sqlite3_create_function(pOther,"now",0,SQLITE_UTF8,0,db_now_function,0,0);
sqlite3_create_function(pOther, "constant_time_cmp", 2, SQLITE_UTF8, 0,
constant_time_cmp_function, 0, 0);
sqlite3_busy_timeout(pOther, 5000);
zSQL = mprintf(
"SELECT cexpire FROM user"
" WHERE login=%Q"
" AND length(cap)>0"
" AND length(pw)>0"
" AND cexpire>julianday('now')"
" AND constant_time_cmp(cookie,%Q)=0",
zLogin, zHash
);
pStmt = 0;
rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
db_multi_exec(
"UPDATE user SET cookie=%Q, cexpire=%.17g"
" WHERE login=%Q",
zHash,
sqlite3_column_double(pStmt, 0), zLogin
);
nXfer++;
}
sqlite3_finalize(pStmt);
}
sqlite3_close(pOther);
|
| ︙ | ︙ | |||
866 867 868 869 870 871 872 | if( fossil_strcmp(zLogin, "nobody")==0 ) return 1; if( fossil_strcmp(zLogin, "developer")==0 ) return 1; if( fossil_strcmp(zLogin, "reader")==0 ) return 1; return 0; } /* | | | | < | < < | | 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 |
if( fossil_strcmp(zLogin, "nobody")==0 ) return 1;
if( fossil_strcmp(zLogin, "developer")==0 ) return 1;
if( fossil_strcmp(zLogin, "reader")==0 ) return 1;
return 0;
}
/*
** Lookup the uid for a non-built-in user with zLogin and zCookie.
** Return 0 if not found.
**
** Note that this only searches for logged-in entries with matching
** zCookie (db: user.cookie) entries.
*/
static int login_find_user(
const char *zLogin, /* User name */
const char *zCookie /* Login cookie value */
){
int uid;
if( login_is_special(zLogin) ) return 0;
uid = db_int(0,
"SELECT uid FROM user"
" WHERE login=%Q"
" AND cexpire>julianday('now')"
" AND length(cap)>0"
" AND length(pw)>0"
" AND constant_time_cmp(cookie,%Q)=0",
zLogin, zCookie
);
return uid;
}
/*
** Attempt to use Basic Authentication to establish the user. Return the
** (non-zero) uid if successful. Return 0 if it does not work.
|
| ︙ | ︙ | |||
961 962 963 964 965 966 967 |
** g.isHuman True if the user is human, not a spider or robot
**
*/
void login_check_credentials(void){
int uid = 0; /* User id */
const char *zCookie; /* Text of the login cookie */
const char *zIpAddr; /* Raw IP address of the requestor */
| < | | 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 |
** g.isHuman True if the user is human, not a spider or robot
**
*/
void login_check_credentials(void){
int uid = 0; /* User id */
const char *zCookie; /* Text of the login cookie */
const char *zIpAddr; /* Raw IP address of the requestor */
const char *zCap = 0; /* Capability string */
const char *zPublicPages = 0; /* GLOB patterns of public pages */
const char *zLogin = 0; /* Login user for credentials */
/* Only run this check once. */
if( g.userUid!=0 ) return;
sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
constant_time_cmp_function, 0, 0);
/* If the HTTP connection is coming over 127.0.0.1 and if
** local login is disabled and if we are using HTTP and not HTTPS,
** then there is no need to check user credentials.
**
** This feature allows the "fossil ui" command to give the user
** full access rights without having to log in.
*/
zIpAddr = PD("REMOTE_ADDR","nil");
if( ( cgi_is_loopback(zIpAddr)
|| (g.fSshClient & CGI_SSH_CLIENT)!=0 )
&& g.useLocalauth
&& db_get_int("localauth",0)==0
&& P("HTTPS")==0
){
if( g.localOpen ) zLogin = db_lget("default-user",0);
|
| ︙ | ︙ | |||
1028 1029 1030 1031 1032 1033 1034 |
/* Cookies of the form "HASH/TIME/anonymous". The TIME must not be
** too old and the sha1 hash of TIME/IPADDR/SECRET must match HASH.
** SECRET is the "captcha-secret" value in the repository.
*/
double rTime = atof(zArg);
Blob b;
blob_zero(&b);
| | < | | | | 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 |
/* Cookies of the form "HASH/TIME/anonymous". The TIME must not be
** too old and the sha1 hash of TIME/IPADDR/SECRET must match HASH.
** SECRET is the "captcha-secret" value in the repository.
*/
double rTime = atof(zArg);
Blob b;
blob_zero(&b);
blob_appendf(&b, "%s/%s", zArg, db_get("captcha-secret",""));
sha1sum_blob(&b, &b);
if( fossil_strcmp(zHash, blob_str(&b))==0 ){
uid = db_int(0,
"SELECT uid FROM user WHERE login='anonymous'"
" AND length(cap)>0"
" AND length(pw)>0"
" AND %.17g+0.25>julianday('now')",
rTime
);
}
blob_reset(&b);
}else{
/* Cookies of the form "HASH/CODE/USER". Search first in the
** local user table, then the user table for project CODE if we
** are part of a login-group.
*/
uid = login_find_user(zUser, zHash);
if( uid==0 && login_transfer_credentials(zUser,zArg,zHash) ){
uid = login_find_user(zUser, zHash);
if( uid ) record_login_attempt(zUser, zIpAddr, 1);
}
}
sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash);
}
/* If no user found and the REMOTE_USER environment variable is set,
|
| ︙ | ︙ | |||
1160 1161 1162 1163 1164 1165 1166 |
*/
zPublicPages = db_get("public-pages",0);
if( zPublicPages!=0 ){
Glob *pGlob = glob_create(zPublicPages);
const char *zUri = PD("REQUEST_URI","");
zUri += (int)strlen(g.zTop);
if( glob_match(pGlob, zUri) ){
| | | 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 |
*/
zPublicPages = db_get("public-pages",0);
if( zPublicPages!=0 ){
Glob *pGlob = glob_create(zPublicPages);
const char *zUri = PD("REQUEST_URI","");
zUri += (int)strlen(g.zTop);
if( glob_match(pGlob, zUri) ){
login_set_capabilities(db_get("default-perms", "u"), 0);
}
glob_free(pGlob);
}
}
/*
** Memory of settings
|
| ︙ | ︙ | |||
1224 1225 1226 1227 1228 1229 1230 |
switch( zCap[i] ){
case 's': p->Setup = 1; /* Fall thru into Admin */
case 'a': p->Admin = p->RdTkt = p->WrTkt = p->Zip =
p->RdWiki = p->WrWiki = p->NewWiki =
p->ApndWiki = p->Hyperlink = p->Clone =
p->NewTkt = p->Password = p->RdAddr =
p->TktFmt = p->Attach = p->ApndTkt =
| | | < < | 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 |
switch( zCap[i] ){
case 's': p->Setup = 1; /* Fall thru into Admin */
case 'a': p->Admin = p->RdTkt = p->WrTkt = p->Zip =
p->RdWiki = p->WrWiki = p->NewWiki =
p->ApndWiki = p->Hyperlink = p->Clone =
p->NewTkt = p->Password = p->RdAddr =
p->TktFmt = p->Attach = p->ApndTkt =
p->ModWiki = p->ModTkt =
p->RdForum = p->WrForum = p->ModForum =
p->WrTForum = p->AdminForum =
p->EmailAlert = p->Announce = p->Debug = 1;
/* Fall thru into Read/Write */
case 'i': p->Read = p->Write = 1; break;
case 'o': p->Read = 1; break;
case 'z': p->Zip = 1; break;
case 'h': p->Hyperlink = 1; break;
case 'g': p->Clone = 1; break;
case 'p': p->Password = 1; break;
case 'j': p->RdWiki = 1; break;
case 'k': p->WrWiki = p->RdWiki = p->ApndWiki =1; break;
case 'm': p->ApndWiki = 1; break;
|
| ︙ | ︙ | |||
1318 1319 1320 1321 1322 1323 1324 |
FossilUserPerms *p = (flgs & LOGIN_ANON) ? &g.anon : &g.perm;
if( nCap<0 ) nCap = strlen(zCap);
for(i=0; i<nCap && rc && zCap[i]; i++){
switch( zCap[i] ){
case 'a': rc = p->Admin; break;
case 'b': rc = p->Attach; break;
case 'c': rc = p->ApndTkt; break;
| | | 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 |
FossilUserPerms *p = (flgs & LOGIN_ANON) ? &g.anon : &g.perm;
if( nCap<0 ) nCap = strlen(zCap);
for(i=0; i<nCap && rc && zCap[i]; i++){
switch( zCap[i] ){
case 'a': rc = p->Admin; break;
case 'b': rc = p->Attach; break;
case 'c': rc = p->ApndTkt; break;
/* d unused: see comment in capabilities.c */
case 'e': rc = p->RdAddr; break;
case 'f': rc = p->NewWiki; break;
case 'g': rc = p->Clone; break;
case 'h': rc = p->Hyperlink; break;
case 'i': rc = p->Write; break;
case 'j': rc = p->RdWiki; break;
case 'k': rc = p->WrWiki; break;
|
| ︙ | ︙ | |||
1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 |
if( g.okCsrf ) return;
if( fossil_strcmp(P("csrf"), g.zCsrfToken)==0 ){
g.okCsrf = 1;
return;
}
fossil_fatal("Cross-site request forgery attempt");
}
/*
** WEBPAGE: register
**
** Page to allow users to self-register. The "self-register" setting
** must be enabled for this page to operate.
*/
void register_page(void){
const char *zUserID, *zPasswd, *zConfirm, *zEAddr;
const char *zDName;
unsigned int uSeed;
const char *zDecoded;
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < > > | | | | | > > > | > > > > > > > > | > > > | | | | > | 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 |
if( g.okCsrf ) return;
if( fossil_strcmp(P("csrf"), g.zCsrfToken)==0 ){
g.okCsrf = 1;
return;
}
fossil_fatal("Cross-site request forgery attempt");
}
/*
** Check to see if the candidate username zUserID is already used.
** Return 1 if it is already in use. Return 0 if the name is
** available for a self-registeration.
*/
static int login_self_choosen_userid_already_exists(const char *zUserID){
int rc = db_exists(
"SELECT 1 FROM user WHERE login=%Q "
"UNION ALL "
"SELECT 1 FROM event WHERE user=%Q OR euser=%Q",
zUserID, zUserID, zUserID
);
return rc;
}
/*
** Check an email address and confirm that it is valid for self-registration.
** The email address is known already to be well-formed. Return true
** if the email address is on the allowed list.
**
** The default behavior is that any valid email address is accepted.
** But if the "auth-sub-email" setting exists and is not empty, then
** it is a comma-separated list of GLOB patterns for email addresses
** that are authorized to self-register.
*/
int authorized_subscription_email(const char *zEAddr){
char *zGlob = db_get("auth-sub-email",0);
Glob *pGlob;
char *zAddr;
int rc;
if( zGlob==0 || zGlob[0]==0 ) return 1;
zGlob = fossil_strtolwr(fossil_strdup(zGlob));
pGlob = glob_create(zGlob);
fossil_free(zGlob);
zAddr = fossil_strtolwr(fossil_strdup(zEAddr));
rc = glob_match(pGlob, zAddr);
fossil_free(zAddr);
glob_free(pGlob);
return rc!=0;
}
/*
** WEBPAGE: register
**
** Page to allow users to self-register. The "self-register" setting
** must be enabled for this page to operate.
*/
void register_page(void){
const char *zUserID, *zPasswd, *zConfirm, *zEAddr;
const char *zDName;
unsigned int uSeed;
const char *zDecoded;
int iErrLine = -1;
const char *zErr = 0;
int captchaIsCorrect = 0; /* True on a correct captcha */
char *zCaptcha = ""; /* Value of the captcha text */
char *zPerms; /* Permissions for the default user */
int canDoAlerts = 0; /* True if receiving email alerts is possible */
int doAlerts = 0; /* True if subscription is wanted too */
if( !db_get_boolean("self-register", 0) ){
style_header("Registration not possible");
@ <p>This project does not allow user self-registration. Please contact the
@ project administrator to obtain an account.</p>
style_footer();
return;
}
zPerms = db_get("default-perms", "u");
/* Prompt the user for email alerts if this repository is configured for
** email alerts and if the default permissions include "7" */
canDoAlerts = alert_tables_exist() && db_int(0,
"SELECT fullcap(%Q) GLOB '*7*'", zPerms
);
doAlerts = canDoAlerts && atoi(PD("alerts","1"))!=0;
zUserID = PDT("u","");
zPasswd = PDT("p","");
zConfirm = PDT("cp","");
zEAddr = PDT("ea","");
zDName = PDT("dn","");
/* Verify user imputs */
if( P("new")==0 || !cgi_csrf_safe(1) ){
/* This is not a valid form submission. Fall through into
** the form display */
}else if( (captchaIsCorrect = captcha_is_correct(1))==0 ){
iErrLine = 6;
zErr = "Incorrect CAPTCHA";
}else if( strlen(zUserID)<6 ){
iErrLine = 1;
zErr = "User ID too short. Must be at least 6 characters.";
}else if( sqlite3_strglob("*[^-a-zA-Z0-9_.]*",zUserID)==0 ){
iErrLine = 1;
zErr = "User ID may not contain spaces or special characters.";
}else if( zDName[0]==0 ){
iErrLine = 2;
zErr = "Required";
}else if( zEAddr[0]==0 ){
iErrLine = 3;
zErr = "Required";
}else if( email_address_is_valid(zEAddr,0)==0 ){
iErrLine = 3;
zErr = "Not a valid email address";
}else if( authorized_subscription_email(zEAddr)==0 ){
iErrLine = 3;
zErr = "Not an authorized email address";
}else if( strlen(zPasswd)<6 ){
iErrLine = 4;
zErr = "Password must be at least 6 characters long";
}else if( fossil_strcmp(zPasswd,zConfirm)!=0 ){
iErrLine = 5;
zErr = "Passwords do not match";
}else if( login_self_choosen_userid_already_exists(zUserID) ){
iErrLine = 1;
zErr = "This User ID is already taken. Choose something different.";
}else if(
/* If the email is found anywhere in USER.INFO... */
db_exists("SELECT 1 FROM user WHERE info LIKE '%%%q%%'", zEAddr)
||
/* Or if the email is a verify subscriber email with an associated
** user... */
db_exists(
"SELECT 1 FROM subscriber WHERE semail=%Q AND suname IS NOT NULL"
" AND sverified",zEAddr)
){
iErrLine = 3;
zErr = "This email address is already claimed by another user";
}else{
/* If all of the tests above have passed, that means that the submitted
** form contains valid data and we can proceed to create the new login */
Blob sql;
int uid;
char *zPass = sha1_shared_secret(zPasswd, zUserID, 0);
const char *zStartPerms = zPerms;
if( db_get_boolean("selfreg-verify",0) ){
/* If email verification is required for self-registration, initalize
** the new user capabilities to just "7" (Sign up for email). The
** full "default-perms" permissions will be added when they click
** the verification link on the email they are sent. */
zStartPerms = "7";
}
blob_init(&sql, 0, 0);
blob_append_sql(&sql,
"INSERT INTO user(login,pw,cap,info,mtime)\n"
"VALUES(%Q,%Q,%Q,"
"'%q <%q>\nself-register from ip %q on '||datetime('now'),now())",
zUserID, zPass, zStartPerms, zDName, zEAddr, g.zIpAddr);
fossil_free(zPass);
db_multi_exec("%s", blob_sql_text(&sql));
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID);
login_set_user_cookie(zUserID, uid, NULL);
if( doAlerts ){
/* Also make the new user a subscriber. */
Blob hdr, body;
AlertSender *pSender;
sqlite3_int64 id; /* New subscriber Id */
const char *zCode; /* New subscriber code (in hex) */
const char *zGoto = P("g");
int nsub = 0;
char ssub[20];
CapabilityString *pCap;
pCap = capability_add(0, zPerms);
capability_expand(pCap);
ssub[nsub++] = 'a';
if( capability_has_any(pCap,"o") ) ssub[nsub++] = 'c';
if( capability_has_any(pCap,"2") ) ssub[nsub++] = 'f';
if( capability_has_any(pCap,"r") ) ssub[nsub++] = 't';
if( capability_has_any(pCap,"j") ) ssub[nsub++] = 'w';
ssub[nsub] = 0;
capability_free(pCap);
/* Also add the user to the subscriber table. */
db_multi_exec(
"INSERT INTO subscriber(semail,suname,"
" sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)"
" VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)"
" ON CONFLICT(semail) DO UPDATE"
" SET suname=excluded.suname",
|
| ︙ | ︙ | |||
1635 1636 1637 1638 1639 1640 1641 |
@ <p>The following internal error was encountered while trying
@ to send the confirmation email:
@ <blockquote><pre>
@ %h(pSender->zErr)
@ </pre></blockquote>
}else{
@ <p>An email has been sent to "%h(zEAddr)". That email contains a
| | < > > > | > | > | | > | | > | | | | > | | > | | | > > > | 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 |
@ <p>The following internal error was encountered while trying
@ to send the confirmation email:
@ <blockquote><pre>
@ %h(pSender->zErr)
@ </pre></blockquote>
}else{
@ <p>An email has been sent to "%h(zEAddr)". That email contains a
@ hyperlink that you must click to activate your account.</p>
}
alert_sender_free(pSender);
if( zGoto ){
@ <p><a href='%h(zGoto)'>Continue</a>
}
style_footer();
return;
}
redirect_to_g();
}
/* Prepare the captcha. */
if( captchaIsCorrect ){
uSeed = strtoul(P("captchaseed"),0,10);
}else{
uSeed = captcha_seed();
}
zDecoded = captcha_decode(uSeed);
zCaptcha = captcha_render(zDecoded);
style_header("Register");
/* Print out the registration form. */
form_begin(0, "%R/register");
if( P("g") ){
@ <input type="hidden" name="g" value="%h(P("g"))" />
}
@ <p><input type="hidden" name="captchaseed" value="%u(uSeed)" />
@ <table class="login_out">
@ <tr>
@ <td class="form_label" align="right" id="uid">User ID:</td>
@ <td><input aria-labelledby="uid" type="text" name="u" \
@ value="%h(zUserID)" size="30"></td>
@
if( iErrLine==1 ){
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr>
}
@ <tr>
@ <td class="form_label" align="right" id="dpyname">Display Name:</td>
@ <td><input aria-labelledby="dpyname" type="text" name="dn" \
@ value="%h(zDName)" size="30"></td>
@ </tr>
if( iErrLine==2 ){
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr>
}
@ </tr>
@ <tr>
@ <td class="form_label" align="right" id="emaddr">Email Address:</td>
@ <td><input aria-labelledby="emaddr" type="text" name="ea" \
@ value="%h(zEAddr)" size="30"></td>
@ </tr>
if( iErrLine==3 ){
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr>
}
if( canDoAlerts ){
int a = atoi(PD("alerts","1"));
@ <tr>
@ <td class="form_label" align="right" id="emalrt">Email Alerts?</td>
@ <td><select aria-labelledby="emalrt" size='1' name='alerts'>
@ <option value="1" %s(a?"selected":"")>Yes</option>
@ <option value="0" %s(!a?"selected":"")>No</option>
@ </select></td></tr>
}
@ <tr>
@ <td class="form_label" align="right" id="pswd">Password:</td>
@ <td><input aria-labelledby="pswd" type="password" name="p" \
@ value="%h(zPasswd)" size="30"></td>
@ <tr>
if( iErrLine==4 ){
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr>
}
@ <tr>
@ <td class="form_label" align="right" id="pwcfrm">Confirm:</td>
@ <td><input aria-labelledby="pwcfrm" type="password" name="cp" \
@ value="%h(zConfirm)" size="30"></td>
@ </tr>
if( iErrLine==5 ){
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr>
}
@ <tr>
@ <td class="form_label" align="right" id="cptcha">Captcha:</td>
@ <td><input type="text" name="captcha" aria-labelledby="cptcha" \
@ value="%h(captchaIsCorrect?zDecoded:"")" size="30">
captcha_speakit_button(uSeed, "Speak the captcha text");
@ </td>
@ </tr>
if( iErrLine==6 ){
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr>
}
@ <tr><td></td>
@ <td><input type="submit" name="new" value="Register" /></td></tr>
@ </table>
|
| ︙ | ︙ | |||
2016 2017 2018 2019 2020 2021 2022 |
nCmd = (int)strlen(zCmd);
if( strncmp(zCmd,"join",nCmd)==0 && nCmd>=1 ){
const char *zNewName = find_option("name",0,1);
const char *zOther;
char *zErr = 0;
verify_all_options();
if( g.argc!=4 ){
| | | 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 |
nCmd = (int)strlen(zCmd);
if( strncmp(zCmd,"join",nCmd)==0 && nCmd>=1 ){
const char *zNewName = find_option("name",0,1);
const char *zOther;
char *zErr = 0;
verify_all_options();
if( g.argc!=4 ){
fossil_fatal("unknown extra arguments to \"login-group join\"");
}
zOther = g.argv[3];
login_group_leave(&zErr);
sqlite3_free(zErr);
zErr = 0;
login_group_join(zOther,0,0,0,zNewName,&zErr);
if( zErr ){
|
| ︙ | ︙ | |||
2040 2041 2042 2043 2044 2045 2046 |
char *zErr = 0;
fossil_print("Leaving login-group \"%s\"\n", zLGName);
login_group_leave(&zErr);
if( zErr ) fossil_fatal("Oops: %s", zErr);
return;
}
}else{
| | | 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 |
char *zErr = 0;
fossil_print("Leaving login-group \"%s\"\n", zLGName);
login_group_leave(&zErr);
if( zErr ) fossil_fatal("Oops: %s", zErr);
return;
}
}else{
fossil_fatal("unknown command \"%s\" - should be \"join\" or \"leave\"",
zCmd);
}
}
/* Show the current login group information */
zLGName = login_group_name();
if( zLGName==0 ){
fossil_print("Not currently a part of any login-group\n");
|
| ︙ | ︙ |
| ︙ | ︙ | |||
361 362 363 364 365 366 367 |
** clause D98 of conformance (section 3.10) of the Unicode standard.
*/
int starts_with_utf16_bom(
const Blob *pContent, /* IN: Blob content to perform BOM detection on. */
int *pnByte, /* OUT: The number of bytes used for the BOM. */
int *pbReverse /* OUT: Non-zero for BOM in reverse byte-order. */
){
| | > | > | | | 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 |
** clause D98 of conformance (section 3.10) of the Unicode standard.
*/
int starts_with_utf16_bom(
const Blob *pContent, /* IN: Blob content to perform BOM detection on. */
int *pnByte, /* OUT: The number of bytes used for the BOM. */
int *pbReverse /* OUT: Non-zero for BOM in reverse byte-order. */
){
const unsigned char *z = (unsigned char *)blob_buffer(pContent);
int bomSize = sizeof(unsigned short);
int size = blob_size(pContent);
unsigned short i0;
if( size<bomSize ) goto noBom; /* No: cannot read BOM. */
if( size>=(2*bomSize) && z[2]==0 && z[3]==0 ) goto noBom;
memcpy(&i0, z, sizeof(i0));
if( i0==0xfeff ){
if( pbReverse ) *pbReverse = 0;
}else if( i0==0xfffe ){
if( pbReverse ) *pbReverse = 1;
}else{
static const int one = 1;
noBom:
if( pbReverse ) *pbReverse = *(char *) &one;
return 0; /* No: UTF-16 byte-order-mark not found. */
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# include "cson_amalgamation.h" /* JSON API. */
# include "json_detail.h"
#endif
#ifdef HAVE_BACKTRACE
# include <execinfo.h>
#endif
/*
** Maximum number of auxiliary parameters on reports
*/
#define MX_AUX 5
/*
** Holds flags for fossil user permissions.
*/
struct FossilUserPerms {
char Setup; /* s: use Setup screens on web interface */
char Admin; /* a: administrative permission */
| > > > > > > > > > < | 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 |
# include "cson_amalgamation.h" /* JSON API. */
# include "json_detail.h"
#endif
#ifdef HAVE_BACKTRACE
# include <execinfo.h>
#endif
/*
** Default length of a timeout for serving an HTTP request. Changable
** using the "--timeout N" command-line option or via "timeout: N" in the
** CGI script.
*/
#ifndef FOSSIL_DEFAULT_TIMEOUT
# define FOSSIL_DEFAULT_TIMEOUT 600 /* 10 minutes */
#endif
/*
** Maximum number of auxiliary parameters on reports
*/
#define MX_AUX 5
/*
** Holds flags for fossil user permissions.
*/
struct FossilUserPerms {
char Setup; /* s: use Setup screens on web interface */
char Admin; /* a: administrative permission */
char Password; /* p: change password */
char Query; /* q: create new reports */
char Write; /* i: xfer inbound. check-in */
char Read; /* o: xfer outbound. check-out */
char Hyperlink; /* h: enable the display of hyperlinks */
char Clone; /* g: clone */
char RdWiki; /* j: view wiki via web */
|
| ︙ | ︙ | |||
94 95 96 97 98 99 100 | char Zip; /* z: download zipped artifact via /zip URL */ char Private; /* x: can send and receive private content */ char WrUnver; /* y: can push unversioned content */ char RdForum; /* 2: Read forum posts */ char WrForum; /* 3: Create new forum posts */ char WrTForum; /* 4: Post to forums not subject to moderation */ char ModForum; /* 5: Moderate (approve or reject) forum posts */ | | | 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | char Zip; /* z: download zipped artifact via /zip URL */ char Private; /* x: can send and receive private content */ char WrUnver; /* y: can push unversioned content */ char RdForum; /* 2: Read forum posts */ char WrForum; /* 3: Create new forum posts */ char WrTForum; /* 4: Post to forums not subject to moderation */ char ModForum; /* 5: Moderate (approve or reject) forum posts */ char AdminForum; /* 6: Grant capability 4 to other users */ char EmailAlert; /* 7: Sign up for email notifications */ char Announce; /* A: Send announcements */ char Debug; /* D: show extra Fossil debugging features */ /* These last two are included to block infinite recursion */ char XReader; /* u: Inherit all privileges of "reader" */ char XDeveloper; /* v: Inherit all privileges of "developer" */ }; |
| ︙ | ︙ | |||
138 139 140 141 142 143 144 | const char *zErrlog; /* Log errors to this file, if not NULL */ int isConst; /* True if the output is unchanging & cacheable */ const char *zVfsName; /* The VFS to use for database connections */ sqlite3 *db; /* The connection to the databases */ sqlite3 *dbConfig; /* Separate connection for global_config table */ char *zAuxSchema; /* Main repository aux-schema */ int dbIgnoreErrors; /* Ignore database errors if true */ | | | | 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 | const char *zErrlog; /* Log errors to this file, if not NULL */ int isConst; /* True if the output is unchanging & cacheable */ const char *zVfsName; /* The VFS to use for database connections */ sqlite3 *db; /* The connection to the databases */ sqlite3 *dbConfig; /* Separate connection for global_config table */ char *zAuxSchema; /* Main repository aux-schema */ int dbIgnoreErrors; /* Ignore database errors if true */ char *zConfigDbName; /* Path of the config database. NULL if not open */ sqlite3_int64 now; /* Seconds since 1970 */ int repositoryOpen; /* True if the main repository database is open */ unsigned iRepoDataVers; /* Initial data version for repository database */ char *zRepositoryOption; /* Most recent cached repository option value */ char *zRepositoryName; /* Name of the repository database file */ char *zLocalDbName; /* Name of the local database file */ char *zOpenRevision; /* Check-in version to use during database open */ const char *zCmdName; /* Name of the Fossil command currently running */ int localOpen; /* True if the local database is open */ char *zLocalRoot; /* The directory holding the local database */ int minPrefix; /* Number of digits needed for a distinct hash */ int eHashPolicy; /* Current hash policy. One of HPOLICY_* */ int fSqlTrace; /* True if --sqltrace flag is present */ int fSqlStats; /* True if --sqltrace or --sqlstats are present */ int fSqlPrint; /* True if --sqlprint flag is present */ int fCgiTrace; /* True if --cgitrace is enabled */ int fQuiet; /* True if -quiet flag is present */ int fJail; /* True if running with a chroot jail */ |
| ︙ | ︙ | |||
200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
char isHTTP; /* True if server/CGI modes, else assume CLI. */
char javascriptHyperlink; /* If true, set href= using script, not HTML */
Blob httpHeader; /* Complete text of the HTTP request header */
UrlData url; /* Information about current URL */
const char *zLogin; /* Login name. NULL or "" if not logged in. */
const char *zSSLIdentity; /* Value of --ssl-identity option, filename of
** SSL client identity */
int useLocalauth; /* No login required if from 127.0.0.1 */
int noPswd; /* Logged in without password (on 127.0.0.1) */
int userUid; /* Integer user id */
int isHuman; /* True if access by a human, not a spider or bot */
int comFmtFlags; /* Zero or more "COMMENT_PRINT_*" bit flags, should be
** accessed through get_comment_format(). */
| > > > > | 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
char isHTTP; /* True if server/CGI modes, else assume CLI. */
char javascriptHyperlink; /* If true, set href= using script, not HTML */
Blob httpHeader; /* Complete text of the HTTP request header */
UrlData url; /* Information about current URL */
const char *zLogin; /* Login name. NULL or "" if not logged in. */
const char *zSSLIdentity; /* Value of --ssl-identity option, filename of
** SSL client identity */
#if defined(_WIN32) && USE_SEE
const char *zPidKey; /* Saved value of the --usepidkey option. Only
* applicable when using SEE on Windows. */
#endif
int useLocalauth; /* No login required if from 127.0.0.1 */
int noPswd; /* Logged in without password (on 127.0.0.1) */
int userUid; /* Integer user id */
int isHuman; /* True if access by a human, not a spider or bot */
int comFmtFlags; /* Zero or more "COMMENT_PRINT_*" bit flags, should be
** accessed through get_comment_format(). */
|
| ︙ | ︙ | |||
256 257 258 259 260 261 262 |
int nRequest; /* Total # of HTTP request */
#ifdef FOSSIL_ENABLE_JSON
struct FossilJsonBits {
int isJsonMode; /* True if running in JSON mode, else
false. This changes how errors are
reported. In JSON mode we try to
always output JSON-form error
| | | > > > > | 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
int nRequest; /* Total # of HTTP request */
#ifdef FOSSIL_ENABLE_JSON
struct FossilJsonBits {
int isJsonMode; /* True if running in JSON mode, else
false. This changes how errors are
reported. In JSON mode we try to
always output JSON-form error
responses and always (in CGI mode)
exit() with code 0 to avoid an HTTP
500 error.
*/
int preserveRc; /* Do not convert error codes into 0.
* This is primarily intended for use
* by the test suite. */
int resultCode; /* used for passing back specific codes
** from /json callbacks. */
int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
cson_output_opt outOpt; /* formatting options for JSON mode. */
cson_value *authToken; /* authentication token */
const char *jsonp; /* Name of JSONP function wrapper. */
unsigned char dispatchDepth /* Tells JSON command dispatching
|
| ︙ | ︙ | |||
317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
Global g;
/*
** atexit() handler which frees up "some" of the resources
** used by fossil.
*/
static void fossil_atexit(void) {
#if USE_SEE
/*
** Zero, unlock, and free the saved database encryption key now.
*/
db_unsave_encryption_key();
#endif
#if defined(_WIN32) || defined(__BIONIC__)
| > > | 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 |
Global g;
/*
** atexit() handler which frees up "some" of the resources
** used by fossil.
*/
static void fossil_atexit(void) {
static int once = 0;
if( once++ ) return; /* Ensure that this routine only runs once */
#if USE_SEE
/*
** Zero, unlock, and free the saved database encryption key now.
*/
db_unsave_encryption_key();
#endif
#if defined(_WIN32) || defined(__BIONIC__)
|
| ︙ | ︙ | |||
351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
cson_value_free(g.json.gc.v);
memset(&g.json, 0, sizeof(g.json));
#endif
free(g.zErrMsg);
if(g.db){
db_close(0);
}
/*
** FIXME: The next two lines cannot always be enabled; however, they
** are very useful for tracking down TH1 memory leaks.
*/
if( fossil_getenv("TH1_DELETE_INTERP")!=0 ){
if( g.interp ){
Th_DeleteInterp(g.interp); g.interp = 0;
| > > > | 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
cson_value_free(g.json.gc.v);
memset(&g.json, 0, sizeof(g.json));
#endif
free(g.zErrMsg);
if(g.db){
db_close(0);
}
manifest_clear_cache();
content_clear_cache(1);
rebuild_clear_cache();
/*
** FIXME: The next two lines cannot always be enabled; however, they
** are very useful for tracking down TH1 memory leaks.
*/
if( fossil_getenv("TH1_DELETE_INTERP")!=0 ){
if( g.interp ){
Th_DeleteInterp(g.interp); g.interp = 0;
|
| ︙ | ︙ | |||
374 375 376 377 378 379 380 | ** (2) Read the file FILENAME ** (3) Use the contents of FILE to replace the two removed arguments: ** (a) Ignore blank lines in the file ** (b) Each non-empty line of the file is an argument, except ** (c) If the line begins with "-" and contains a space, it is broken ** into two arguments at the space. */ | | | 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 |
** (2) Read the file FILENAME
** (3) Use the contents of FILE to replace the two removed arguments:
** (a) Ignore blank lines in the file
** (b) Each non-empty line of the file is an argument, except
** (c) If the line begins with "-" and contains a space, it is broken
** into two arguments at the space.
*/
void expand_args_option(int argc, void *argv){
Blob file = empty_blob; /* Content of the file */
Blob line = empty_blob; /* One line of the file */
unsigned int nLine; /* Number of lines in the file*/
unsigned int i, j, k; /* Loop counters */
int n; /* Number of bytes in one line */
unsigned int nArg; /* Number of new arguments */
char *z; /* General use string pointer */
|
| ︙ | ︙ | |||
609 610 611 612 613 614 615 |
return 0;
}
}
/*
** This procedure runs first.
*/
| > > > | | | < | | > | | | > > > > > > | 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 |
return 0;
}
}
/*
** This procedure runs first.
*/
#if defined(FOSSIL_FUZZ)
/* Do not include a main() procedure when building for fuzz testing.
** libFuzzer will supply main(). */
#elif defined(_WIN32) && !defined(BROKEN_MINGW_CMDLINE)
int _dowildcard = -1; /* This turns on command-line globbing in MinGW-w64 */
int wmain(int argc, wchar_t **argv){ return fossil_main(argc,(char**)argv); }
#elif defined(_WIN32)
int _CRT_glob = 0x0001; /* See MinGW bug #2062 */
int main(int argc, char **argv){ return fossil_main(argc, argv); }
#else
int main(int argc, char **argv){ return fossil_main(argc, argv); }
#endif
/* All the work of main() is done by a separate procedure "fossil_main()".
** We have to break this out, because fossil_main() is sometimes called
** separately (by the "shell" command) but we do not want atwait() handlers
** being called by separate invocations of fossil_main().
*/
int fossil_main(int argc, char **argv){
const char *zCmdName = "unknown";
const CmdOrPage *pCmd = 0;
int rc;
#if !defined(_WIN32_WCE)
if( fossil_getenv("FOSSIL_BREAK") ){
if( isatty(0) && isatty(2) ){
|
| ︙ | ︙ | |||
641 642 643 644 645 646 647 |
raise(SIGTRAP);
#endif
}
}
#endif
fossil_limit_memory(1);
| | | | 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 |
raise(SIGTRAP);
#endif
}
}
#endif
fossil_limit_memory(1);
if( sqlite3_libversion_number()<3033000 ){
fossil_panic("Unsuitable SQLite version %s, must be at least 3.33.0",
sqlite3_libversion());
}
sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0);
memset(&g, 0, sizeof(g));
g.now = time(0);
g.httpHeader = empty_blob;
|
| ︙ | ︙ | |||
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 |
g.fSqlStats = find_option("sqlstats", 0, 0)!=0;
g.fSystemTrace = find_option("systemtrace", 0, 0)!=0;
g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
g.fCgiTrace = find_option("cgitrace", 0, 0)!=0;
g.fSshClient = 0;
g.zSshCmd = 0;
if( g.fSqlTrace ) g.fSqlStats = 1;
g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
#ifdef FOSSIL_ENABLE_TH1_HOOKS
g.fNoThHook = find_option("no-th-hook", 0, 0)!=0;
#endif
g.fAnyTrace = g.fSqlTrace|g.fSystemTrace|g.fSshTrace|
g.fHttpTrace|g.fCgiTrace;
g.zHttpAuth = 0;
g.zLogin = find_option("user", "U", 1);
g.zSSLIdentity = find_option("ssl-identity", 0, 1);
g.zErrlog = find_option("errorlog", 0, 1);
fossil_init_flags_from_options();
if( find_option("utc",0,0) ) g.fTimeFormat = 1;
if( find_option("localtime",0,0) ) g.fTimeFormat = 2;
if( zChdir && file_chdir(zChdir, 0) ){
fossil_fatal("unable to change directories to %s", zChdir);
}
if( find_option("help",0,0)!=0 ){
/* If --help is found anywhere on the command line, translate the command
* to "fossil help cmdname" where "cmdname" is the first argument that
* does not begin with a "-" character. If all arguments start with "-",
* translate to "fossil help argv[1] argv[2]...". */
int i, nNewArgc;
char **zNewArgv = fossil_malloc( sizeof(char*)*(g.argc+2) );
| > > > > > > > > > > > > > > > > > > > > > > > | 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 |
g.fSqlStats = find_option("sqlstats", 0, 0)!=0;
g.fSystemTrace = find_option("systemtrace", 0, 0)!=0;
g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
g.fCgiTrace = find_option("cgitrace", 0, 0)!=0;
g.fSshClient = 0;
g.zSshCmd = 0;
if( g.fSqlTrace ) g.fSqlStats = 1;
#ifdef FOSSIL_ENABLE_JSON
g.json.preserveRc = find_option("json-preserve-rc", 0, 0)!=0;
#endif
g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
#ifdef FOSSIL_ENABLE_TH1_HOOKS
g.fNoThHook = find_option("no-th-hook", 0, 0)!=0;
#endif
g.fAnyTrace = g.fSqlTrace|g.fSystemTrace|g.fSshTrace|
g.fHttpTrace|g.fCgiTrace;
g.zHttpAuth = 0;
g.zLogin = find_option("user", "U", 1);
g.zSSLIdentity = find_option("ssl-identity", 0, 1);
g.zErrlog = find_option("errorlog", 0, 1);
fossil_init_flags_from_options();
if( find_option("utc",0,0) ) g.fTimeFormat = 1;
if( find_option("localtime",0,0) ) g.fTimeFormat = 2;
if( zChdir && file_chdir(zChdir, 0) ){
fossil_fatal("unable to change directories to %s", zChdir);
}
#if defined(_WIN32) && USE_SEE
{
g.zPidKey = find_option("usepidkey",0,1);
if( g.zPidKey ){
DWORD processId = 0;
LPVOID pAddress = NULL;
SIZE_T nSize = 0;
parse_pid_key_value(g.zPidKey, &processId, &pAddress, &nSize);
db_read_saved_encryption_key_from_process(processId, pAddress, nSize);
}else{
const char *zSeeDbConfig = find_option("seedbcfg",0,1);
if( !zSeeDbConfig ){
zSeeDbConfig = fossil_getenv("FOSSIL_SEE_DB_CONFIG");
}
if( zSeeDbConfig ){
db_read_saved_encryption_key_from_process_via_th1(zSeeDbConfig);
}
}
}
#endif
if( find_option("help",0,0)!=0 ){
/* If --help is found anywhere on the command line, translate the command
* to "fossil help cmdname" where "cmdname" is the first argument that
* does not begin with a "-" character. If all arguments start with "-",
* translate to "fossil help argv[1] argv[2]...". */
int i, nNewArgc;
char **zNewArgv = fossil_malloc( sizeof(char*)*(g.argc+2) );
|
| ︙ | ︙ | |||
757 758 759 760 761 762 763 |
if( i==g.argc ){
for(i=1; i<g.argc; i++) zNewArgv[i+1] = g.argv[i];
nNewArgc = g.argc+1;
zNewArgv[i+1] = 0;
}
g.argc = nNewArgc;
g.argv = zNewArgv;
| > > > > > > > > > > | | 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 |
if( i==g.argc ){
for(i=1; i<g.argc; i++) zNewArgv[i+1] = g.argv[i];
nNewArgc = g.argc+1;
zNewArgv[i+1] = 0;
}
g.argc = nNewArgc;
g.argv = zNewArgv;
#if 0
}else if( g.argc==2 && file_is_repository(g.argv[1]) ){
char **zNewArgv = fossil_malloc( sizeof(char*)*4 );
zNewArgv[0] = g.argv[0];
zNewArgv[1] = "ui";
zNewArgv[2] = g.argv[1];
zNewArgv[3] = 0;
g.argc = 3;
g.argv = zNewArgv;
#endif
}
zCmdName = g.argv[1];
}
#ifndef _WIN32
/* There is a bug in stunnel4 in which it sometimes starts up client
** processes without first opening file descriptor 2 (standard error).
** If this happens, and a subsequent open() of a database returns file
** descriptor 2, and then an assert() fires and writes on fd 2, that
|
| ︙ | ︙ | |||
787 788 789 790 791 792 793 794 795 796 797 798 799 800 |
fossil_panic("file descriptor 2 is not open. (fd=%d, errno=%d)",
fd, x);
}
}
#endif
g.zCmdName = zCmdName;
rc = dispatch_name_search(zCmdName, CMDFLAG_COMMAND|CMDFLAG_PREFIX, &pCmd);
if( rc==1 ){
#ifdef FOSSIL_ENABLE_TH1_HOOKS
if( !g.isHTTP && !g.fNoThHook ){
rc = Th_CommandHook(zCmdName, 0);
}else{
rc = TH_OK;
}
| > > > > > > > > > > > > > > > | 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 |
fossil_panic("file descriptor 2 is not open. (fd=%d, errno=%d)",
fd, x);
}
}
#endif
g.zCmdName = zCmdName;
rc = dispatch_name_search(zCmdName, CMDFLAG_COMMAND|CMDFLAG_PREFIX, &pCmd);
if( rc==1 && g.argc==2 && file_is_repository(g.argv[1]) ){
/* If the command-line is "fossil ABC" and "ABC" is no a valid command,
** but "ABC" is the name of a repository file, make the command be
** "fossil ui ABC" instead.
*/
char **zNewArgv = fossil_malloc( sizeof(char*)*4 );
zNewArgv[0] = g.argv[0];
zNewArgv[1] = "ui";
zNewArgv[2] = g.argv[1];
zNewArgv[3] = 0;
g.argc = 3;
g.argv = zNewArgv;
g.zCmdName = zCmdName = "ui";
rc = dispatch_name_search(zCmdName, CMDFLAG_COMMAND|CMDFLAG_PREFIX, &pCmd);
}
if( rc==1 ){
#ifdef FOSSIL_ENABLE_TH1_HOOKS
if( !g.isHTTP && !g.fNoThHook ){
rc = Th_CommandHook(zCmdName, 0);
}else{
rc = TH_OK;
}
|
| ︙ | ︙ | |||
818 819 820 821 822 823 824 825 826 827 828 829 830 831 |
dispatch_matching_names(zCmdName, &couldbe);
fossil_print("%s: ambiguous command prefix: %s\n"
"%s: could be any of:%s\n"
"%s: use \"help\" for more information\n",
g.argv[0], zCmdName, g.argv[0], blob_str(&couldbe), g.argv[0]);
fossil_exit(1);
}
atexit( fossil_atexit );
#ifdef FOSSIL_ENABLE_TH1_HOOKS
/*
** The TH1 return codes from the hook will be handled as follows:
**
** TH_OK: The xFunc() and the TH1 notification will both be executed.
**
| > > > > > > > | 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 |
dispatch_matching_names(zCmdName, &couldbe);
fossil_print("%s: ambiguous command prefix: %s\n"
"%s: could be any of:%s\n"
"%s: use \"help\" for more information\n",
g.argv[0], zCmdName, g.argv[0], blob_str(&couldbe), g.argv[0]);
fossil_exit(1);
}
#ifdef FOSSIL_ENABLE_JSON
else if( rc==0 && strcmp("json",pCmd->zName)==0 ){
g.json.isJsonMode = 1;
}else{
assert(!g.json.isJsonMode && "JSON-mode misconfiguration.");
}
#endif
atexit( fossil_atexit );
#ifdef FOSSIL_ENABLE_TH1_HOOKS
/*
** The TH1 return codes from the hook will be handled as follows:
**
** TH_OK: The xFunc() and the TH1 notification will both be executed.
**
|
| ︙ | ︙ | |||
878 879 880 881 882 883 884 |
g.argv[i] = g.argv[j];
}
g.argc = i;
}
/*
| | > > | > > > > | > > > | > > > | > | 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 |
g.argv[i] = g.argv[j];
}
g.argc = i;
}
/*
** Look for a command-line option. If present, remove it from the
** argument list and return a pointer to either the flag's name (if
** hasArg==0), sans leading - or --, or its value (if hasArg==1).
** Return NULL if the flag is not found.
**
** zLong is the "long" form of the flag and zShort is the
** short/abbreviated form (typically a single letter, but it may be
** longer). zLong must not be NULL, but zShort may be.
**
** hasArg==0 means the option is a flag. It is either present or not.
** hasArg==1 means the option has an argument, in which case a pointer
** to the argument's value is returned. For zLong, a flag value (if
** hasValue==1) may either be in the form (--flag=value) or (--flag
** value). For zShort, only the latter form is accepted.
**
** If a standalone argument of "--" is encountered in the argument
** list while searching for the given flag(s), this routine stops
** searching and NULL is returned.
*/
const char *find_option(const char *zLong, const char *zShort, int hasArg){
int i;
int nLong;
const char *zReturn = 0;
assert( hasArg==0 || hasArg==1 );
nLong = strlen(zLong);
for(i=1; i<g.argc; i++){
char *z;
if( i+hasArg >= g.argc ) break;
z = g.argv[i];
if( z[0]!='-' ) continue;
z++;
if( z[0]=='-' ){
if( z[1]==0 ){
/* Stop processing at "--" without consuming it.
verify_all_options() will consume this flag. */
break;
}
z++;
}
if( strncmp(z,zLong,nLong)==0 ){
if( hasArg && z[nLong]=='=' ){
zReturn = &z[nLong+1];
|
| ︙ | ︙ | |||
933 934 935 936 937 938 939 |
int has_option(const char *zOption){
int i;
int n = (int)strlen(zOption);
for(i=1; i<g.argc; i++){
char *z = g.argv[i];
if( z[0]!='-' ) continue;
z++;
| | > > > > > > | > > > | 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 |
int has_option(const char *zOption){
int i;
int n = (int)strlen(zOption);
for(i=1; i<g.argc; i++){
char *z = g.argv[i];
if( z[0]!='-' ) continue;
z++;
if( z[0]=='-' ){
if( z[1]==0 ){
/* Stop processing at "--" */
break;
}
z++;
}
if( strncmp(z,zOption,n)==0 && (z[n]==0 || z[n]=='=') ) return 1;
}
return 0;
}
/*
** Look for multiple occurrences of a command-line option with the
** corresponding argument.
**
** Return a malloc allocated array of pointers to the arguments.
**
** pnUsedArgs is used to store the number of matched arguments.
**
** Caller is responsible for freeing allocated memory by passing the
** head of the array (not each entry) to fossil_free(). (The
** individual entries have the same lifetime as values returned from
** find_option().)
*/
const char **find_repeatable_option(
const char *zLong,
const char *zShort,
int *pnUsedArgs
){
const char *zOption;
|
| ︙ | ︙ | |||
991 992 993 994 995 996 997 998 999 1000 1001 |
return g.zRepositoryOption;
}
/*
** Verify that there are no unprocessed command-line options. If
** Any remaining command-line argument begins with "-" print
** an error message and quit.
*/
void verify_all_options(void){
int i;
for(i=1; i<g.argc; i++){
| > > > > > > > > > > > > > | > > > > > > | | | > < | | 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 |
return g.zRepositoryOption;
}
/*
** Verify that there are no unprocessed command-line options. If
** Any remaining command-line argument begins with "-" print
** an error message and quit.
**
** Exception: if "--" is encountered, it is consumed from the argument
** list and this function immediately returns. The effect is to treat
** all arguments after "--" as non-flags (conventionally used to
** enable passing-in of filenames which start with a dash).
**
** This function must normally only be called one time per app
** invokation. The exception is commands which process their
** arguments, call this to confirm that there are no extraneous flags,
** then modify the arguments list for forwarding to another
** (sub)command (which itself will call this to confirm its own
** arguments).
*/
void verify_all_options(void){
int i;
for(i=1; i<g.argc; i++){
const char * arg = g.argv[i];
if( arg[0]=='-' ){
if( arg[1]=='-' && arg[2]==0 ){
/* Remove "--" from the list and treat all following
** arguments as non-flags. */
remove_from_argv(i, 1);
break;
}else if( arg[1]!=0 ){
fossil_fatal(
"unrecognized command-line option or missing argument: %s",
arg);
}
}
}
}
/*
** This function returns a human readable version string.
*/
const char *get_version(){
static const char version[] = RELEASE_VERSION " " MANIFEST_VERSION " "
MANIFEST_DATE " UTC";
return version;
}
/*
** This function populates a blob with version information. It is used by
** the "version" command and "test-version" web page. It assumes the blob
** passed to it is uninitialized; otherwise, it will leak memory.
*/
void fossil_version_blob(
Blob *pOut, /* Write the manifest here */
int bVerbose /* Non-zero for full information. */
){
#if defined(FOSSIL_ENABLE_TCL)
int rc;
const char *zRc;
#endif
|
| ︙ | ︙ | |||
1153 1154 1155 1156 1157 1158 1159 |
*/
void version_cmd(void){
Blob versionInfo;
int verboseFlag = find_option("verbose","v",0)!=0;
/* We should be done with options.. */
verify_all_options();
| | | 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 |
*/
void version_cmd(void){
Blob versionInfo;
int verboseFlag = find_option("verbose","v",0)!=0;
/* We should be done with options.. */
verify_all_options();
fossil_version_blob(&versionInfo, verboseFlag);
fossil_print("%s", blob_str(&versionInfo));
}
/*
** WEBPAGE: version
**
|
| ︙ | ︙ | |||
1176 1177 1178 1179 1180 1181 1182 |
int verboseFlag;
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
verboseFlag = PD("verbose", 0) != 0;
style_header("Version Information");
style_submenu_element("Stat", "stat");
| | | 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 |
int verboseFlag;
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
verboseFlag = PD("verbose", 0) != 0;
style_header("Version Information");
style_submenu_element("Stat", "stat");
fossil_version_blob(&versionInfo, verboseFlag);
@ <pre>
@ %h(blob_str(&versionInfo))
@ </pre>
style_footer();
}
|
| ︙ | ︙ | |||
1444 1445 1446 1447 1448 1449 1450 |
/* Handle universal query parameters */
if( PB("utc") ){
g.fTimeFormat = 1;
}else if( PB("localtime") ){
g.fTimeFormat = 2;
}
| > > > > > > > > > > > > > | > | 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 |
/* Handle universal query parameters */
if( PB("utc") ){
g.fTimeFormat = 1;
}else if( PB("localtime") ){
g.fTimeFormat = 2;
}
#ifdef FOSSIL_ENABLE_JSON
/*
** Ensure that JSON mode is set up if we're visiting /json, to allow
** us to customize some following behaviour (error handling and only
** process JSON-mode POST data if we're actually in a /json
** page). This is normally set up before this routine is called, but
** it looks like the ssh_request_loop() approach to dispatching
** might bypass that.
*/
if( g.json.isJsonMode==0 && zPathInfo!=0
&& 0==strncmp("/json",zPathInfo,5)
&& (zPathInfo[5]==0 || zPathInfo[5]=='/')){
g.json.isJsonMode = 1;
}
#endif
/* If the repository has not been opened already, then find the
** repository based on the first element of PATH_INFO and open it.
*/
if( !g.repositoryOpen ){
char *zRepo; /* Candidate repository name */
char *zToFree = 0; /* Malloced memory that needs to be freed */
const char *zCleanRepo; /* zRepo with surplus leading "/" removed */
|
| ︙ | ︙ | |||
1575 1576 1577 1578 1579 1580 1581 |
return;
}
zRepo[j] = '.';
}
/* If we reach this point, it means that the search of the PATH_INFO
** string is finished. Either zRepo contains the name of the
| | | 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 |
return;
}
zRepo[j] = '.';
}
/* If we reach this point, it means that the search of the PATH_INFO
** string is finished. Either zRepo contains the name of the
** repository to be used, or else no repository could be found and
** some kind of error response is required.
*/
if( szFile<1024 ){
set_base_url(0);
if( (zPathInfo[0]==0 || strcmp(zPathInfo,"/")==0)
&& allowRepoList
&& repo_list_page() ){
|
| ︙ | ︙ | |||
1599 1600 1601 1602 1603 1604 1605 |
#endif
@ <html><head>
@ <meta name="viewport" \
@ content="width=device-width, initial-scale=1.0">
@ </head><body>
@ <h1>Not Found</h1>
@ </body>
| | | 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 |
#endif
@ <html><head>
@ <meta name="viewport" \
@ content="width=device-width, initial-scale=1.0">
@ </head><body>
@ <h1>Not Found</h1>
@ </body>
cgi_set_status(404, "Not Found");
cgi_reply();
}
return;
}
break;
}
|
| ︙ | ︙ | |||
1706 1707 1708 1709 1710 1711 1712 |
zPath[i] = 0;
g.zExtra = &zPath[i+1];
}else{
g.zExtra = 0;
}
break;
}
| < < < < < < < < < < < | 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 |
zPath[i] = 0;
g.zExtra = &zPath[i+1];
}else{
g.zExtra = 0;
}
break;
}
if( g.zExtra ){
/* CGI parameters get this treatment elsewhere, but places like getfile
** will use g.zExtra directly.
** Reminder: the login mechanism uses 'name' differently, and may
** eventually have a problem/collision with this.
**
** Disabled by stephan when running in JSON mode because this
|
| ︙ | ︙ | |||
1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 |
#endif
{
@ <h1>Server Configuration Error</h1>
@ <p>The database schema on the server is out-of-date. Please ask
@ the administrator to run <b>fossil rebuild</b>.</p>
}
}else{
if( (pCmd->eCmdFlags & CMDFLAG_RAWCONTENT)==0 ){
cgi_decode_post_parameters();
}
if( g.fCgiTrace ){
fossil_trace("######## Calling %s #########\n", pCmd->zName);
cgi_print_all(1, 1);
}
| > > > > > > > | 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 |
#endif
{
@ <h1>Server Configuration Error</h1>
@ <p>The database schema on the server is out-of-date. Please ask
@ the administrator to run <b>fossil rebuild</b>.</p>
}
}else{
#ifdef FOSSIL_ENABLE_JSON
static int jsonOnce = 0;
if( !jsonOnce && g.json.isJsonMode ){
json_mode_bootstrap();
jsonOnce = 1;
}
#endif
if( (pCmd->eCmdFlags & CMDFLAG_RAWCONTENT)==0 ){
cgi_decode_post_parameters();
}
if( g.fCgiTrace ){
fossil_trace("######## Calling %s #########\n", pCmd->zName);
cgi_print_all(1, 1);
}
|
| ︙ | ︙ | |||
1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 | ** HOME: PATH Shorthand for "setenv: HOME PATH" ** ** debug: FILE Causing debugging information to be written ** into FILE. ** ** errorlog: FILE Warnings, errors, and panics written to FILE. ** ** extroot: DIR Directory that is the root of the sub-CGI tree ** on the /ext page. ** ** redirect: REPO URL Extract the "name" query parameter and search ** REPO for a check-in or ticket that matches the ** value of "name", then redirect to URL. There ** can be multiple "redirect:" lines that are | > > > | 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 | ** HOME: PATH Shorthand for "setenv: HOME PATH" ** ** debug: FILE Causing debugging information to be written ** into FILE. ** ** errorlog: FILE Warnings, errors, and panics written to FILE. ** ** timeout: SECONDS Do not run for longer than SECONDS. The default ** timeout is FOSSIL_DEFAULT_TIMEOUT (600) seconds. ** ** extroot: DIR Directory that is the root of the sub-CGI tree ** on the /ext page. ** ** redirect: REPO URL Extract the "name" query parameter and search ** REPO for a check-in or ticket that matches the ** value of "name", then redirect to URL. There ** can be multiple "redirect:" lines that are |
| ︙ | ︙ | |||
1969 1970 1971 1972 1973 1974 1975 | const char *zFile; const char *zNotFound = 0; char **azRedirect = 0; /* List of repositories to redirect to */ int nRedirect = 0; /* Number of entries in azRedirect */ Glob *pFileGlob = 0; /* Pattern for files */ int allowRepoList = 0; /* Allow lists of repository files */ Blob config, line, key, value, value2; | | < < < < > > > > > > > > > > | 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 |
const char *zFile;
const char *zNotFound = 0;
char **azRedirect = 0; /* List of repositories to redirect to */
int nRedirect = 0; /* Number of entries in azRedirect */
Glob *pFileGlob = 0; /* Pattern for files */
int allowRepoList = 0; /* Allow lists of repository files */
Blob config, line, key, value, value2;
/* Initialize the CGI environment. */
g.httpOut = stdout;
g.httpIn = stdin;
fossil_binary_mode(g.httpOut);
fossil_binary_mode(g.httpIn);
g.cgiOutput = 1;
fossil_set_timeout(FOSSIL_DEFAULT_TIMEOUT);
/* Find the name of the CGI control file */
if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){
zFile = g.argv[2];
}else if( g.argc>=2 ){
zFile = g.argv[1];
}else{
cgi_panic("No CGI control file specified");
}
/* Read and parse the CGI control file. */
blob_read_from_file(&config, zFile, ExtFILE);
while( blob_line(&config, &line) ){
if( !blob_token(&line, &key) ) continue;
if( blob_buffer(&key)[0]=='#' ) continue;
if( blob_eq(&key, "repository:") && blob_tail(&line, &value) ){
/* repository: FILENAME
**
|
| ︙ | ︙ | |||
2075 2076 2077 2078 2079 2080 2081 |
*/
blob_token(&line,&value2);
fossil_setenv(blob_str(&value), blob_str(&value2));
blob_reset(&value);
blob_reset(&value2);
continue;
}
| < < < < < < < < < < > > > > > > > > > | 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 |
*/
blob_token(&line,&value2);
fossil_setenv(blob_str(&value), blob_str(&value2));
blob_reset(&value);
blob_reset(&value2);
continue;
}
if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
/* errorlog: FILENAME
**
** Causes messages from warnings, errors, and panics to be appended
** to FILENAME.
*/
g.zErrlog = mprintf("%s", blob_str(&value));
blob_reset(&value);
continue;
}
if( blob_eq(&key, "extroot:") && blob_token(&line, &value) ){
/* extroot: DIRECTORY
**
** Enables the /ext webpage to use sub-cgi rooted at DIRECTORY
*/
g.zExtRoot = mprintf("%s", blob_str(&value));
blob_reset(&value);
continue;
}
if( blob_eq(&key, "timeout:") && blob_token(&line, &value) ){
/* timeout: SECONDS
**
** Set an alarm() that kills the process after SECONDS. The
** default value is FOSSIL_DEFAULT_TIMEOUT (600) seconds.
*/
fossil_set_timeout(atoi(blob_str(&value)));
continue;
}
if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){
/* HOME: VALUE
**
** Set CGI parameter "HOME" to VALUE. This is legacy. Use
** setenv: instead.
*/
|
| ︙ | ︙ | |||
2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 |
** name of the subdirectory under the skins/ directory that holds
** the elements of the built-in skin. If LABEL does not match,
** this directive is a silent no-op.
*/
skin_use_alternative(blob_str(&value));
blob_reset(&value);
continue;
}
}
blob_reset(&config);
if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
cgi_panic("Unable to find or open the project repository");
}
cgi_init();
| > > > > > > > > > > > > > > > | 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 |
** name of the subdirectory under the skins/ directory that holds
** the elements of the built-in skin. If LABEL does not match,
** this directive is a silent no-op.
*/
skin_use_alternative(blob_str(&value));
blob_reset(&value);
continue;
}
if( blob_eq(&key, "cgi-debug:") && blob_token(&line, &value) ){
/* cgi-debug: FILENAME
**
** Causes output from cgi_debug() and CGIDEBUG(()) calls to go
** into FILENAME. Useful for debugging CGI configuration problems.
*/
char *zNow = cgi_iso8601_datestamp();
cgi_load_environment();
g.fDebug = fossil_fopen(blob_str(&value), "ab");
blob_reset(&value);
cgi_debug("-------- BEGIN cgi at %s --------\n", zNow);
fossil_free(zNow);
cgi_print_all(1,2);
continue;
}
}
blob_reset(&config);
if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
cgi_panic("Unable to find or open the project repository");
}
cgi_init();
|
| ︙ | ︙ | |||
2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 |
if( sscanf(zPidKey, "%lu:%p:%u", pProcessId, ppAddress, &nSize)==3 ){
*pnSize = (SIZE_T)nSize;
}else{
fossil_fatal("failed to parse pid key");
}
}
#endif
/*
** COMMAND: http*
**
** Usage: %fossil http ?REPOSITORY? ?OPTIONS?
**
** Handle a single HTTP request appearing on stdin. The resulting webpage
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 |
if( sscanf(zPidKey, "%lu:%p:%u", pProcessId, ppAddress, &nSize)==3 ){
*pnSize = (SIZE_T)nSize;
}else{
fossil_fatal("failed to parse pid key");
}
}
#endif
/*
** WEBPAGE: test-pid
**
** Return the process identifier of the running Fossil server instance.
**
** Query parameters:
**
** usepidkey When present and available, also return the
** address and size, within this server process,
** of the saved database encryption key. This
** is only supported when using SEE on Windows.
*/
void test_pid_page(void){
login_check_credentials();
if( !g.perm.Setup ){ login_needed(0); return; }
#if defined(_WIN32) && USE_SEE
if( P("usepidkey")!=0 ){
if( g.zPidKey ){
@ %s(g.zPidKey)
return;
}else{
const char *zSavedKey = db_get_saved_encryption_key();
size_t savedKeySize = db_get_saved_encryption_key_size();
if( zSavedKey!=0 && savedKeySize>0 ){
@ %lu(GetCurrentProcessId()):%p(zSavedKey):%u(savedKeySize)
return;
}
}
}
#endif
@ %d(GETPID())
}
/*
** COMMAND: http*
**
** Usage: %fossil http ?REPOSITORY? ?OPTIONS?
**
** Handle a single HTTP request appearing on stdin. The resulting webpage
|
| ︙ | ︙ | |||
2289 2290 2291 2292 2293 2294 2295 | const char *zAltBase; const char *zFileGlob; const char *zInFile; const char *zOutFile; int useSCGI; int noJail; int allowRepoList; | < < < | 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 | const char *zAltBase; const char *zFileGlob; const char *zInFile; const char *zOutFile; int useSCGI; int noJail; int allowRepoList; Th_InitTraceLog(); /* The winhttp module passes the --files option as --files-urlenc with ** the argument being URL encoded, to avoid wildcard expansion in the ** shell. This option is for internal use and is undocumented. */ |
| ︙ | ︙ | |||
2342 2343 2344 2345 2346 2347 2348 |
if( find_option("https",0,0)!=0 ){
zIpAddr = fossil_getenv("REMOTE_HOST"); /* From stunnel */
cgi_replace_parameter("HTTPS","on");
}
zHost = find_option("host", 0, 1);
if( zHost ) cgi_replace_parameter("HTTP_HOST",zHost);
| < < < < < < < < < < < | 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 |
if( find_option("https",0,0)!=0 ){
zIpAddr = fossil_getenv("REMOTE_HOST"); /* From stunnel */
cgi_replace_parameter("HTTPS","on");
}
zHost = find_option("host", 0, 1);
if( zHost ) cgi_replace_parameter("HTTP_HOST",zHost);
/* We should be done with options.. */
verify_all_options();
if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
g.cgiOutput = 1;
g.fullHttpReply = 1;
find_server_repository(2, 0);
|
| ︙ | ︙ | |||
2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 |
**
** COMMAND: test-http
**
** Works like the http command but gives setup permission to all users.
**
** Options:
** --th-trace trace TH1 execution (for debugging purposes)
**
*/
void cmd_test_http(void){
const char *zIpAddr; /* IP address of remote client */
Th_InitTraceLog();
| > > < > > | > > > > > | 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 |
**
** COMMAND: test-http
**
** Works like the http command but gives setup permission to all users.
**
** Options:
** --th-trace trace TH1 execution (for debugging purposes)
** --usercap CAP user capability string. (Default: "sx")
**
*/
void cmd_test_http(void){
const char *zIpAddr; /* IP address of remote client */
const char *zUserCap;
Th_InitTraceLog();
zUserCap = find_option("usercap",0,1);
if( zUserCap==0 ){
g.useLocalauth = 1;
zUserCap = "sx";
}
login_set_capabilities(zUserCap, 0);
g.httpIn = stdin;
g.httpOut = stdout;
fossil_binary_mode(g.httpOut);
fossil_binary_mode(g.httpIn);
g.zExtRoot = find_option("extroot",0,1);
find_server_repository(2, 0);
g.cgiOutput = 1;
g.fNoHttpCompress = 1;
g.fullHttpReply = 1;
zIpAddr = cgi_ssh_remote_addr(0);
if( zIpAddr && zIpAddr[0] ){
|
| ︙ | ︙ | |||
2451 2452 2453 2454 2455 2456 2457 | } return 0; } #endif #endif /* | > | > | > > > > > > > > > > > > > > > > | 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 |
}
return 0;
}
#endif
#endif
/*
** Respond to a SIGALRM by writing a message to the error log (if there
** is one) and exiting.
*/
#ifndef _WIN32
static void sigalrm_handler(int x){
fossil_panic("TIMEOUT");
}
#endif
/*
** Arrange to timeout using SIGALRM after N seconds. Or if N==0, cancel
** any pending timeout.
**
** Bugs:
** (1) This only works on unix systems.
** (2) Any call to sleep() or sqlite3_sleep() will cancel the alarm.
*/
void fossil_set_timeout(int N){
#ifndef _WIN32
signal(SIGALRM, sigalrm_handler);
alarm(N);
#endif
}
/*
** COMMAND: server*
** COMMAND: ui
**
** Usage: %fossil server ?OPTIONS? ?REPOSITORY?
|
| ︙ | ︙ | |||
2537 2538 2539 2540 2541 2542 2543 | const char *zBrowser; /* Name of web browser program */ char *zBrowserCmd = 0; /* Command to launch the web browser */ int isUiCmd; /* True if command is "ui", not "server' */ const char *zNotFound; /* The --notfound option or NULL */ int flags = 0; /* Server flags */ #if !defined(_WIN32) int noJail; /* Do not enter the chroot jail */ | | < < < | 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 |
const char *zBrowser; /* Name of web browser program */
char *zBrowserCmd = 0; /* Command to launch the web browser */
int isUiCmd; /* True if command is "ui", not "server' */
const char *zNotFound; /* The --notfound option or NULL */
int flags = 0; /* Server flags */
#if !defined(_WIN32)
int noJail; /* Do not enter the chroot jail */
const char *zTimeout = 0; /* Max runtime of any single HTTP request */
#endif
int allowRepoList; /* List repositories on URL "/" */
const char *zAltBase; /* Argument to the --baseurl option */
const char *zFileGlob; /* Static content must match this */
char *zIpAddr = 0; /* Bind to this IP address */
int fCreate = 0; /* The --create flag */
const char *zInitPage = 0; /* Start on this page. --page option */
#if defined(_WIN32)
const char *zStopperFile; /* Name of file used to terminate server */
zStopperFile = find_option("stopper", 0, 1);
#endif
if( g.zErrlog==0 ){
|
| ︙ | ︙ | |||
2569 2570 2571 2572 2573 2574 2575 |
zFileGlob = z;
}else{
zFileGlob = find_option("files",0,1);
}
skin_override();
#if !defined(_WIN32)
noJail = find_option("nojail",0,0)!=0;
| | | 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 |
zFileGlob = z;
}else{
zFileGlob = find_option("files",0,1);
}
skin_override();
#if !defined(_WIN32)
noJail = find_option("nojail",0,0)!=0;
zTimeout = find_option("max-latency",0,1);
#endif
g.useLocalauth = find_option("localauth", 0, 0)!=0;
Th_InitTraceLog();
zPort = find_option("port", "P", 1);
isUiCmd = g.argv[1][0]=='u';
if( isUiCmd ){
zInitPage = find_option("page", 0, 1);
|
| ︙ | ︙ | |||
2595 2596 2597 2598 2599 2600 2601 |
if( find_option("https",0,0)!=0 ){
cgi_replace_parameter("HTTPS","on");
}
if( find_option("localhost", 0, 0)!=0 ){
flags |= HTTP_SERVER_LOCALHOST;
}
| < < < < < < < < < < < | 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 |
if( find_option("https",0,0)!=0 ){
cgi_replace_parameter("HTTPS","on");
}
if( find_option("localhost", 0, 0)!=0 ){
flags |= HTTP_SERVER_LOCALHOST;
}
/* We should be done with options.. */
verify_all_options();
if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
if( isUiCmd ){
flags |= HTTP_SERVER_LOCALHOST|HTTP_SERVER_REPOLIST;
g.useLocalauth = 1;
|
| ︙ | ︙ | |||
2662 2663 2664 2665 2666 2667 2668 |
}
}
}
#else
zBrowser = db_get("web-browser", "open");
#endif
if( zIpAddr==0 ){
| | | | | < | > > | 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 |
}
}
}
#else
zBrowser = db_get("web-browser", "open");
#endif
if( zIpAddr==0 ){
zBrowserCmd = mprintf("%s \"http://localhost:%%d/%s\" &",
zBrowser, zInitPage);
}else if( strchr(zIpAddr,':') ){
zBrowserCmd = mprintf("%s \"http://[%s]:%%d/%s\" &",
zBrowser, zIpAddr, zInitPage);
}else{
zBrowserCmd = mprintf("%s \"http://%s:%%d/%s\" &",
zBrowser, zIpAddr, zInitPage);
}
}
if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
db_close(1);
if( cgi_http_server(iPort, mxPort, zBrowserCmd, zIpAddr, flags) ){
fossil_fatal("unable to listen on TCP socket %d", iPort);
}
/* For the parent process, the cgi_http_server() command above never
** returns (except in the case of an error). Instead, for each incoming
** client connection, a child process is created, file descriptors 0
** and 1 are bound to that connection, and the child returns.
**
** So, when control reaches this point, we are running as a
** child process, the HTTP or SCGI request is pending on file
** descriptor 0 and the reply should be written to file descriptor 1.
*/
if( zTimeout ){
fossil_set_timeout(atoi(zTimeout));
}else{
fossil_set_timeout(FOSSIL_DEFAULT_TIMEOUT);
}
g.httpIn = stdin;
g.httpOut = stdout;
#if !defined(_WIN32)
signal(SIGSEGV, sigsegv_handler);
signal(SIGPIPE, sigpipe_handler);
|
| ︙ | ︙ |
| ︙ | ︙ | |||
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | XBCC = $(BCC) $(BCCFLAGS) XTCC = $(TCC) -I. -I$(SRCDIR) -I$(OBJDIR) $(TCCFLAGS) TESTFLAGS := -quiet SRC = \ $(SRCDIR)/add.c \ $(SRCDIR)/alerts.c \ $(SRCDIR)/allrepo.c \ $(SRCDIR)/attach.c \ $(SRCDIR)/backoffice.c \ $(SRCDIR)/bag.c \ $(SRCDIR)/bisect.c \ $(SRCDIR)/blob.c \ $(SRCDIR)/branch.c \ $(SRCDIR)/browse.c \ $(SRCDIR)/builtin.c \ | > > | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | XBCC = $(BCC) $(BCCFLAGS) XTCC = $(TCC) -I. -I$(SRCDIR) -I$(OBJDIR) $(TCCFLAGS) TESTFLAGS := -quiet SRC = \ $(SRCDIR)/add.c \ $(SRCDIR)/ajax.c \ $(SRCDIR)/alerts.c \ $(SRCDIR)/allrepo.c \ $(SRCDIR)/attach.c \ $(SRCDIR)/backlink.c \ $(SRCDIR)/backoffice.c \ $(SRCDIR)/bag.c \ $(SRCDIR)/bisect.c \ $(SRCDIR)/blob.c \ $(SRCDIR)/branch.c \ $(SRCDIR)/browse.c \ $(SRCDIR)/builtin.c \ |
| ︙ | ︙ | |||
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | $(SRCDIR)/doc.c \ $(SRCDIR)/encode.c \ $(SRCDIR)/etag.c \ $(SRCDIR)/event.c \ $(SRCDIR)/export.c \ $(SRCDIR)/extcgi.c \ $(SRCDIR)/file.c \ $(SRCDIR)/finfo.c \ $(SRCDIR)/foci.c \ $(SRCDIR)/forum.c \ $(SRCDIR)/fshell.c \ $(SRCDIR)/fusefs.c \ $(SRCDIR)/glob.c \ $(SRCDIR)/graph.c \ $(SRCDIR)/gzip.c \ $(SRCDIR)/hname.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ $(SRCDIR)/http_ssl.c \ | > > | 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | $(SRCDIR)/doc.c \ $(SRCDIR)/encode.c \ $(SRCDIR)/etag.c \ $(SRCDIR)/event.c \ $(SRCDIR)/export.c \ $(SRCDIR)/extcgi.c \ $(SRCDIR)/file.c \ $(SRCDIR)/fileedit.c \ $(SRCDIR)/finfo.c \ $(SRCDIR)/foci.c \ $(SRCDIR)/forum.c \ $(SRCDIR)/fshell.c \ $(SRCDIR)/fusefs.c \ $(SRCDIR)/fuzz.c \ $(SRCDIR)/glob.c \ $(SRCDIR)/graph.c \ $(SRCDIR)/gzip.c \ $(SRCDIR)/hname.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ $(SRCDIR)/http_ssl.c \ |
| ︙ | ︙ | |||
127 128 129 130 131 132 133 134 135 136 137 138 139 140 | $(SRCDIR)/stash.c \ $(SRCDIR)/stat.c \ $(SRCDIR)/statrep.c \ $(SRCDIR)/style.c \ $(SRCDIR)/sync.c \ $(SRCDIR)/tag.c \ $(SRCDIR)/tar.c \ $(SRCDIR)/th_main.c \ $(SRCDIR)/timeline.c \ $(SRCDIR)/tkt.c \ $(SRCDIR)/tktsetup.c \ $(SRCDIR)/undo.c \ $(SRCDIR)/unicode.c \ $(SRCDIR)/unversioned.c \ | > | 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | $(SRCDIR)/stash.c \ $(SRCDIR)/stat.c \ $(SRCDIR)/statrep.c \ $(SRCDIR)/style.c \ $(SRCDIR)/sync.c \ $(SRCDIR)/tag.c \ $(SRCDIR)/tar.c \ $(SRCDIR)/terminal.c \ $(SRCDIR)/th_main.c \ $(SRCDIR)/timeline.c \ $(SRCDIR)/tkt.c \ $(SRCDIR)/tktsetup.c \ $(SRCDIR)/undo.c \ $(SRCDIR)/unicode.c \ $(SRCDIR)/unversioned.c \ |
| ︙ | ︙ | |||
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 | $(SRCDIR)/../skins/rounded1/details.txt \ $(SRCDIR)/../skins/rounded1/footer.txt \ $(SRCDIR)/../skins/rounded1/header.txt \ $(SRCDIR)/../skins/xekri/css.txt \ $(SRCDIR)/../skins/xekri/details.txt \ $(SRCDIR)/../skins/xekri/footer.txt \ $(SRCDIR)/../skins/xekri/header.txt \ $(SRCDIR)/ci_edit.js \ $(SRCDIR)/copybtn.js \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ $(SRCDIR)/graph.js \ $(SRCDIR)/href.js \ $(SRCDIR)/login.js \ $(SRCDIR)/markdown.md \ $(SRCDIR)/menu.js \ $(SRCDIR)/sbsdiff.js \ $(SRCDIR)/scroll.js \ $(SRCDIR)/skin.js \ $(SRCDIR)/sorttable.js \ $(SRCDIR)/tree.js \ $(SRCDIR)/useredit.js \ $(SRCDIR)/wiki.wiki TRANS_SRC = \ $(OBJDIR)/add_.c \ $(OBJDIR)/alerts_.c \ $(OBJDIR)/allrepo_.c \ $(OBJDIR)/attach_.c \ $(OBJDIR)/backoffice_.c \ $(OBJDIR)/bag_.c \ $(OBJDIR)/bisect_.c \ $(OBJDIR)/blob_.c \ $(OBJDIR)/branch_.c \ $(OBJDIR)/browse_.c \ $(OBJDIR)/builtin_.c \ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | $(SRCDIR)/../skins/rounded1/details.txt \ $(SRCDIR)/../skins/rounded1/footer.txt \ $(SRCDIR)/../skins/rounded1/header.txt \ $(SRCDIR)/../skins/xekri/css.txt \ $(SRCDIR)/../skins/xekri/details.txt \ $(SRCDIR)/../skins/xekri/footer.txt \ $(SRCDIR)/../skins/xekri/header.txt \ $(SRCDIR)/accordion.js \ $(SRCDIR)/ci_edit.js \ $(SRCDIR)/copybtn.js \ $(SRCDIR)/default.css \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ $(SRCDIR)/fossil.bootstrap.js \ $(SRCDIR)/fossil.confirmer.js \ $(SRCDIR)/fossil.dom.js \ $(SRCDIR)/fossil.fetch.js \ $(SRCDIR)/fossil.page.fileedit.js \ $(SRCDIR)/fossil.storage.js \ $(SRCDIR)/fossil.tabs.js \ $(SRCDIR)/graph.js \ $(SRCDIR)/href.js \ $(SRCDIR)/login.js \ $(SRCDIR)/markdown.md \ $(SRCDIR)/menu.js \ $(SRCDIR)/sbsdiff.js \ $(SRCDIR)/scroll.js \ $(SRCDIR)/skin.js \ $(SRCDIR)/sorttable.js \ $(SRCDIR)/sounds/0.wav \ $(SRCDIR)/sounds/1.wav \ $(SRCDIR)/sounds/2.wav \ $(SRCDIR)/sounds/3.wav \ $(SRCDIR)/sounds/4.wav \ $(SRCDIR)/sounds/5.wav \ $(SRCDIR)/sounds/6.wav \ $(SRCDIR)/sounds/7.wav \ $(SRCDIR)/sounds/8.wav \ $(SRCDIR)/sounds/9.wav \ $(SRCDIR)/sounds/a.wav \ $(SRCDIR)/sounds/b.wav \ $(SRCDIR)/sounds/c.wav \ $(SRCDIR)/sounds/d.wav \ $(SRCDIR)/sounds/e.wav \ $(SRCDIR)/sounds/f.wav \ $(SRCDIR)/style.admin_log.css \ $(SRCDIR)/style.fileedit.css \ $(SRCDIR)/tree.js \ $(SRCDIR)/useredit.js \ $(SRCDIR)/wiki.wiki TRANS_SRC = \ $(OBJDIR)/add_.c \ $(OBJDIR)/ajax_.c \ $(OBJDIR)/alerts_.c \ $(OBJDIR)/allrepo_.c \ $(OBJDIR)/attach_.c \ $(OBJDIR)/backlink_.c \ $(OBJDIR)/backoffice_.c \ $(OBJDIR)/bag_.c \ $(OBJDIR)/bisect_.c \ $(OBJDIR)/blob_.c \ $(OBJDIR)/branch_.c \ $(OBJDIR)/browse_.c \ $(OBJDIR)/builtin_.c \ |
| ︙ | ︙ | |||
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 | $(OBJDIR)/doc_.c \ $(OBJDIR)/encode_.c \ $(OBJDIR)/etag_.c \ $(OBJDIR)/event_.c \ $(OBJDIR)/export_.c \ $(OBJDIR)/extcgi_.c \ $(OBJDIR)/file_.c \ $(OBJDIR)/finfo_.c \ $(OBJDIR)/foci_.c \ $(OBJDIR)/forum_.c \ $(OBJDIR)/fshell_.c \ $(OBJDIR)/fusefs_.c \ $(OBJDIR)/glob_.c \ $(OBJDIR)/graph_.c \ $(OBJDIR)/gzip_.c \ $(OBJDIR)/hname_.c \ $(OBJDIR)/http_.c \ $(OBJDIR)/http_socket_.c \ $(OBJDIR)/http_ssl_.c \ | > > | 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | $(OBJDIR)/doc_.c \ $(OBJDIR)/encode_.c \ $(OBJDIR)/etag_.c \ $(OBJDIR)/event_.c \ $(OBJDIR)/export_.c \ $(OBJDIR)/extcgi_.c \ $(OBJDIR)/file_.c \ $(OBJDIR)/fileedit_.c \ $(OBJDIR)/finfo_.c \ $(OBJDIR)/foci_.c \ $(OBJDIR)/forum_.c \ $(OBJDIR)/fshell_.c \ $(OBJDIR)/fusefs_.c \ $(OBJDIR)/fuzz_.c \ $(OBJDIR)/glob_.c \ $(OBJDIR)/graph_.c \ $(OBJDIR)/gzip_.c \ $(OBJDIR)/hname_.c \ $(OBJDIR)/http_.c \ $(OBJDIR)/http_socket_.c \ $(OBJDIR)/http_ssl_.c \ |
| ︙ | ︙ | |||
341 342 343 344 345 346 347 348 349 350 351 352 353 354 | $(OBJDIR)/stash_.c \ $(OBJDIR)/stat_.c \ $(OBJDIR)/statrep_.c \ $(OBJDIR)/style_.c \ $(OBJDIR)/sync_.c \ $(OBJDIR)/tag_.c \ $(OBJDIR)/tar_.c \ $(OBJDIR)/th_main_.c \ $(OBJDIR)/timeline_.c \ $(OBJDIR)/tkt_.c \ $(OBJDIR)/tktsetup_.c \ $(OBJDIR)/undo_.c \ $(OBJDIR)/unicode_.c \ $(OBJDIR)/unversioned_.c \ | > | 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 | $(OBJDIR)/stash_.c \ $(OBJDIR)/stat_.c \ $(OBJDIR)/statrep_.c \ $(OBJDIR)/style_.c \ $(OBJDIR)/sync_.c \ $(OBJDIR)/tag_.c \ $(OBJDIR)/tar_.c \ $(OBJDIR)/terminal_.c \ $(OBJDIR)/th_main_.c \ $(OBJDIR)/timeline_.c \ $(OBJDIR)/tkt_.c \ $(OBJDIR)/tktsetup_.c \ $(OBJDIR)/undo_.c \ $(OBJDIR)/unicode_.c \ $(OBJDIR)/unversioned_.c \ |
| ︙ | ︙ | |||
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 | $(OBJDIR)/wysiwyg_.c \ $(OBJDIR)/xfer_.c \ $(OBJDIR)/xfersetup_.c \ $(OBJDIR)/zip_.c OBJ = \ $(OBJDIR)/add.o \ $(OBJDIR)/alerts.o \ $(OBJDIR)/allrepo.o \ $(OBJDIR)/attach.o \ $(OBJDIR)/backoffice.o \ $(OBJDIR)/bag.o \ $(OBJDIR)/bisect.o \ $(OBJDIR)/blob.o \ $(OBJDIR)/branch.o \ $(OBJDIR)/browse.o \ $(OBJDIR)/builtin.o \ | > > | 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 | $(OBJDIR)/wysiwyg_.c \ $(OBJDIR)/xfer_.c \ $(OBJDIR)/xfersetup_.c \ $(OBJDIR)/zip_.c OBJ = \ $(OBJDIR)/add.o \ $(OBJDIR)/ajax.o \ $(OBJDIR)/alerts.o \ $(OBJDIR)/allrepo.o \ $(OBJDIR)/attach.o \ $(OBJDIR)/backlink.o \ $(OBJDIR)/backoffice.o \ $(OBJDIR)/bag.o \ $(OBJDIR)/bisect.o \ $(OBJDIR)/blob.o \ $(OBJDIR)/branch.o \ $(OBJDIR)/browse.o \ $(OBJDIR)/builtin.o \ |
| ︙ | ︙ | |||
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 | $(OBJDIR)/doc.o \ $(OBJDIR)/encode.o \ $(OBJDIR)/etag.o \ $(OBJDIR)/event.o \ $(OBJDIR)/export.o \ $(OBJDIR)/extcgi.o \ $(OBJDIR)/file.o \ $(OBJDIR)/finfo.o \ $(OBJDIR)/foci.o \ $(OBJDIR)/forum.o \ $(OBJDIR)/fshell.o \ $(OBJDIR)/fusefs.o \ $(OBJDIR)/glob.o \ $(OBJDIR)/graph.o \ $(OBJDIR)/gzip.o \ $(OBJDIR)/hname.o \ $(OBJDIR)/http.o \ $(OBJDIR)/http_socket.o \ $(OBJDIR)/http_ssl.o \ | > > | 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 | $(OBJDIR)/doc.o \ $(OBJDIR)/encode.o \ $(OBJDIR)/etag.o \ $(OBJDIR)/event.o \ $(OBJDIR)/export.o \ $(OBJDIR)/extcgi.o \ $(OBJDIR)/file.o \ $(OBJDIR)/fileedit.o \ $(OBJDIR)/finfo.o \ $(OBJDIR)/foci.o \ $(OBJDIR)/forum.o \ $(OBJDIR)/fshell.o \ $(OBJDIR)/fusefs.o \ $(OBJDIR)/fuzz.o \ $(OBJDIR)/glob.o \ $(OBJDIR)/graph.o \ $(OBJDIR)/gzip.o \ $(OBJDIR)/hname.o \ $(OBJDIR)/http.o \ $(OBJDIR)/http_socket.o \ $(OBJDIR)/http_ssl.o \ |
| ︙ | ︙ | |||
481 482 483 484 485 486 487 488 489 490 491 492 493 494 | $(OBJDIR)/stash.o \ $(OBJDIR)/stat.o \ $(OBJDIR)/statrep.o \ $(OBJDIR)/style.o \ $(OBJDIR)/sync.o \ $(OBJDIR)/tag.o \ $(OBJDIR)/tar.o \ $(OBJDIR)/th_main.o \ $(OBJDIR)/timeline.o \ $(OBJDIR)/tkt.o \ $(OBJDIR)/tktsetup.o \ $(OBJDIR)/undo.o \ $(OBJDIR)/unicode.o \ $(OBJDIR)/unversioned.o \ | > | 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 | $(OBJDIR)/stash.o \ $(OBJDIR)/stat.o \ $(OBJDIR)/statrep.o \ $(OBJDIR)/style.o \ $(OBJDIR)/sync.o \ $(OBJDIR)/tag.o \ $(OBJDIR)/tar.o \ $(OBJDIR)/terminal.o \ $(OBJDIR)/th_main.o \ $(OBJDIR)/timeline.o \ $(OBJDIR)/tkt.o \ $(OBJDIR)/tktsetup.o \ $(OBJDIR)/undo.o \ $(OBJDIR)/unicode.o \ $(OBJDIR)/unversioned.o \ |
| ︙ | ︙ | |||
504 505 506 507 508 509 510 | $(OBJDIR)/wikiformat.o \ $(OBJDIR)/winfile.o \ $(OBJDIR)/winhttp.o \ $(OBJDIR)/wysiwyg.o \ $(OBJDIR)/xfer.o \ $(OBJDIR)/xfersetup.o \ $(OBJDIR)/zip.o | < < < < < | 546 547 548 549 550 551 552 553 554 555 556 557 558 559 | $(OBJDIR)/wikiformat.o \ $(OBJDIR)/winfile.o \ $(OBJDIR)/winhttp.o \ $(OBJDIR)/wysiwyg.o \ $(OBJDIR)/xfer.o \ $(OBJDIR)/xfersetup.o \ $(OBJDIR)/zip.o all: $(OBJDIR) $(APPNAME) install: all mkdir -p $(INSTALLDIR) cp $(APPNAME) $(INSTALLDIR) codecheck: $(TRANS_SRC) $(OBJDIR)/codecheck1 |
| ︙ | ︙ | |||
536 537 538 539 540 541 542 | $(OBJDIR)/mkbuiltin: $(SRCDIR)/mkbuiltin.c $(XBCC) -o $(OBJDIR)/mkbuiltin $(SRCDIR)/mkbuiltin.c $(OBJDIR)/mkversion: $(SRCDIR)/mkversion.c $(XBCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c | < < < | 573 574 575 576 577 578 579 580 581 582 583 584 585 586 | $(OBJDIR)/mkbuiltin: $(SRCDIR)/mkbuiltin.c $(XBCC) -o $(OBJDIR)/mkbuiltin $(SRCDIR)/mkbuiltin.c $(OBJDIR)/mkversion: $(SRCDIR)/mkversion.c $(XBCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c $(OBJDIR)/codecheck1: $(SRCDIR)/codecheck1.c $(XBCC) -o $(OBJDIR)/codecheck1 $(SRCDIR)/codecheck1.c # Run the test suite. # Other flags that can be included in TESTFLAGS are: # # -halt Stop testing after the first failed test |
| ︙ | ︙ | |||
561 562 563 564 565 566 567 | # test: $(OBJDIR) $(APPNAME) $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(TESTFLAGS) $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion $(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h | < < < < | > < > | 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 |
#
test: $(OBJDIR) $(APPNAME)
$(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(TESTFLAGS)
$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion
$(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h
# Setup the options used to compile the included SQLite library.
SQLITE_OPTIONS = -DNDEBUG=1 \
-DSQLITE_DQS=0 \
-DSQLITE_THREADSAFE=0 \
-DSQLITE_DEFAULT_MEMSTATUS=0 \
-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \
-DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
-DSQLITE_OMIT_DECLTYPE \
-DSQLITE_OMIT_DEPRECATED \
-DSQLITE_OMIT_PROGRESS_CALLBACK \
-DSQLITE_OMIT_SHARED_CACHE \
-DSQLITE_OMIT_LOAD_EXTENSION \
-DSQLITE_MAX_EXPR_DEPTH=0 \
-DSQLITE_USE_ALLOCA \
-DSQLITE_ENABLE_LOCKING_STYLE=0 \
-DSQLITE_DEFAULT_FILE_FORMAT=4 \
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
-DSQLITE_ENABLE_FTS4 \
-DSQLITE_ENABLE_DBSTAT_VTAB \
-DSQLITE_ENABLE_JSON1 \
-DSQLITE_ENABLE_FTS5 \
-DSQLITE_ENABLE_STMTVTAB \
-DSQLITE_HAVE_ZLIB \
-DSQLITE_INTROSPECTION_PRAGMAS \
-DSQLITE_ENABLE_DBPAGE_VTAB \
-DSQLITE_TRUSTED_SCHEMA=0
# Setup the options used to compile the included SQLite shell.
SHELL_OPTIONS = -DNDEBUG=1 \
-DSQLITE_DQS=0 \
-DSQLITE_THREADSAFE=0 \
-DSQLITE_DEFAULT_MEMSTATUS=0 \
-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \
-DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
-DSQLITE_OMIT_DECLTYPE \
-DSQLITE_OMIT_DEPRECATED \
-DSQLITE_OMIT_PROGRESS_CALLBACK \
-DSQLITE_OMIT_SHARED_CACHE \
-DSQLITE_OMIT_LOAD_EXTENSION \
-DSQLITE_MAX_EXPR_DEPTH=0 \
-DSQLITE_USE_ALLOCA \
-DSQLITE_ENABLE_LOCKING_STYLE=0 \
-DSQLITE_DEFAULT_FILE_FORMAT=4 \
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
-DSQLITE_ENABLE_FTS4 \
-DSQLITE_ENABLE_DBSTAT_VTAB \
-DSQLITE_ENABLE_JSON1 \
-DSQLITE_ENABLE_FTS5 \
-DSQLITE_ENABLE_STMTVTAB \
-DSQLITE_HAVE_ZLIB \
-DSQLITE_INTROSPECTION_PRAGMAS \
-DSQLITE_ENABLE_DBPAGE_VTAB \
-DSQLITE_TRUSTED_SCHEMA=0 \
-Dmain=sqlite3_shell \
-DSQLITE_SHELL_IS_UTF8=1 \
-DSQLITE_OMIT_LOAD_EXTENSION=1 \
-DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \
-DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname \
-DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc
|
| ︙ | ︙ | |||
685 686 687 688 689 690 691 | $(OBJDIR)/th_lang.o \ $(OBJDIR)/th_tcl.o \ $(OBJDIR)/cson_amalgamation.o $(APPNAME): $(OBJDIR)/headers $(OBJDIR)/codecheck1 $(OBJ) $(EXTRAOBJ) $(OBJDIR)/codecheck1 $(TRANS_SRC) | | | > > | 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 | $(OBJDIR)/th_lang.o \ $(OBJDIR)/th_tcl.o \ $(OBJDIR)/cson_amalgamation.o $(APPNAME): $(OBJDIR)/headers $(OBJDIR)/codecheck1 $(OBJ) $(EXTRAOBJ) $(OBJDIR)/codecheck1 $(TRANS_SRC) $(TCC) $(TCCFLAGS) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB) # This rule prevents make from using its default rules to try build # an executable named "manifest" out of the file named "manifest.c" # $(SRCDIR)/../manifest: # noop clean: -rm -rf $(OBJDIR)/* $(APPNAME) $(OBJDIR)/page_index.h: $(TRANS_SRC) $(OBJDIR)/mkindex $(OBJDIR)/mkindex $(TRANS_SRC) >$@ $(OBJDIR)/builtin_data.h: $(OBJDIR)/mkbuiltin $(EXTRA_FILES) $(OBJDIR)/mkbuiltin --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ $(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h $(OBJDIR)/makeheaders $(OBJDIR)/add_.c:$(OBJDIR)/add.h \ $(OBJDIR)/ajax_.c:$(OBJDIR)/ajax.h \ $(OBJDIR)/alerts_.c:$(OBJDIR)/alerts.h \ $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \ $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \ $(OBJDIR)/backlink_.c:$(OBJDIR)/backlink.h \ $(OBJDIR)/backoffice_.c:$(OBJDIR)/backoffice.h \ $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \ $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \ $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \ $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \ $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h \ $(OBJDIR)/builtin_.c:$(OBJDIR)/builtin.h \ |
| ︙ | ︙ | |||
743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 | $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \ $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \ $(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \ $(OBJDIR)/event_.c:$(OBJDIR)/event.h \ $(OBJDIR)/export_.c:$(OBJDIR)/export.h \ $(OBJDIR)/extcgi_.c:$(OBJDIR)/extcgi.h \ $(OBJDIR)/file_.c:$(OBJDIR)/file.h \ $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \ $(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \ $(OBJDIR)/forum_.c:$(OBJDIR)/forum.h \ $(OBJDIR)/fshell_.c:$(OBJDIR)/fshell.h \ $(OBJDIR)/fusefs_.c:$(OBJDIR)/fusefs.h \ $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h \ $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h \ $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h \ $(OBJDIR)/hname_.c:$(OBJDIR)/hname.h \ $(OBJDIR)/http_.c:$(OBJDIR)/http.h \ $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \ $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \ | > > | 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 | $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \ $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \ $(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \ $(OBJDIR)/event_.c:$(OBJDIR)/event.h \ $(OBJDIR)/export_.c:$(OBJDIR)/export.h \ $(OBJDIR)/extcgi_.c:$(OBJDIR)/extcgi.h \ $(OBJDIR)/file_.c:$(OBJDIR)/file.h \ $(OBJDIR)/fileedit_.c:$(OBJDIR)/fileedit.h \ $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \ $(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \ $(OBJDIR)/forum_.c:$(OBJDIR)/forum.h \ $(OBJDIR)/fshell_.c:$(OBJDIR)/fshell.h \ $(OBJDIR)/fusefs_.c:$(OBJDIR)/fusefs.h \ $(OBJDIR)/fuzz_.c:$(OBJDIR)/fuzz.h \ $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h \ $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h \ $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h \ $(OBJDIR)/hname_.c:$(OBJDIR)/hname.h \ $(OBJDIR)/http_.c:$(OBJDIR)/http.h \ $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \ $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \ |
| ︙ | ︙ | |||
819 820 821 822 823 824 825 826 827 828 829 830 831 832 | $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \ $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \ $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \ $(OBJDIR)/style_.c:$(OBJDIR)/style.h \ $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \ $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h \ $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h \ $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h \ $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h \ $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h \ $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h \ $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h \ $(OBJDIR)/unicode_.c:$(OBJDIR)/unicode.h \ $(OBJDIR)/unversioned_.c:$(OBJDIR)/unversioned.h \ | > | 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 | $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \ $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \ $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \ $(OBJDIR)/style_.c:$(OBJDIR)/style.h \ $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \ $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h \ $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h \ $(OBJDIR)/terminal_.c:$(OBJDIR)/terminal.h \ $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h \ $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h \ $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h \ $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h \ $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h \ $(OBJDIR)/unicode_.c:$(OBJDIR)/unicode.h \ $(OBJDIR)/unversioned_.c:$(OBJDIR)/unversioned.h \ |
| ︙ | ︙ | |||
856 857 858 859 860 861 862 863 864 865 866 867 868 869 | $(OBJDIR)/add_.c: $(SRCDIR)/add.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/add.c >$@ $(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c $(OBJDIR)/add.h: $(OBJDIR)/headers $(OBJDIR)/alerts_.c: $(SRCDIR)/alerts.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/alerts.c >$@ $(OBJDIR)/alerts.o: $(OBJDIR)/alerts_.c $(OBJDIR)/alerts.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/alerts.o -c $(OBJDIR)/alerts_.c | > > > > > > > > | 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 | $(OBJDIR)/add_.c: $(SRCDIR)/add.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/add.c >$@ $(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c $(OBJDIR)/add.h: $(OBJDIR)/headers $(OBJDIR)/ajax_.c: $(SRCDIR)/ajax.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/ajax.c >$@ $(OBJDIR)/ajax.o: $(OBJDIR)/ajax_.c $(OBJDIR)/ajax.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/ajax.o -c $(OBJDIR)/ajax_.c $(OBJDIR)/ajax.h: $(OBJDIR)/headers $(OBJDIR)/alerts_.c: $(SRCDIR)/alerts.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/alerts.c >$@ $(OBJDIR)/alerts.o: $(OBJDIR)/alerts_.c $(OBJDIR)/alerts.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/alerts.o -c $(OBJDIR)/alerts_.c |
| ︙ | ︙ | |||
880 881 882 883 884 885 886 887 888 889 890 891 892 893 | $(OBJDIR)/attach_.c: $(SRCDIR)/attach.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/attach.c >$@ $(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c $(OBJDIR)/attach.h: $(OBJDIR)/headers $(OBJDIR)/backoffice_.c: $(SRCDIR)/backoffice.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/backoffice.c >$@ $(OBJDIR)/backoffice.o: $(OBJDIR)/backoffice_.c $(OBJDIR)/backoffice.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/backoffice.o -c $(OBJDIR)/backoffice_.c | > > > > > > > > | 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 | $(OBJDIR)/attach_.c: $(SRCDIR)/attach.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/attach.c >$@ $(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c $(OBJDIR)/attach.h: $(OBJDIR)/headers $(OBJDIR)/backlink_.c: $(SRCDIR)/backlink.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/backlink.c >$@ $(OBJDIR)/backlink.o: $(OBJDIR)/backlink_.c $(OBJDIR)/backlink.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/backlink.o -c $(OBJDIR)/backlink_.c $(OBJDIR)/backlink.h: $(OBJDIR)/headers $(OBJDIR)/backoffice_.c: $(SRCDIR)/backoffice.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/backoffice.c >$@ $(OBJDIR)/backoffice.o: $(OBJDIR)/backoffice_.c $(OBJDIR)/backoffice.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/backoffice.o -c $(OBJDIR)/backoffice_.c |
| ︙ | ︙ | |||
1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 | $(OBJDIR)/file_.c: $(SRCDIR)/file.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/file.c >$@ $(OBJDIR)/file.o: $(OBJDIR)/file_.c $(OBJDIR)/file.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/file.o -c $(OBJDIR)/file_.c $(OBJDIR)/file.h: $(OBJDIR)/headers $(OBJDIR)/finfo_.c: $(SRCDIR)/finfo.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/finfo.c >$@ $(OBJDIR)/finfo.o: $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/finfo.o -c $(OBJDIR)/finfo_.c | > > > > > > > > | 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 | $(OBJDIR)/file_.c: $(SRCDIR)/file.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/file.c >$@ $(OBJDIR)/file.o: $(OBJDIR)/file_.c $(OBJDIR)/file.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/file.o -c $(OBJDIR)/file_.c $(OBJDIR)/file.h: $(OBJDIR)/headers $(OBJDIR)/fileedit_.c: $(SRCDIR)/fileedit.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/fileedit.c >$@ $(OBJDIR)/fileedit.o: $(OBJDIR)/fileedit_.c $(OBJDIR)/fileedit.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/fileedit.o -c $(OBJDIR)/fileedit_.c $(OBJDIR)/fileedit.h: $(OBJDIR)/headers $(OBJDIR)/finfo_.c: $(SRCDIR)/finfo.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/finfo.c >$@ $(OBJDIR)/finfo.o: $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/finfo.o -c $(OBJDIR)/finfo_.c |
| ︙ | ︙ | |||
1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 | $(OBJDIR)/fusefs_.c: $(SRCDIR)/fusefs.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/fusefs.c >$@ $(OBJDIR)/fusefs.o: $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/fusefs.o -c $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h: $(OBJDIR)/headers $(OBJDIR)/glob_.c: $(SRCDIR)/glob.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/glob.c >$@ $(OBJDIR)/glob.o: $(OBJDIR)/glob_.c $(OBJDIR)/glob.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/glob.o -c $(OBJDIR)/glob_.c | > > > > > > > > | 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 | $(OBJDIR)/fusefs_.c: $(SRCDIR)/fusefs.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/fusefs.c >$@ $(OBJDIR)/fusefs.o: $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/fusefs.o -c $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h: $(OBJDIR)/headers $(OBJDIR)/fuzz_.c: $(SRCDIR)/fuzz.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/fuzz.c >$@ $(OBJDIR)/fuzz.o: $(OBJDIR)/fuzz_.c $(OBJDIR)/fuzz.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/fuzz.o -c $(OBJDIR)/fuzz_.c $(OBJDIR)/fuzz.h: $(OBJDIR)/headers $(OBJDIR)/glob_.c: $(SRCDIR)/glob.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/glob.c >$@ $(OBJDIR)/glob.o: $(OBJDIR)/glob_.c $(OBJDIR)/glob.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/glob.o -c $(OBJDIR)/glob_.c |
| ︙ | ︙ | |||
1740 1741 1742 1743 1744 1745 1746 | $(XTCC) -o $(OBJDIR)/statrep.o -c $(OBJDIR)/statrep_.c $(OBJDIR)/statrep.h: $(OBJDIR)/headers $(OBJDIR)/style_.c: $(SRCDIR)/style.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/style.c >$@ | | | 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 | $(XTCC) -o $(OBJDIR)/statrep.o -c $(OBJDIR)/statrep_.c $(OBJDIR)/statrep.h: $(OBJDIR)/headers $(OBJDIR)/style_.c: $(SRCDIR)/style.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/style.c >$@ $(OBJDIR)/style.o: $(OBJDIR)/style_.c $(OBJDIR)/style.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/style.o -c $(OBJDIR)/style_.c $(OBJDIR)/style.h: $(OBJDIR)/headers $(OBJDIR)/sync_.c: $(SRCDIR)/sync.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/sync.c >$@ |
| ︙ | ︙ | |||
1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 | $(OBJDIR)/tar_.c: $(SRCDIR)/tar.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/tar.c >$@ $(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tar.o -c $(OBJDIR)/tar_.c $(OBJDIR)/tar.h: $(OBJDIR)/headers $(OBJDIR)/th_main_.c: $(SRCDIR)/th_main.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/th_main.c >$@ $(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/th_main.o -c $(OBJDIR)/th_main_.c | > > > > > > > > | 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 | $(OBJDIR)/tar_.c: $(SRCDIR)/tar.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/tar.c >$@ $(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tar.o -c $(OBJDIR)/tar_.c $(OBJDIR)/tar.h: $(OBJDIR)/headers $(OBJDIR)/terminal_.c: $(SRCDIR)/terminal.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/terminal.c >$@ $(OBJDIR)/terminal.o: $(OBJDIR)/terminal_.c $(OBJDIR)/terminal.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/terminal.o -c $(OBJDIR)/terminal_.c $(OBJDIR)/terminal.h: $(OBJDIR)/headers $(OBJDIR)/th_main_.c: $(SRCDIR)/th_main.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/th_main.c >$@ $(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/th_main.o -c $(OBJDIR)/th_main_.c |
| ︙ | ︙ |
| ︙ | ︙ | |||
27 28 29 30 31 32 33 | ** liability, whether in contract, strict liability, or tort (including ** negligence or otherwise) arising in any way out of the use of this ** software, even if advised of the possibility of such damage. ** ** 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. | < | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | ** liability, whether in contract, strict liability, or tort (including ** negligence or otherwise) arising in any way out of the use of this ** software, even if advised of the possibility of such damage. ** ** 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. */ #include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <memory.h> #include <sys/stat.h> #include <assert.h> |
| ︙ | ︙ | |||
1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 |
}else if( type & PS_Private ){
StringAppend(&str, "private:\n", 0);
pDecl->extraType = PS_Private;
}
}
StringAppend(&str, " ", 0);
zDecl = TokensToString(pFirst, pLast, ";\n", pClass, 2);
StringAppend(&str, zDecl, 0);
SafeFree(zDecl);
pDecl->zExtra = StrDup(StringGet(&str), 0);
StringReset(&str);
return 0;
}
| > > > > > > > > > > | 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 |
}else if( type & PS_Private ){
StringAppend(&str, "private:\n", 0);
pDecl->extraType = PS_Private;
}
}
StringAppend(&str, " ", 0);
zDecl = TokensToString(pFirst, pLast, ";\n", pClass, 2);
if(strncmp(zDecl, pClass->zText, pClass->nText)==0){
/* If member initializer list is found after a constructor,
** skip that part. */
char * colon = strchr(zDecl, ':');
if(colon!=0 && colon[1]!=0){
*colon++ = ';';
*colon++ = '\n';
*colon = 0;
}
}
StringAppend(&str, zDecl, 0);
SafeFree(zDecl);
pDecl->zExtra = StrDup(StringGet(&str), 0);
StringReset(&str);
return 0;
}
|
| ︙ | ︙ | |||
1782 1783 1784 1785 1786 1787 1788 |
}
pName = FindDeclName(pFirst,pLast);
if( pName==0 ){
fprintf(stderr,"%s:%d: Malformed function or procedure definition.\n",
zFilename, pFirst->nLine);
return 1;
}
| > > > | | 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 |
}
pName = FindDeclName(pFirst,pLast);
if( pName==0 ){
fprintf(stderr,"%s:%d: Malformed function or procedure definition.\n",
zFilename, pFirst->nLine);
return 1;
}
if( strncmp(pName->zText,"main",pName->nText)==0 ){
/* skip main() decl. */
return 0;
}
/*
** At this point we've isolated a procedure declaration between pFirst
** and pLast with the name pName.
*/
#ifdef DEBUG
if( debugMask & PARSER ){
printf("**** Found routine: %.*s on line %d...\n", pName->nText,
|
| ︙ | ︙ | |||
3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 |
zNew[j] = malloc( n + 1 );
if( zNew[j] ){
strcpy( zNew[j], zBuf );
}
}
}
}
newArgc = argc + nNew - 1;
for(i=0; i<=index; i++){
zNew[i] = argv[i];
}
for(i=nNew + index + 1; i<newArgc; i++){
zNew[i] = argv[i + 1 - nNew];
}
| > | 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 |
zNew[j] = malloc( n + 1 );
if( zNew[j] ){
strcpy( zNew[j], zBuf );
}
}
}
}
fclose(in);
newArgc = argc + nNew - 1;
for(i=0; i<=index; i++){
zNew[i] = argv[i];
}
for(i=nNew + index + 1; i<newArgc; i++){
zNew[i] = argv[i + 1 - nNew];
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
341 342 343 344 345 346 347 | Finally, makeheaders also includes a “<code>-doc</code>” option. This command line option prevents makeheaders from generating any headers at all. Instead, makeheaders will write to standard output information about every definition and declaration that it encounters in its scan of source files. The information output includes the type of the definition or | | | 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 | Finally, makeheaders also includes a “<code>-doc</code>” option. This command line option prevents makeheaders from generating any headers at all. Instead, makeheaders will write to standard output information about every definition and declaration that it encounters in its scan of source files. The information output includes the type of the definition or declaration and any comment that precedes the definition or declaration. The output is in a format that can be easily parsed, and is intended to be read by another program that will generate documentation about the program. We'll talk more about this feature later. </p> |
| ︙ | ︙ | |||
397 398 399 400 401 402 403 | named “<code>alpha.h</code>”. For that reason, you don't want to use that name for any of the .h files you write since that will prevent makeheaders from generating the .h file automatically. </p> <p> | | | 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 | named “<code>alpha.h</code>”. For that reason, you don't want to use that name for any of the .h files you write since that will prevent makeheaders from generating the .h file automatically. </p> <p> The structure of a .c file intended for use with makeheaders is very simple. All you have to do is add a single “<code>#include</code>” to the top of the file that sources the header file that makeheaders will generate. Hence, the beginning of a source file named “<code>alpha.c</code>” might look something like this: </p> |
| ︙ | ︙ | |||
589 590 591 592 593 594 595 | <a name="H0009"></a> <h3>3.3 How To Avoid Having To Write Any Header Files</h3> <p> In my experience, large projects work better if all of the manually written code is placed in .c files and all .h files are generated automatically. | | | | 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 | <a name="H0009"></a> <h3>3.3 How To Avoid Having To Write Any Header Files</h3> <p> In my experience, large projects work better if all of the manually written code is placed in .c files and all .h files are generated automatically. This is slightly different from the traditional C method of placing the interface in .h files and the implementation in .c files, but it is a refreshing change that brings a noticeable improvement to the coding experience. Others, I believe, share this view since I've noticed recent languages (ex: java, tcl, perl, awk) tend to support the one-file approach to coding as the only option. </p> <p> |
| ︙ | ︙ | |||
617 618 619 620 621 622 623 | it were a .h file by enclosing that part of the .c file within: <pre> #if INTERFACE #endif </pre> Thus any structure definitions that appear after the “<code>#if INTERFACE</code>” but before the corresponding | | | 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 | it were a .h file by enclosing that part of the .c file within: <pre> #if INTERFACE #endif </pre> Thus any structure definitions that appear after the “<code>#if INTERFACE</code>” but before the corresponding “<code>#endif</code>” are eligible to be copied into the automatically generated .h files of other .c files. </p> <p> If you use the “<code>#if INTERFACE</code>” mechanism in a .c file, then the generated header for that .c file will contain a line |
| ︙ | ︙ | |||
682 683 684 685 686 687 688 | </p> <p> That isn't the complete truth, actually. The semantics of C are such that once an object becomes visible outside of a single source file, it is also visible to any user of the library that is made from the source file. | | | 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 | </p> <p> That isn't the complete truth, actually. The semantics of C are such that once an object becomes visible outside of a single source file, it is also visible to any user of the library that is made from the source file. Makeheaders can not prevent outsiders from using non-exported resources, but it can discourage the practice by refusing to provide prototypes and declarations for the services it does not want to export. Thus the only real effect of the making an object exportable is to include it in the output makeheaders generates when it is run using the -H command line option. This is not a perfect solution, but it works well in practice. </p> |
| ︙ | ︙ | |||
866 867 868 869 870 871 872 | v1 = 0; } </pre></blockquote> <p> The first form is preferred because only a single declaration of the constructor is required. The second form requires two declarations, | | | 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 | v1 = 0; } </pre></blockquote> <p> The first form is preferred because only a single declaration of the constructor is required. The second form requires two declarations, one in the class definition and one on the definition of the constructor. </p> <h4>3.6.1 C++ Limitations</h4> <p> Makeheaders does not understand more recent C++ syntax such as templates and namespaces. |
| ︙ | ︙ | |||
1071 1072 1073 1074 1075 1076 1077 |
<ul>
<li> The name of the object.
<li> The type of the object. (Structure, typedef, macro, etc.)
<li> Flags to indicate if the declaration is exported (contained within
an EXPORT_INTERFACE block) or local (contained with LOCAL_INTERFACE).
<li> A flag to indicate if the object is declared in a C++ file.
<li> The name of the file in which the object was declared.
| | | 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 |
<ul>
<li> The name of the object.
<li> The type of the object. (Structure, typedef, macro, etc.)
<li> Flags to indicate if the declaration is exported (contained within
an EXPORT_INTERFACE block) or local (contained with LOCAL_INTERFACE).
<li> A flag to indicate if the object is declared in a C++ file.
<li> The name of the file in which the object was declared.
<li> The complete text of any block comment that precedes the declarations.
<li> If the declaration occurred inside a preprocessor conditional
(“<code>#if</code>”) then the text of that conditional is
provided.
<li> The complete text of a declaration for the object.
</ul>
The exact output format will not be described here.
It is simple to understand and parse and should be obvious to
|
| ︙ | ︙ |
| ︙ | ︙ | |||
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# project, simply add the basename to this list and rerun this script.
#
# Set the separate extra_files variable further down for how to add non-C
# files, such as string and BLOB resources.
#
set src {
add
alerts
allrepo
attach
backoffice
bag
bisect
blob
branch
browse
builtin
| > > | 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# project, simply add the basename to this list and rerun this script.
#
# Set the separate extra_files variable further down for how to add non-C
# files, such as string and BLOB resources.
#
set src {
add
ajax
alerts
allrepo
attach
backlink
backoffice
bag
bisect
blob
branch
browse
builtin
|
| ︙ | ︙ | |||
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | doc encode etag event extcgi export file finfo foci forum fshell fusefs glob graph gzip hname http http_socket http_transport | > > | 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | doc encode etag event extcgi export file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname http http_socket http_transport |
| ︙ | ︙ | |||
137 138 139 140 141 142 143 144 145 146 147 148 149 150 | stash stat statrep style sync tag tar th_main timeline tkt tktsetup undo unicode unversioned | > | 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned |
| ︙ | ︙ | |||
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# Additional resource files that get built into the executable.
#
set extra_files {
diff.tcl
markdown.md
wiki.wiki
*.js
../skins/*/*.txt
}
# Options used to compile the included SQLite library.
#
set SQLITE_OPTIONS {
-DNDEBUG=1
-DSQLITE_DQS=0
-DSQLITE_THREADSAFE=0
-DSQLITE_DEFAULT_MEMSTATUS=0
-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1
-DSQLITE_LIKE_DOESNT_MATCH_BLOBS
-DSQLITE_OMIT_DECLTYPE
-DSQLITE_OMIT_DEPRECATED
| > > > < > | 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 |
# Additional resource files that get built into the executable.
#
set extra_files {
diff.tcl
markdown.md
wiki.wiki
*.js
default.css
style.*.css
../skins/*/*.txt
sounds/*.wav
}
# Options used to compile the included SQLite library.
#
set SQLITE_OPTIONS {
-DNDEBUG=1
-DSQLITE_DQS=0
-DSQLITE_THREADSAFE=0
-DSQLITE_DEFAULT_MEMSTATUS=0
-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1
-DSQLITE_LIKE_DOESNT_MATCH_BLOBS
-DSQLITE_OMIT_DECLTYPE
-DSQLITE_OMIT_DEPRECATED
-DSQLITE_OMIT_PROGRESS_CALLBACK
-DSQLITE_OMIT_SHARED_CACHE
-DSQLITE_OMIT_LOAD_EXTENSION
-DSQLITE_MAX_EXPR_DEPTH=0
-DSQLITE_USE_ALLOCA
-DSQLITE_ENABLE_LOCKING_STYLE=0
-DSQLITE_DEFAULT_FILE_FORMAT=4
-DSQLITE_ENABLE_EXPLAIN_COMMENTS
-DSQLITE_ENABLE_FTS4
-DSQLITE_ENABLE_DBSTAT_VTAB
-DSQLITE_ENABLE_JSON1
-DSQLITE_ENABLE_FTS5
-DSQLITE_ENABLE_STMTVTAB
-DSQLITE_HAVE_ZLIB
-DSQLITE_INTROSPECTION_PRAGMAS
-DSQLITE_ENABLE_DBPAGE_VTAB
-DSQLITE_TRUSTED_SCHEMA=0
}
#lappend SQLITE_OPTIONS -DSQLITE_ENABLE_FTS3=1
#lappend SQLITE_OPTIONS -DSQLITE_ENABLE_STAT4
#lappend SQLITE_OPTIONS -DSQLITE_WIN32_NO_ANSI
#lappend SQLITE_OPTIONS -DSQLITE_WINNT_MAX_PATH_CHARS=4096
# Options used to compile the included SQLite shell.
|
| ︙ | ︙ | |||
308 309 310 311 312 313 314 |
writeln -nonewline " \\\n \$(OBJDIR)/${s}_.c"
}
writeln "\n"
writeln -nonewline "OBJ ="
foreach s [lsort $src] {
writeln -nonewline " \\\n \$(OBJDIR)/$s.o"
}
| < < < | 316 317 318 319 320 321 322 323 324 325 326 327 328 329 |
writeln -nonewline " \\\n \$(OBJDIR)/${s}_.c"
}
writeln "\n"
writeln -nonewline "OBJ ="
foreach s [lsort $src] {
writeln -nonewline " \\\n \$(OBJDIR)/$s.o"
}
writeln [string map [list \
<<<SQLITE_OPTIONS>>> [join $SQLITE_OPTIONS " \\\n "] \
<<<SHELL_OPTIONS>>> [join $SHELL_OPTIONS " \\\n "] \
<<<MINIZ_OPTIONS>>> [join $MINIZ_OPTIONS " \\\n "]] {
all: $(OBJDIR) $(APPNAME)
|
| ︙ | ︙ | |||
343 344 345 346 347 348 349 | $(OBJDIR)/mkbuiltin: $(SRCDIR)/mkbuiltin.c $(XBCC) -o $(OBJDIR)/mkbuiltin $(SRCDIR)/mkbuiltin.c $(OBJDIR)/mkversion: $(SRCDIR)/mkversion.c $(XBCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c | < < < | 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | $(OBJDIR)/mkbuiltin: $(SRCDIR)/mkbuiltin.c $(XBCC) -o $(OBJDIR)/mkbuiltin $(SRCDIR)/mkbuiltin.c $(OBJDIR)/mkversion: $(SRCDIR)/mkversion.c $(XBCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c $(OBJDIR)/codecheck1: $(SRCDIR)/codecheck1.c $(XBCC) -o $(OBJDIR)/codecheck1 $(SRCDIR)/codecheck1.c # Run the test suite. # Other flags that can be included in TESTFLAGS are: # # -halt Stop testing after the first failed test |
| ︙ | ︙ | |||
370 371 372 373 374 375 376 | $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(TESTFLAGS) $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion $(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid \ $(SRCDIR)/../manifest \ $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h | < < < | 372 373 374 375 376 377 378 379 380 381 382 383 384 385 | $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(TESTFLAGS) $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion $(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid \ $(SRCDIR)/../manifest \ $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h # Setup the options used to compile the included SQLite library. SQLITE_OPTIONS = <<<SQLITE_OPTIONS>>> # Setup the options used to compile the included SQLite shell. SHELL_OPTIONS = <<<SHELL_OPTIONS>>> # Setup the options used to compile the included miniz library. |
| ︙ | ︙ | |||
440 441 442 443 444 445 446 |
$(OBJDIR)/th_tcl.o <<<NEXT_LINE>>>
$(OBJDIR)/cson_amalgamation.o
}]
writeln {
$(APPNAME): $(OBJDIR)/headers $(OBJDIR)/codecheck1 $(OBJ) $(EXTRAOBJ)
$(OBJDIR)/codecheck1 $(TRANS_SRC)
| | | 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 |
$(OBJDIR)/th_tcl.o <<<NEXT_LINE>>>
$(OBJDIR)/cson_amalgamation.o
}]
writeln {
$(APPNAME): $(OBJDIR)/headers $(OBJDIR)/codecheck1 $(OBJ) $(EXTRAOBJ)
$(OBJDIR)/codecheck1 $(TRANS_SRC)
$(TCC) $(TCCFLAGS) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB)
# This rule prevents make from using its default rules to try build
# an executable named "manifest" out of the file named "manifest.c"
#
$(SRCDIR)/../manifest:
# noop
|
| ︙ | ︙ | |||
469 470 471 472 473 474 475 | set mhargs [string map [list <<<NEXT_LINE>>> \\\n\t] $mhargs] writeln "\$(OBJDIR)/page_index.h: \$(TRANS_SRC) \$(OBJDIR)/mkindex" writeln "\t\$(OBJDIR)/mkindex \$(TRANS_SRC) >\$@\n" writeln "\$(OBJDIR)/builtin_data.h: \$(OBJDIR)/mkbuiltin \$(EXTRA_FILES)" writeln "\t\$(OBJDIR)/mkbuiltin --prefix \$(SRCDIR)/ \$(EXTRA_FILES) >\$@\n" | | > < | 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 |
set mhargs [string map [list <<<NEXT_LINE>>> \\\n\t] $mhargs]
writeln "\$(OBJDIR)/page_index.h: \$(TRANS_SRC) \$(OBJDIR)/mkindex"
writeln "\t\$(OBJDIR)/mkindex \$(TRANS_SRC) >\$@\n"
writeln "\$(OBJDIR)/builtin_data.h: \$(OBJDIR)/mkbuiltin \$(EXTRA_FILES)"
writeln "\t\$(OBJDIR)/mkbuiltin --prefix \$(SRCDIR)/ \$(EXTRA_FILES) >\$@\n"
writeln "\$(OBJDIR)/headers:\t\$(OBJDIR)/page_index.h \$(OBJDIR)/builtin_data.h \$(OBJDIR)/makeheaders \$(OBJDIR)/VERSION.h"
writeln "\t\$(OBJDIR)/makeheaders $mhargs"
writeln "\ttouch \$(OBJDIR)/headers"
writeln "\$(OBJDIR)/headers: Makefile"
writeln "\$(OBJDIR)/json.o \$(OBJDIR)/json_artifact.o \$(OBJDIR)/json_branch.o \$(OBJDIR)/json_config.o \$(OBJDIR)/json_diff.o \$(OBJDIR)/json_dir.o \$(OBJDIR)/json_finfo.o \$(OBJDIR)/json_login.o \$(OBJDIR)/json_query.o \$(OBJDIR)/json_report.o \$(OBJDIR)/json_status.o \$(OBJDIR)/json_tag.o \$(OBJDIR)/json_timeline.o \$(OBJDIR)/json_user.o \$(OBJDIR)/json_wiki.o : \$(SRCDIR)/json_detail.h"
writeln "Makefile:"
set extra_h(dispatch) " \$(OBJDIR)/page_index.h "
set extra_h(builtin) " \$(OBJDIR)/builtin_data.h "
foreach s [lsort $src] {
writeln "\$(OBJDIR)/${s}_.c:\t\$(SRCDIR)/$s.c \$(OBJDIR)/translate"
writeln "\t\$(OBJDIR)/translate \$(SRCDIR)/$s.c >\$@\n"
writeln "\$(OBJDIR)/$s.o:\t\$(OBJDIR)/${s}_.c \$(OBJDIR)/$s.h$extra_h($s)\$(SRCDIR)/config.h"
writeln "\t\$(XTCC) -o \$(OBJDIR)/$s.o -c \$(OBJDIR)/${s}_.c\n"
writeln "\$(OBJDIR)/$s.h:\t\$(OBJDIR)/headers\n"
|
| ︙ | ︙ | |||
711 712 713 714 715 716 717 | endif #### The directories where the OpenSSL include and library files are located. # The recommended usage here is to use the Sysinternals junction tool # to create a hard link between an "openssl-1.x" sub-directory of the # Fossil source code directory and the target OpenSSL source directory. # | | | 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 | endif #### The directories where the OpenSSL include and library files are located. # The recommended usage here is to use the Sysinternals junction tool # to create a hard link between an "openssl-1.x" sub-directory of the # Fossil source code directory and the target OpenSSL source directory. # OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1g OPENSSLINCDIR = $(OPENSSLDIR)/include OPENSSLLIBDIR = $(OPENSSLDIR) #### Either the directory where the Tcl library is installed or the Tcl # source code directory resides (depending on the value of the macro # FOSSIL_TCL_SOURCE). If this points to the Tcl install directory, # this directory must have "include" and "lib" sub-directories. If |
| ︙ | ︙ | |||
1008 1009 1010 1011 1012 1013 1014 | # ifdef USE_WINDOWS TRANSLATE = $(subst /,\,$(OBJDIR)/translate.exe) MAKEHEADERS = $(subst /,\,$(OBJDIR)/makeheaders.exe) MKINDEX = $(subst /,\,$(OBJDIR)/mkindex.exe) MKBUILTIN = $(subst /,\,$(OBJDIR)/mkbuiltin.exe) MKVERSION = $(subst /,\,$(OBJDIR)/mkversion.exe) | < < | | 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 |
#
ifdef USE_WINDOWS
TRANSLATE = $(subst /,\,$(OBJDIR)/translate.exe)
MAKEHEADERS = $(subst /,\,$(OBJDIR)/makeheaders.exe)
MKINDEX = $(subst /,\,$(OBJDIR)/mkindex.exe)
MKBUILTIN = $(subst /,\,$(OBJDIR)/mkbuiltin.exe)
MKVERSION = $(subst /,\,$(OBJDIR)/mkversion.exe)
CODECHECK1 = $(subst /,\,$(OBJDIR)/codecheck1.exe)
CAT = type
CP = copy
GREP = find
MV = copy
RM = del /Q
MKDIR = -mkdir
RMDIR = rmdir /S /Q
else
TRANSLATE = $(OBJDIR)/translate.exe
MAKEHEADERS = $(OBJDIR)/makeheaders.exe
MKINDEX = $(OBJDIR)/mkindex.exe
MKBUILTIN = $(OBJDIR)/mkbuiltin.exe
MKVERSION = $(OBJDIR)/mkversion.exe
CODECHECK1 = $(OBJDIR)/codecheck1.exe
CAT = cat
CP = cp
GREP = grep
MV = mv
RM = rm -f
MKDIR = -mkdir -p
RMDIR = rm -rf
endif}
writeln {
all: $(OBJDIR) $(APPNAME)
$(OBJDIR)/fossil.o: $(SRCDIR)/../win/fossil.rc $(OBJDIR)/VERSION.h
ifdef USE_WINDOWS
$(CAT) $(subst /,\,$(SRCDIR)\miniz.c) | $(GREP) "define MZ_VERSION" > $(subst /,\,$(OBJDIR)\minizver.h)
$(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.rc) $(subst /,\,$(OBJDIR))
$(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.ico) $(subst /,\,$(OBJDIR))
$(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.exe.manifest) $(subst /,\,$(OBJDIR))
else
$(CAT) $(SRCDIR)/miniz.c | $(GREP) "define MZ_VERSION" > $(OBJDIR)/minizver.h
|
| ︙ | ︙ | |||
1082 1083 1084 1085 1086 1087 1088 | $(MKBUILTIN): $(SRCDIR)/mkbuiltin.c $(XBCC) -o $@ $(SRCDIR)/mkbuiltin.c $(MKVERSION): $(SRCDIR)/mkversion.c $(XBCC) -o $@ $(SRCDIR)/mkversion.c | < < < < < < | 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 | $(MKBUILTIN): $(SRCDIR)/mkbuiltin.c $(XBCC) -o $@ $(SRCDIR)/mkbuiltin.c $(MKVERSION): $(SRCDIR)/mkversion.c $(XBCC) -o $@ $(SRCDIR)/mkversion.c $(CODECHECK1): $(SRCDIR)/codecheck1.c $(XBCC) -o $@ $(SRCDIR)/codecheck1.c # WARNING. DANGER. Running the test suite 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: $(OBJDIR) $(APPNAME) $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(MKVERSION) $(MKVERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$@ # The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set # to 1. If it is set to 1, then there is no need to build or link # the sqlite3.o object. Instead, the system SQLite will be linked # using -lsqlite3. SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o SQLITE3_OBJ.1 = SQLITE3_OBJ. = $(SQLITE3_OBJ.0) |
| ︙ | ︙ | |||
1225 1226 1227 1228 1229 1230 1231 | append mhargs " \\\n\t\t\$(OBJDIR)/VERSION.h" writeln "\$(OBJDIR)/page_index.h: \$(TRANS_SRC) \$(MKINDEX)" writeln "\t\$(MKINDEX) \$(TRANS_SRC) >\$@\n" writeln "\$(OBJDIR)/builtin_data.h:\t\$(MKBUILTIN) \$(EXTRA_FILES)" writeln "\t\$(MKBUILTIN) --prefix \$(SRCDIR)/ \$(EXTRA_FILES) >\$@\n" | | < | 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 |
append mhargs " \\\n\t\t\$(OBJDIR)/VERSION.h"
writeln "\$(OBJDIR)/page_index.h: \$(TRANS_SRC) \$(MKINDEX)"
writeln "\t\$(MKINDEX) \$(TRANS_SRC) >\$@\n"
writeln "\$(OBJDIR)/builtin_data.h:\t\$(MKBUILTIN) \$(EXTRA_FILES)"
writeln "\t\$(MKBUILTIN) --prefix \$(SRCDIR)/ \$(EXTRA_FILES) >\$@\n"
writeln "\$(OBJDIR)/headers:\t\$(OBJDIR)/page_index.h \$(OBJDIR)/builtin_data.h \$(MAKEHEADERS) \$(OBJDIR)/VERSION.h"
writeln "\t\$(MAKEHEADERS) $mhargs"
writeln "\techo Done >\$(OBJDIR)/headers\n"
writeln "\$(OBJDIR)/headers: Makefile\n"
writeln "Makefile:\n"
set extra_h(main) " \$(OBJDIR)/page_index.h "
set extra_h(builtin) " \$(OBJDIR)/builtin_data.h "
foreach s [lsort $src] {
writeln "\$(OBJDIR)/${s}_.c:\t\$(SRCDIR)/$s.c \$(TRANSLATE)"
writeln "\t\$(TRANSLATE) \$(SRCDIR)/$s.c >\$@\n"
writeln "\$(OBJDIR)/$s.o:\t\$(OBJDIR)/${s}_.c \$(OBJDIR)/$s.h$extra_h($s)\$(SRCDIR)/config.h"
writeln "\t\$(XTCC) -o \$(OBJDIR)/$s.o -c \$(OBJDIR)/${s}_.c\n"
writeln "\$(OBJDIR)/${s}.h:\t\$(OBJDIR)/headers\n"
|
| ︙ | ︙ | |||
1382 1383 1384 1385 1386 1387 1388 | mkbuiltin$E: $(SRCDIR)\mkbuiltin.c $(BCC) -o$@ $** mkversion$E: $(SRCDIR)\mkversion.c $(BCC) -o$@ $** | < < < | 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 | mkbuiltin$E: $(SRCDIR)\mkbuiltin.c $(BCC) -o$@ $** mkversion$E: $(SRCDIR)\mkversion.c $(BCC) -o$@ $** codecheck1$E: $(SRCDIR)\codecheck1.c $(BCC) -o$@ $** $(OBJDIR)\shell$O : $(SRCDIR)\shell.c $(TCC) -o$@ -c $(SHELL_OPTIONS) $(SQLITE_OPTIONS) $(SHELL_CFLAGS) $** $(OBJDIR)\sqlite3$O : $(SRCDIR)\sqlite3.c |
| ︙ | ︙ | |||
1406 1407 1408 1409 1410 1411 1412 | $(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h cp $@ $@ VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION +$** > $@ | < < < | < | | 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 |
$(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h
cp $@ $@
VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION
+$** > $@
page_index.h: mkindex$E $(SRC)
+$** > $@
builtin_data.h: mkbuiltin$E $(EXTRA_FILES)
mkbuiltin$E --prefix $(SRCDIR)/ $(EXTRA_FILES) > $@
clean:
-del $(OBJDIR)\*.obj
-del *.obj *_.c *.h *.map
realclean:
-del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E codecheck1$E mkbuiltin$E
$(OBJDIR)\json$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_artifact$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_branch$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_config$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_diff$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_dir$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_finfo$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_query$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_report$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_status$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h
}
foreach s [lsort $src] {
writeln "\$(OBJDIR)\\$s\$O : ${s}_.c ${s}.h"
writeln "\t\$(TCC) -o\$@ -c ${s}_.c\n"
writeln "${s}_.c : \$(SRCDIR)\\$s.c"
writeln "\t+translate\$E \$** > \$@\n"
}
writeln -nonewline "headers: makeheaders\$E page_index.h builtin_data.h VERSION.h\n\t +makeheaders\$E "
foreach s [lsort $src] {
writeln -nonewline "${s}_.c:$s.h "
}
writeln "\$(SRCDIR)\\sqlite3.h \$(SRCDIR)\\th.h VERSION.h \$(SRCDIR)\\cson_amalgamation.h"
writeln "\t@copy /Y nul: headers"
close $output_file
|
| ︙ | ︙ | |||
1471 1472 1473 1474 1475 1476 1477 |
fconfigure $output_file -translation binary
writeln {#
##############################################################################
# WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl")
##############################################################################
#
| < < < < < < < | > | | > > > > > > > > > > > > > > > > | | | | > > > | 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 |
fconfigure $output_file -translation binary
writeln {#
##############################################################################
# WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl")
##############################################################################
#
#
# This file is automatically generated. Instead of editing this
# file, edit "makemake.tcl" then run "tclsh makemake.tcl"
# to regenerate this file.
#
B = ..
SRCDIR = $(B)\src
T = .
OBJDIR = $(T)
OX = $(OBJDIR)
O = .obj
E = .exe
P = .pdb
INSTALLDIR = .
!ifdef DESTDIR
INSTALLDIR = $(DESTDIR)\$(INSTALLDIR)
!endif
# When building out of source, this Makefile needs to know the path to the base
# top-level directory for this project. Pass it on NMAKE command line via make
# variable B:
# NMAKE /f "path\to\this\Makefile" B="path/to/fossil/root"
#
# NOTE: Make sure B path has no trailing backslash, UNIX-style path is OK too.
#
!if !exist("$(B)\.fossil-settings")
!error Please specify path to project base directory: B="path/to/fossil"
!endif
# Perl is only necessary if OpenSSL support is enabled and it is built from
# source code. The PERLDIR environment variable, if it exists, should point
# to the directory containing the main Perl executable specified here (i.e.
# "perl.exe").
PERL = perl.exe
# Enable debugging symbols?
!ifndef DEBUG
DEBUG = 0
!endif
!ifdef FOSSIL_DEBUG
DEBUG = 1
!endif
# Build the OpenSSL libraries?
!ifndef FOSSIL_BUILD_SSL
FOSSIL_BUILD_SSL = 0
!endif
|
| ︙ | ︙ | |||
1568 1569 1570 1571 1572 1573 1574 | # Enable support for the SQLite Encryption Extension? !ifndef USE_SEE USE_SEE = 0 !endif !if $(FOSSIL_ENABLE_SSL)!=0 | | | 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 | # Enable support for the SQLite Encryption Extension? !ifndef USE_SEE USE_SEE = 0 !endif !if $(FOSSIL_ENABLE_SSL)!=0 SSLDIR = $(B)\compat\openssl-1.1.1g SSLINCDIR = $(SSLDIR)\include !if $(FOSSIL_DYNAMIC_BUILD)!=0 SSLLIBDIR = $(SSLDIR) !else SSLLIBDIR = $(SSLDIR) !endif SSLLFLAGS = /nologo /opt:ref /debug |
| ︙ | ︙ | |||
1620 1621 1622 1623 1624 1625 1626 | !if $(FOSSIL_DYNAMIC_BUILD)!=0 ZLIB = zdll.lib !else ZLIB = zlib.lib !endif | | | | | > > > | 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 | !if $(FOSSIL_DYNAMIC_BUILD)!=0 ZLIB = zdll.lib !else ZLIB = zlib.lib !endif INCL = /I. /I"$(OX)" /I"$(SRCDIR)" /I"$(B)\win\include" !if $(FOSSIL_ENABLE_MINIZ)==0 INCL = $(INCL) /I"$(ZINCDIR)" !endif !if $(FOSSIL_ENABLE_SSL)!=0 INCL = $(INCL) /I"$(SSLINCDIR)" !endif !if $(FOSSIL_ENABLE_TCL)!=0 INCL = $(INCL) /I"$(TCLINCDIR)" !endif CFLAGS = /nologo LDFLAGS = CFLAGS = $(CFLAGS) /D_CRT_SECURE_NO_DEPRECATE /D_CRT_SECURE_NO_WARNINGS CFLAGS = $(CFLAGS) /D_CRT_NONSTDC_NO_DEPRECATE /D_CRT_NONSTDC_NO_WARNINGS !if $(FOSSIL_DYNAMIC_BUILD)!=0 LDFLAGS = $(LDFLAGS) /MANIFEST !else LDFLAGS = $(LDFLAGS) /NODEFAULTLIB:msvcrt /MANIFEST:NO !endif !if $(FOSSIL_ENABLE_WINXP)!=0 |
| ︙ | ︙ | |||
1669 1670 1671 1672 1673 1674 1675 | CRTFLAGS = /MTd !else CRTFLAGS = /MT !endif !endif !if $(DEBUG)!=0 | | | | | 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 | CRTFLAGS = /MTd !else CRTFLAGS = /MT !endif !endif !if $(DEBUG)!=0 CFLAGS = $(CFLAGS) /Zi $(CRTFLAGS) /Od /DFOSSIL_DEBUG LDFLAGS = $(LDFLAGS) /DEBUG !else CFLAGS = $(CFLAGS) $(CRTFLAGS) /O2 !endif BCC = $(CC) $(CFLAGS) TCC = $(CC) /c $(CFLAGS) $(MSCDEF) $(INCL) RCC = $(RC) /D_WIN32 /D_MSC_VER $(MSCDEF) $(INCL) MTC = mt LIBS = ws2_32.lib advapi32.lib dnsapi.lib LIBDIR = !if $(FOSSIL_DYNAMIC_BUILD)!=0 TCC = $(TCC) /DFOSSIL_DYNAMIC_BUILD=1 RCC = $(RCC) /DFOSSIL_DYNAMIC_BUILD=1 !endif !if $(FOSSIL_ENABLE_MINIZ)==0 LIBS = $(LIBS) $(ZLIB) LIBDIR = $(LIBDIR) /LIBPATH:"$(ZLIBDIR)" !endif !if $(FOSSIL_ENABLE_MINIZ)!=0 TCC = $(TCC) /DFOSSIL_ENABLE_MINIZ=1 RCC = $(RCC) /DFOSSIL_ENABLE_MINIZ=1 !endif !if $(FOSSIL_ENABLE_JSON)!=0 TCC = $(TCC) /DFOSSIL_ENABLE_JSON=1 RCC = $(RCC) /DFOSSIL_ENABLE_JSON=1 !endif !if $(FOSSIL_ENABLE_SSL)!=0 TCC = $(TCC) /DFOSSIL_ENABLE_SSL=1 RCC = $(RCC) /DFOSSIL_ENABLE_SSL=1 LIBS = $(LIBS) $(SSLLIB) LIBDIR = $(LIBDIR) /LIBPATH:"$(SSLLIBDIR)" !endif !if $(FOSSIL_ENABLE_EXEC_REL_PATHS)!=0 TCC = $(TCC) /DFOSSIL_ENABLE_EXEC_REL_PATHS=1 RCC = $(RCC) /DFOSSIL_ENABLE_EXEC_REL_PATHS=1 !endif |
| ︙ | ︙ | |||
1764 1765 1766 1767 1768 1769 1770 |
writeln -nonewline "SRC = "
set i 0
foreach s [lsort $src] {
if {$i > 0} {
writeln " \\"
writeln -nonewline " "
}
| | | | | | > > > > > > | > > > > > > > > > > | | > > < > > > | < | | | | | | | < < < | | | | | | | | | | < < < | | | | | | | | | | | | | | | | | < < | < | | | | > | | | | | | | | > | > > > > > > > > > > > > > > > > > < < < < < < < < < < < < < < < < | | | | > | > > > > > > > > | > | < | | | | | < < > > | | | | | | | | | | | | | | | 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 |
writeln -nonewline "SRC = "
set i 0
foreach s [lsort $src] {
if {$i > 0} {
writeln " \\"
writeln -nonewline " "
}
writeln -nonewline "\"\$(OX)\\${s}_.c\""; incr i
}
writeln "\n"
writeln -nonewline "EXTRA_FILES = "
set i 0
foreach s [lsort $extra_files] {
if {$i > 0} {
writeln " \\"
writeln -nonewline " "
}
set s [regsub -all / $s \\]
writeln -nonewline "\"\$(SRCDIR)\\${s}\""; incr i
}
writeln "\n"
set AdditionalObj [list shell sqlite3 th th_lang th_tcl cson_amalgamation]
writeln -nonewline "OBJ = "
set i 0
foreach s [lsort [concat $src $AdditionalObj]] {
if {$i > 0} {
writeln " \\"
writeln -nonewline " "
}
writeln -nonewline "\"\$(OX)\\$s\$O\""; incr i
}
if {$i > 0} {
writeln " \\"
}
writeln "!if \$(FOSSIL_ENABLE_MINIZ)!=0"
writeln -nonewline " "
writeln "\"\$(OX)\\miniz\$O\" \\"; incr i
writeln "!endif"
writeln -nonewline " \"\$(OX)\\fossil.res\"\n\n"
writeln [string map [list <<<NEXT_LINE>>> \\] {
!ifndef BASEAPPNAME
BASEAPPNAME = fossil
!endif
APPNAME = $(OX)\$(BASEAPPNAME)$(E)
PDBNAME = $(OX)\$(BASEAPPNAME)$(P)
APPTARGETS =
all: "$(OX)" "$(APPNAME)"
$(BASEAPPNAME): "$(APPNAME)"
$(BASEAPPNAME)$(E): "$(APPNAME)"
install: "$(APPNAME)"
echo F | xcopy /Y "$(APPNAME)" "$(INSTALLDIR)"\*
!if $(DEBUG)!=0
echo F | xcopy /Y "$(PDBNAME)" "$(INSTALLDIR)"\*
!endif
$(OX):
@-mkdir $@
zlib:
@echo Building zlib from "$(ZLIBDIR)"...
!if $(FOSSIL_ENABLE_WINXP)!=0
@pushd "$(ZLIBDIR)" && $(MAKE) /f win32\Makefile.msc $(ZLIB) "CC=cl $(XPCFLAGS)" "LD=link $(XPLDFLAGS)" && popd
!else
@pushd "$(ZLIBDIR)" && $(MAKE) /f win32\Makefile.msc $(ZLIB) && popd
!endif
clean-zlib:
@pushd "$(ZLIBDIR)" && $(MAKE) /f win32\Makefile.msc clean && popd
!if $(FOSSIL_ENABLE_SSL)!=0
openssl:
@echo Building OpenSSL from "$(SSLDIR)"...
!ifdef PERLDIR
@pushd "$(SSLDIR)" && "$(PERLDIR)\$(PERL)" Configure $(SSLCONFIG) && popd
!else
@pushd "$(SSLDIR)" && "$(PERL)" Configure $(SSLCONFIG) && popd
!endif
!if $(FOSSIL_ENABLE_WINXP)!=0
@pushd "$(SSLDIR)" && $(MAKE) "CC=cl $(XPCFLAGS)" "LFLAGS=$(XPLDFLAGS)" && popd
!else
@pushd "$(SSLDIR)" && $(MAKE) && popd
!endif
clean-openssl:
@pushd "$(SSLDIR)" && $(MAKE) clean && popd
!endif
!if $(FOSSIL_ENABLE_MINIZ)==0
!if $(FOSSIL_BUILD_ZLIB)!=0
APPTARGETS = $(APPTARGETS) zlib
!endif
!endif
!if $(FOSSIL_ENABLE_SSL)!=0
!if $(FOSSIL_BUILD_SSL)!=0
APPTARGETS = $(APPTARGETS) openssl
!endif
!endif
"$(APPNAME)" : $(APPTARGETS) "$(OBJDIR)\translate$E" "$(OBJDIR)\mkindex$E" "$(OBJDIR)\codecheck1$E" "$(OX)\headers" $(OBJ) "$(OX)\linkopts"
"$(OBJDIR)\codecheck1$E" $(SRC)
link $(LDFLAGS) /OUT:$@ /PDB:$(@D)\ $(LIBDIR) Wsetargv.obj "$(OX)\fossil.res" @"$(OX)\linkopts"
if exist "$(B)\win\fossil.exe.manifest" <<<NEXT_LINE>>>
$(MTC) -nologo -manifest "$(B)\win\fossil.exe.manifest" -outputresource:$@;1
"$(OX)\linkopts": "$(B)\win\Makefile.msc"}]
set redir {>}
foreach s [lsort [concat $src $AdditionalObj]] {
writeln "\techo \"\$(OX)\\$s.obj\" $redir \$@"
set redir {>>}
}
set redir {>>}
writeln "!if \$(FOSSIL_ENABLE_MINIZ)!=0"
writeln "\techo \"\$(OX)\\miniz.obj\" $redir \$@"
writeln "!endif"
writeln "\techo \$(LIBS) $redir \$@"
writeln {
"$(OBJDIR)\translate$E": "$(SRCDIR)\translate.c"
$(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $**
"$(OBJDIR)\makeheaders$E": "$(SRCDIR)\makeheaders.c"
$(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $**
"$(OBJDIR)\mkindex$E": "$(SRCDIR)\mkindex.c"
$(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $**
"$(OBJDIR)\mkbuiltin$E": "$(SRCDIR)\mkbuiltin.c"
$(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $**
"$(OBJDIR)\mkversion$E": "$(SRCDIR)\mkversion.c"
$(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $**
"$(OBJDIR)\codecheck1$E": "$(SRCDIR)\codecheck1.c"
$(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $**
!if $(USE_SEE)!=0
SEE_FLAGS = /DSQLITE_HAS_CODEC=1 /DSQLITE_SHELL_DBKEY_PROC=fossil_key
SQLITE3_SHELL_SRC = $(SRCDIR)\shell-see.c
SQLITE3_SRC = $(SRCDIR)\sqlite3-see.c
!else
SEE_FLAGS =
SQLITE3_SHELL_SRC = $(SRCDIR)\shell.c
SQLITE3_SRC = $(SRCDIR)\sqlite3.c
!endif
"$(OX)\shell$O" : "$(SQLITE3_SHELL_SRC)" "$(B)\win\Makefile.msc"
$(TCC) /Fo$@ /Fd$(@D)\ $(SHELL_OPTIONS) $(SQLITE_OPTIONS) $(SHELL_CFLAGS) $(SEE_FLAGS) -c "$(SQLITE3_SHELL_SRC)"
"$(OX)\sqlite3$O" : "$(SQLITE3_SRC)" "$(B)\win\Makefile.msc"
$(TCC) /Fo$@ /Fd$(@D)\ -c $(SQLITE_OPTIONS) $(SQLITE_CFLAGS) $(SEE_FLAGS) "$(SQLITE3_SRC)"
"$(OX)\th$O" : "$(SRCDIR)\th.c"
$(TCC) /Fo$@ /Fd$(@D)\ -c $**
"$(OX)\th_lang$O" : "$(SRCDIR)\th_lang.c"
$(TCC) /Fo$@ /Fd$(@D)\ -c $**
"$(OX)\th_tcl$O" : "$(SRCDIR)\th_tcl.c"
$(TCC) /Fo$@ /Fd$(@D)\ -c $**
"$(OX)\miniz$O" : "$(SRCDIR)\miniz.c"
$(TCC) /Fo$@ /Fd$(@D)\ -c $(MINIZ_OPTIONS) $**
"$(OX)\VERSION.h" : "$(OBJDIR)\mkversion$E" "$(B)\manifest.uuid" "$(B)\manifest" "$(B)\VERSION"
$** > $@
"$(OX)\cson_amalgamation$O" : "$(SRCDIR)\cson_amalgamation.c"
$(TCC) /Fo$@ /Fd$(@D)\ -c $**
"$(OX)\page_index.h": "$(OBJDIR)\mkindex$E" $(SRC)
$** > $@
"$(OX)\builtin_data.h": "$(OBJDIR)\mkbuiltin$E" "$(OX)\builtin_data.reslist"
"$(OBJDIR)\mkbuiltin$E" --prefix "$(SRCDIR)/" --reslist "$(OX)\builtin_data.reslist" > $@
cleanx:
-del "$(OX)\*.obj" 2>NUL
-del "$(OBJDIR)\*.obj" 2>NUL
-del "$(OX)\*_.c" 2>NUL
-del "$(OX)\*.h" 2>NUL
-del "$(OX)\*.ilk" 2>NUL
-del "$(OX)\*.map" 2>NUL
-del "$(OX)\*.res" 2>NUL
-del "$(OX)\*.reslist" 2>NUL
-del "$(OX)\headers" 2>NUL
-del "$(OX)\linkopts" 2>NUL
-del "$(OX)\vc*.pdb" 2>NUL
clean: cleanx
-del "$(APPNAME)" 2>NUL
-del "$(PDBNAME)" 2>NUL
-del "$(OBJDIR)\translate$E" 2>NUL
-del "$(OBJDIR)\translate$P" 2>NUL
-del "$(OBJDIR)\mkindex$E" 2>NUL
-del "$(OBJDIR)\mkindex$P" 2>NUL
-del "$(OBJDIR)\makeheaders$E" 2>NUL
-del "$(OBJDIR)\makeheaders$P" 2>NUL
-del "$(OBJDIR)\mkversion$E" 2>NUL
-del "$(OBJDIR)\mkversion$P" 2>NUL
-del "$(OBJDIR)\mkcss$E" 2>NUL
-del "$(OBJDIR)\mkcss$P" 2>NUL
-del "$(OBJDIR)\codecheck1$E" 2>NUL
-del "$(OBJDIR)\codecheck1$P" 2>NUL
-del "$(OBJDIR)\mkbuiltin$E" 2>NUL
-del "$(OBJDIR)\mkbuiltin$P" 2>NUL
realclean: clean
"$(OBJDIR)\json$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_artifact$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_branch$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_config$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_diff$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_dir$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_finfo$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_login$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_query$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_report$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_status$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_tag$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_timeline$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_user$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_wiki$O" : "$(SRCDIR)\json_detail.h"
}
writeln {"$(OX)\builtin_data.reslist": $(EXTRA_FILES) "$(B)\win\Makefile.msc"}
set redir {>}
foreach s [lsort $extra_files] {
writeln "\techo \"\$(SRCDIR)\\${s}\" $redir \$@"
set redir {>>}
}
writeln ""
foreach s [lsort $src] {
writeln "\"\$(OX)\\$s\$O\" : \"\$(OX)\\${s}_.c\" \"\$(OX)\\${s}.h\""
writeln "\t\$(TCC) /Fo\$@ /Fd\$(@D)\\ -c \"\$(OX)\\${s}_.c\"\n"
writeln "\"\$(OX)\\${s}_.c\" : \"\$(SRCDIR)\\$s.c\""
writeln "\t\"\$(OBJDIR)\\translate\$E\" \$** > \$@\n"
}
writeln "\"\$(OX)\\fossil.res\" : \"\$(B)\\win\\fossil.rc\""
writeln "\t\$(RCC) /fo \$@ \$**\n"
writeln "\"\$(OX)\\headers\": \"\$(OBJDIR)\\makeheaders\$E\" \"\$(OX)\\page_index.h\" \"\$(OX)\\builtin_data.h\" \"\$(OX)\\VERSION.h\""
writeln -nonewline "\t\"\$(OBJDIR)\\makeheaders\$E\" "
set i 0
foreach s [lsort $src] {
if {$i > 0} {
writeln " \\"
writeln -nonewline "\t\t\t"
}
writeln -nonewline "\"\$(OX)\\${s}_.c\":\"\$(OX)\\$s.h\""; incr i
}
writeln " \\\n\t\t\t\"\$(SRCDIR)\\sqlite3.h\" \\"
writeln "\t\t\t\"\$(SRCDIR)\\th.h\" \\"
writeln "\t\t\t\"\$(OX)\\VERSION.h\" \\"
writeln "\t\t\t\"\$(SRCDIR)\\cson_amalgamation.h\""
writeln "\t@copy /Y nul: $@"
close $output_file
#
# End of the win/Makefile.msc output
##############################################################################
##############################################################################
|
| ︙ | ︙ | |||
2165 2166 2167 2168 2169 2170 2171 | builtin_data.h: $(EXTRA_FILES) mkbuiltin.exe mkbuiltin.exe --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ # extracting version info from manifest VERSION.h: version.exe ..\manifest.uuid ..\manifest ..\VERSION version.exe ..\manifest.uuid ..\manifest ..\VERSION >$@ | < < < | | 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 | builtin_data.h: $(EXTRA_FILES) mkbuiltin.exe mkbuiltin.exe --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ # extracting version info from manifest VERSION.h: version.exe ..\manifest.uuid ..\manifest ..\VERSION version.exe ..\manifest.uuid ..\manifest ..\VERSION >$@ # generate the simplified headers headers: makeheaders.exe page_index.h builtin_data.h VERSION.h ../src/sqlite3.h ../src/th.h makeheaders.exe $(foreach ts,$(TRANSLATEDSRC),$(ts):$(ts:_.c=.h)) ../src/sqlite3.h ../src/th.h VERSION.h echo Done >$@ # compile C sources with relevant options $(TRANSLATEDOBJ): %_.obj: %_.c %.h $(CC) $(CCFLAGS) $(INCLUDE) "$<" -Fo"$@" |
| ︙ | ︙ |
| ︙ | ︙ | |||
66 67 68 69 70 71 72 |
/*
** A parsed manifest or cluster.
*/
struct Manifest {
Blob content; /* The original content blob */
int type; /* Type of artifact. One of CFTYPE_xxxxx */
int rid; /* The blob-id for this manifest */
| | | 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
/*
** A parsed manifest or cluster.
*/
struct Manifest {
Blob content; /* The original content blob */
int type; /* Type of artifact. One of CFTYPE_xxxxx */
int rid; /* The blob-id for this manifest */
const char *zBaseline;/* Baseline manifest. The B card. */
Manifest *pBaseline; /* The actual baseline manifest */
char *zComment; /* Decoded comment. The C card. */
double rDate; /* Date and time from D card. 0.0 if no D card. */
char *zUser; /* Name of the user from the U card. */
char *zRepoCksum; /* MD5 checksum of the baseline content. R card. */
char *zWiki; /* Text of the wiki page. W card. */
char *zWikiTitle; /* Name of the wiki page. L card. */
|
| ︙ | ︙ | |||
128 129 130 131 132 133 134 |
} manifestCardTypes[] = {
/* Allowed Required */
/* CFTYPE_MANIFEST 1 */ { "BCDFNPQRTUZ", "DZ" },
/* Wants to be "CDUZ" ----^^^^
** but we must limit for historical compatibility */
/* CFTYPE_CLUSTER 2 */ { "MZ", "MZ" },
/* CFTYPE_CONTROL 3 */ { "DTUZ", "DTUZ" },
| | | 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
} manifestCardTypes[] = {
/* Allowed Required */
/* CFTYPE_MANIFEST 1 */ { "BCDFNPQRTUZ", "DZ" },
/* Wants to be "CDUZ" ----^^^^
** but we must limit for historical compatibility */
/* CFTYPE_CLUSTER 2 */ { "MZ", "MZ" },
/* CFTYPE_CONTROL 3 */ { "DTUZ", "DTUZ" },
/* CFTYPE_WIKI 4 */ { "CDLNPUWZ", "DLUWZ" },
/* CFTYPE_TICKET 5 */ { "DJKUZ", "DJKUZ" },
/* CFTYPE_ATTACHMENT 6 */ { "ACDNUZ", "ADZ" },
/* CFTYPE_EVENT 7 */ { "CDENPTUWZ", "DEWZ" },
/* CFTYPE_FORUM 8 */ { "DGHINPUWZ", "DUWZ" },
};
/*
|
| ︙ | ︙ | |||
316 317 318 319 320 321 322 | ** checksum. Return 0 if there is no Z-card. Return 1 if the Z-card ** exists and is correct. Return 2 if the Z-card exists and has the wrong ** value. ** ** 0123456789 123456789 123456789 123456789 ** Z aea84f4f863865a8d59d0384e4d2a41c */ | | > > | > < | | > | | | < | | 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 |
** checksum. Return 0 if there is no Z-card. Return 1 if the Z-card
** exists and is correct. Return 2 if the Z-card exists and has the wrong
** value.
**
** 0123456789 123456789 123456789 123456789
** Z aea84f4f863865a8d59d0384e4d2a41c
*/
static int verify_z_card(const char *z, int n, Blob *pErr){
const char *zHash;
if( n<35 ) return 0;
if( z[n-35]!='Z' || z[n-34]!=' ' ) return 0;
md5sum_init();
md5sum_step_text(z, n-35);
zHash = md5sum_finish(0);
if( memcmp(&z[n-33], zHash, 32)==0 ){
return 1;
}else{
blob_appendf(pErr, "incorrect Z-card cksum: expected %.32s", zHash);
return 2;
}
}
/*
** A structure used for rapid parsing of the Manifest file
*/
typedef struct ManifestText ManifestText;
struct ManifestText {
char *z; /* The first character of the next token */
char *zEnd; /* One character beyond the end of the manifest */
int atEol; /* True if z points to the start of a new line */
};
/*
** Return a pointer to the next token. The token is zero-terminated.
** Return NULL if there are no more tokens on the current line.
*/
static char *next_token(ManifestText *p, int *pLen){
char *zStart;
int n;
if( p->atEol ) return 0;
zStart = p->z;
n = strcspn(p->z, " \n");
p->atEol = p->z[n]=='\n';
p->z[n] = 0;
p->z += n+1;
if( pLen ) *pLen = n;
return zStart;
}
/*
** Return the card-type for the next card. Or, return 0 if there are no
** more cards or if we are not at the end of the current card.
*/
|
| ︙ | ︙ | |||
380 381 382 383 384 385 386 387 388 389 390 391 392 393 |
return c;
}
/*
** Shorthand for a control-artifact parsing error
*/
#define SYNTAX(T) {zErr=(T); goto manifest_syntax_error;}
/*
** 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.
**
| > > > > > > > > > > > > > | 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 |
return c;
}
/*
** Shorthand for a control-artifact parsing error
*/
#define SYNTAX(T) {zErr=(T); goto manifest_syntax_error;}
/*
** A cache of manifest IDs which manifest_parse() has seen in this
** session.
*/
static Bag seenManifests = Bag_INIT;
/*
** Frees all memory owned by the manifest "has-seen" cache. Intended
** to be called only from the app's atexit() handler.
*/
void manifest_clear_cache(){
bag_clear(&seenManifests);
}
/*
** 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.
**
|
| ︙ | ︙ | |||
424 425 426 427 428 429 430 | char *z; int n; char *zUuid; int sz = 0; int isRepeat; int nSelfTag = 0; /* Number of T cards referring to this manifest */ int nSimpleTag = 0; /* Number of T cards with "+" prefix */ | < | | | 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 |
char *z;
int n;
char *zUuid;
int sz = 0;
int isRepeat;
int nSelfTag = 0; /* Number of T cards referring to this manifest */
int nSimpleTag = 0; /* Number of T cards with "+" prefix */
const char *zErr = 0;
unsigned int m;
unsigned int seenCard = 0; /* Which card types have been seen */
char zErrBuf[100]; /* Write error messages here */
if( rid==0 ){
isRepeat = 1;
}else if( bag_find(&seenManifests, rid) ){
isRepeat = 1;
}else{
isRepeat = 0;
bag_insert(&seenManifests, rid);
}
/* Every structural artifact ends with a '\n' character. Exit early
** if that is not the case for this artifact.
*/
if( !isRepeat ) g.parseCnt[0]++;
z = blob_materialize(pContent);
|
| ︙ | ︙ | |||
465 466 467 468 469 470 471 |
if( n<10 || z[0]<'A' || z[0]>'Z' || z[1]!=' ' ){
blob_reset(pContent);
blob_appendf(pErr, "line 1 not recognized");
return 0;
}
/* Then verify the Z-card.
*/
| | < | 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 |
if( n<10 || z[0]<'A' || z[0]>'Z' || z[1]!=' ' ){
blob_reset(pContent);
blob_appendf(pErr, "line 1 not recognized");
return 0;
}
/* Then verify the Z-card.
*/
if( verify_z_card(z, n, pErr)==2 ){
blob_reset(pContent);
return 0;
}
/* Allocate a Manifest object to hold the parsed control artifact.
*/
p = fossil_malloc( sizeof(*p) );
memset(p, 0, sizeof(*p));
|
| ︙ | ︙ | |||
507 508 509 510 511 512 513 |
int nTarget = 0, nSrc = 0;
zName = next_token(&x, 0);
zTarget = next_token(&x, &nTarget);
zSrc = next_token(&x, &nSrc);
if( zName==0 || zTarget==0 ) goto manifest_syntax_error;
if( p->zAttachName!=0 ) goto manifest_syntax_error;
defossilize(zName);
| | | 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 |
int nTarget = 0, nSrc = 0;
zName = next_token(&x, 0);
zTarget = next_token(&x, &nTarget);
zSrc = next_token(&x, &nSrc);
if( zName==0 || zTarget==0 ) goto manifest_syntax_error;
if( p->zAttachName!=0 ) goto manifest_syntax_error;
defossilize(zName);
if( !file_is_simple_pathname_nonstrict(zName) ){
SYNTAX("invalid filename on A-card");
}
defossilize(zTarget);
if( !hname_validate(zTarget,nTarget)
&& !wiki_name_is_wellformed((const unsigned char *)zTarget) ){
SYNTAX("invalid target on A-card");
}
|
| ︙ | ︙ | |||
605 606 607 608 609 610 611 |
** other control file. The filename and old-name are fossil-encoded.
*/
case 'F': {
char *zName, *zPerm, *zPriorName;
zName = next_token(&x,0);
if( zName==0 ) SYNTAX("missing filename on F-card");
defossilize(zName);
| | | | 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 |
** other control file. The filename and old-name are fossil-encoded.
*/
case 'F': {
char *zName, *zPerm, *zPriorName;
zName = next_token(&x,0);
if( zName==0 ) SYNTAX("missing filename on F-card");
defossilize(zName);
if( !file_is_simple_pathname_nonstrict(zName) ){
SYNTAX("F-card filename is not a simple path");
}
zUuid = next_token(&x, &sz);
if( p->zBaseline==0 || zUuid!=0 ){
if( !hname_validate(zUuid,sz) ){
SYNTAX("F-card hash invalid");
}
}
zPerm = next_token(&x,0);
zPriorName = next_token(&x,0);
if( zPriorName ){
defossilize(zPriorName);
if( !file_is_simple_pathname_nonstrict(zPriorName) ){
SYNTAX("F-card old filename is not a simple path");
}
}
if( p->nFile>=p->nFileAlloc ){
p->nFileAlloc = p->nFileAlloc*2 + 10;
p->aFile = fossil_realloc(p->aFile,
p->nFileAlloc*sizeof(p->aFile[0]) );
|
| ︙ | ︙ | |||
1049 1050 1051 1052 1053 1054 1055 |
md5sum_init();
if( !isRepeat ) g.parseCnt[p->type]++;
return p;
manifest_syntax_error:
{
| | | 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 |
md5sum_init();
if( !isRepeat ) g.parseCnt[p->type]++;
return p;
manifest_syntax_error:
{
char *zUuid = rid_to_uuid(rid);
if( zUuid ){
blob_appendf(pErr, "artifact [%s] ", zUuid);
fossil_free(zUuid);
}
}
if( zErr ){
blob_appendf(pErr, "line %d: %s", lineNo, zErr);
|
| ︙ | ︙ | |||
1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 |
blob_copy(&b2, &b);
blob_zero(&err);
p = manifest_parse(&b2, 0, &err);
if( p==0 ) fossil_print("ERROR: %s\n", blob_str(&err));
blob_reset(&err);
manifest_destroy(p);
}
}
/*
** COMMAND: test-parse-all-blobs
**
| > | > > > > > > | | 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 |
blob_copy(&b2, &b);
blob_zero(&err);
p = manifest_parse(&b2, 0, &err);
if( p==0 ) fossil_print("ERROR: %s\n", blob_str(&err));
blob_reset(&err);
manifest_destroy(p);
}
blob_reset(&b);
}
/*
** COMMAND: test-parse-all-blobs
**
** Usage: %fossil test-parse-all-blobs [--limit N]
**
** Parse all entries in the BLOB table that are believed to be non-data
** artifacts and report any errors. Run this test command on historical
** repositories after making any changes to the manifest_parse()
** implementation to confirm that the changes did not break anything.
**
** If the --limit N argument is given, parse no more than N blobs
*/
void manifest_test_parse_all_blobs_cmd(void){
Manifest *p;
Blob err;
Stmt q;
int nTest = 0;
int nErr = 0;
int N = 1000000000;
const char *z;
db_find_and_open_repository(0, 0);
z = find_option("limit", 0, 1);
if( z ) N = atoi(z);
verify_all_options();
db_prepare(&q, "SELECT DISTINCT objid FROM EVENT");
while( (N--)>0 && db_step(&q)==SQLITE_ROW ){
int id = db_column_int(&q,0);
fossil_print("Checking %d \r", id);
nTest++;
fflush(stdout);
blob_init(&err, 0, 0);
p = manifest_get(id, CFTYPE_ANY, &err);
if( p==0 ){
|
| ︙ | ︙ | |||
1305 1306 1307 1308 1309 1310 1311 | return fnid; } /* ** Compute an appropriate mlink.mperm integer for the permission string ** of a file. */ | | | 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 |
return fnid;
}
/*
** Compute an appropriate mlink.mperm integer for the permission string
** of a file.
*/
int manifest_file_mperm(const ManifestFile *pFile){
int mperm = PERM_REG;
if( pFile && pFile->zPerm){
if( strstr(pFile->zPerm,"x")!=0 ){
mperm = PERM_EXE;
}else if( strstr(pFile->zPerm,"l")!=0 ){
mperm = PERM_LNK;
}
|
| ︙ | ︙ | |||
1694 1695 1696 1697 1698 1699 1700 | ** ** Return the RID of the primary parent. */ static int manifest_add_checkin_linkages( int rid, /* The RID of the check-in */ Manifest *p, /* Manifest for this check-in */ int nParent, /* Number of parents for this check-in */ | | | 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 |
**
** Return the RID of the primary parent.
*/
static int manifest_add_checkin_linkages(
int rid, /* The RID of the check-in */
Manifest *p, /* Manifest for this check-in */
int nParent, /* Number of parents for this check-in */
char * const * azParent /* hashes for each parent */
){
int i;
int parentid = 0;
char zBaseId[30]; /* Baseline manifest RID for deltas. "NULL" otherwise */
Stmt q;
if( p->zBaseline ){
|
| ︙ | ︙ | |||
1806 1807 1808 1809 1810 1811 1812 |
** by the call to manifest_crosslink_end().
*/
void manifest_crosslink_begin(void){
assert( manifest_crosslink_busy==0 );
manifest_crosslink_busy = 1;
db_begin_transaction();
db_multi_exec(
| | > > > > > > > > > > > | 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 |
** by the call to manifest_crosslink_end().
*/
void manifest_crosslink_begin(void){
assert( manifest_crosslink_busy==0 );
manifest_crosslink_busy = 1;
db_begin_transaction();
db_multi_exec(
"CREATE TEMP TABLE pending_xlink(id TEXT PRIMARY KEY)WITHOUT ROWID;"
"CREATE TEMP TABLE time_fudge("
" mid INTEGER PRIMARY KEY," /* The rid of a manifest */
" m1 REAL," /* The timestamp on mid */
" cid INTEGER," /* A child or mid */
" m2 REAL" /* Timestamp on the child */
");"
);
}
/*
** Add a new entry to the pending_xlink table.
*/
static void add_pending_crosslink(char cType, const char *zId){
assert( manifest_crosslink_busy==1 );
db_multi_exec(
"INSERT OR IGNORE INTO pending_xlink VALUES('%c%q')",
cType, zId
);
}
#if INTERFACE
/* Timestamps might be adjusted slightly to ensure that check-ins appear
** on the timeline in chronological order. This is the maximum amount
** of the adjustment window, in days.
*/
#define AGE_FUDGE_WINDOW (2.0/86400.0) /* 2 seconds */
|
| ︙ | ︙ | |||
1857 1858 1859 1860 1861 1862 1863 |
);
while( db_step(&q)==SQLITE_ROW ){
int rid = db_column_int(&q,0);
const char *zValue = db_column_text(&q,1);
manifest_reparent_checkin(rid, zValue);
}
db_finalize(&q);
| | | > > > > > | | | > > > | | 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 |
);
while( db_step(&q)==SQLITE_ROW ){
int rid = db_column_int(&q,0);
const char *zValue = db_column_text(&q,1);
manifest_reparent_checkin(rid, zValue);
}
db_finalize(&q);
db_prepare(&q, "SELECT id FROM pending_xlink");
while( db_step(&q)==SQLITE_ROW ){
const char *zId = db_column_text(&q, 0);
char cType;
if( zId==0 || zId[0]==0 ) continue;
cType = zId[0];
zId++;
if( cType=='t' ){
ticket_rebuild_entry(zId);
if( permitHooks && rc==TH_OK ){
rc = xfer_run_script(zScript, zId, 0);
}
}else if( cType=='w' ){
backlink_wiki_refresh(zId);
}
}
db_finalize(&q);
db_multi_exec("DROP TABLE pending_xlink");
/* If multiple check-ins happen close together in time, adjust their
** times by a few milliseconds to make sure they appear in chronological
** order.
*/
db_prepare(&q,
"UPDATE time_fudge SET m1=m2-:incr WHERE m1>=m2 AND m1<m2+:window"
|
| ︙ | ︙ | |||
2037 2038 2039 2040 2041 2042 2043 |
c = fossil_strcmp(pA->zName, pB->zName);
}
return c;
}
/*
** Scan artifact rid/pContent to see if it is a control artifact of
| | > | 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 |
c = fossil_strcmp(pA->zName, pB->zName);
}
return c;
}
/*
** Scan artifact rid/pContent to see if it is a control artifact of
** any type:
**
** * Manifest
** * Control
** * Wiki Page
** * Ticket Change
** * Cluster
** * Attachment
** * Event
** * Forum post
**
** If the input is a control artifact, then make appropriate entries
** in the auxiliary tables of the database in order to crosslink the
** artifact.
**
** If global variable g.xlinkClusterOnly is true, then ignore all
** control artifacts other than clusters.
|
| ︙ | ︙ | |||
2077 2078 2079 2080 2081 2082 2083 |
fossil_trace("-- manifest_crosslink(%d)\n", rid);
}
if( (p = manifest_cache_find(rid))!=0 ){
blob_reset(pContent);
}else if( (p = manifest_parse(pContent, rid, 0))==0 ){
assert( blob_is_reset(pContent) || pContent==0 );
if( (flags & MC_NO_ERRORS)==0 ){
| > | < > | 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 |
fossil_trace("-- manifest_crosslink(%d)\n", rid);
}
if( (p = manifest_cache_find(rid))!=0 ){
blob_reset(pContent);
}else if( (p = manifest_parse(pContent, rid, 0))==0 ){
assert( blob_is_reset(pContent) || pContent==0 );
if( (flags & MC_NO_ERRORS)==0 ){
char * zErrUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d",rid);
fossil_error(1, "syntax error in manifest [%S]", zErrUuid);
fossil_free(zErrUuid);
}
return 0;
}
if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){
manifest_destroy(p);
assert( blob_is_reset(pContent) );
if( (flags & MC_NO_ERRORS)==0 ) fossil_error(1, "no manifest");
|
| ︙ | ︙ | |||
2138 2139 2140 2141 2142 2143 2144 |
rid, p->zUser, p->zComment,
TAG_BGCOLOR, rid,
TAG_USER, rid,
TAG_COMMENT, rid, p->rDate
);
zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event"
" WHERE rowid=last_insert_rowid()");
| | | 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 |
rid, p->zUser, p->zComment,
TAG_BGCOLOR, rid,
TAG_USER, rid,
TAG_COMMENT, rid, p->rDate
);
zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event"
" WHERE rowid=last_insert_rowid()");
backlink_extract(zCom, 0, rid, BKLNK_COMMENT, p->rDate, 1);
fossil_free(zCom);
/* If this is a delta-manifest, record the fact that this repository
** contains delta manifests, to free the "commit" logic to generate
** new delta manifests.
*/
if( p->zBaseline!=0 ){
|
| ︙ | ︙ | |||
2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 |
if( tid ){
switch( p->aTag[i].zName[0] ){
case '-': type = 0; break; /* Cancel prior occurrences */
case '+': type = 1; break; /* Apply to target only */
case '*': type = 2; break; /* Propagate to descendants */
default:
fossil_error(1, "unknown tag type in manifest: %s", p->aTag);
return 0;
}
tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue,
rid, p->rDate, tid);
}
}
if( parentid ){
tag_propagate_all(parentid);
}
}
if( p->type==CFTYPE_WIKI ){
char *zTag = mprintf("wiki-%s", p->zWikiTitle);
int tagid = tag_findid(zTag, 1);
int prior;
char *zComment;
int nWiki;
char zLength[40];
while( fossil_isspace(p->zWiki[0]) ) p->zWiki++;
nWiki = strlen(p->zWiki);
sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki);
tag_insert(zTag, 1, zLength, rid, p->rDate, rid);
fossil_free(zTag);
prior = db_int(0,
"SELECT rid FROM tagxref"
" WHERE tagid=%d AND mtime<%.17g"
" ORDER BY mtime DESC",
tagid, p->rDate
);
if( prior ){
content_deltify(prior, &rid, 1, 0);
}
| > > | | > > > > > > | > > | > > > > > > > > > > > > > > > > > > > > | 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 |
if( tid ){
switch( p->aTag[i].zName[0] ){
case '-': type = 0; break; /* Cancel prior occurrences */
case '+': type = 1; break; /* Apply to target only */
case '*': type = 2; break; /* Propagate to descendants */
default:
fossil_error(1, "unknown tag type in manifest: %s", p->aTag);
manifest_destroy(p);
return 0;
}
tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue,
rid, p->rDate, tid);
}
}
if( parentid ){
tag_propagate_all(parentid);
}
}
if( p->type==CFTYPE_WIKI ){
char *zTag = mprintf("wiki-%s", p->zWikiTitle);
int tagid = tag_findid(zTag, 1);
int prior;
char *zComment;
const char *zPrefix;
int nWiki;
char zLength[40];
while( fossil_isspace(p->zWiki[0]) ) p->zWiki++;
nWiki = strlen(p->zWiki);
sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki);
tag_insert(zTag, 1, zLength, rid, p->rDate, rid);
fossil_free(zTag);
prior = db_int(0,
"SELECT rid FROM tagxref"
" WHERE tagid=%d AND mtime<%.17g"
" ORDER BY mtime DESC",
tagid, p->rDate
);
if( prior ){
content_deltify(prior, &rid, 1, 0);
}
if( nWiki<=0 ){
zPrefix = "Deleted";
}else if( !prior ){
zPrefix = "Added";
}else{
zPrefix = "Changes to";
}
switch( wiki_page_type(p->zWikiTitle) ){
case WIKITYPE_CHECKIN: {
zComment = mprintf("%s wiki for check-in [%S]", zPrefix,
p->zWikiTitle+8);
break;
}
case WIKITYPE_BRANCH: {
zComment = mprintf("%s wiki for branch [/timeline?r=%t|%h]",
zPrefix, p->zWikiTitle+7, p->zWikiTitle+7);
break;
}
case WIKITYPE_TAG: {
zComment = mprintf("%s wiki for tag [/timeline?t=%t|%h]",
zPrefix, p->zWikiTitle+4, p->zWikiTitle+4);
break;
}
default: {
zComment = mprintf("%s wiki page [%h]", zPrefix, p->zWikiTitle);
break;
}
}
search_doc_touch('w',rid,p->zWikiTitle);
if( manifest_crosslink_busy ){
add_pending_crosslink('w',p->zWikiTitle);
}else{
backlink_wiki_refresh(p->zWikiTitle);
}
db_multi_exec(
"REPLACE INTO event(type,mtime,objid,user,comment,"
" bgcolor,euser,ecomment)"
"VALUES('w',%.17g,%d,%Q,%Q,"
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1),"
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
|
| ︙ | ︙ | |||
2324 2325 2326 2327 2328 2329 2330 |
if( p->type==CFTYPE_TICKET ){
char *zTag;
Stmt qatt;
assert( manifest_crosslink_busy==1 );
zTag = mprintf("tkt-%s", p->zTicketUuid);
tag_insert(zTag, 1, 0, rid, p->rDate, rid);
fossil_free(zTag);
| < | | 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 |
if( p->type==CFTYPE_TICKET ){
char *zTag;
Stmt qatt;
assert( manifest_crosslink_busy==1 );
zTag = mprintf("tkt-%s", p->zTicketUuid);
tag_insert(zTag, 1, 0, rid, p->rDate, rid);
fossil_free(zTag);
add_pending_crosslink('t',p->zTicketUuid);
/* Locate and update comment for any attachments */
db_prepare(&qatt,
"SELECT attachid, src, target, filename FROM attachment"
" WHERE target=%Q",
p->zTicketUuid
);
while( db_step(&qatt)==SQLITE_ROW ){
|
| ︙ | ︙ | |||
2362 2363 2364 2365 2366 2367 2368 |
char *zComment = 0;
const char isAdd = (p->zAttachSrc && p->zAttachSrc[0]) ? 1 : 0;
/* We assume that we're attaching to a wiki page until we
** prove otherwise (which could on a later artifact if we
** process the attachment artifact before the artifact to
** which it is attached!) */
char attachToType = 'w';
| | | 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 |
char *zComment = 0;
const char isAdd = (p->zAttachSrc && p->zAttachSrc[0]) ? 1 : 0;
/* We assume that we're attaching to a wiki page until we
** prove otherwise (which could on a later artifact if we
** process the attachment artifact before the artifact to
** which it is attached!) */
char attachToType = 'w';
if( fossil_is_artifact_hash(p->zAttachTarget) ){
if( db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'",
p->zAttachTarget)
){
attachToType = 't'; /* Attaching to known ticket */
}else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",
p->zAttachTarget)
){
|
| ︙ | ︙ | |||
2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 |
blob_reset(&comment);
}
if( p->type==CFTYPE_FORUM ){
int froot, fprev, firt;
char *zFType;
char *zTitle;
schema_forum();
froot = p->zThreadRoot ? uuid_to_rid(p->zThreadRoot, 1) : rid;
fprev = p->nParent ? uuid_to_rid(p->azParent[0],1) : 0;
firt = p->zInReplyTo ? uuid_to_rid(p->zInReplyTo,1) : 0;
db_multi_exec(
"REPLACE INTO forumpost(fpid,froot,fprev,firt,fmtime)"
"VALUES(%d,%d,nullif(%d,0),nullif(%d,0),%.17g)",
p->rid, froot, fprev, firt, p->rDate
| > | 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 |
blob_reset(&comment);
}
if( p->type==CFTYPE_FORUM ){
int froot, fprev, firt;
char *zFType;
char *zTitle;
schema_forum();
search_doc_touch('f', rid, 0);
froot = p->zThreadRoot ? uuid_to_rid(p->zThreadRoot, 1) : rid;
fprev = p->nParent ? uuid_to_rid(p->azParent[0],1) : 0;
firt = p->zInReplyTo ? uuid_to_rid(p->zInReplyTo,1) : 0;
db_multi_exec(
"REPLACE INTO forumpost(fpid,froot,fprev,firt,fmtime)"
"VALUES(%d,%d,nullif(%d,0),nullif(%d,0),%.17g)",
p->rid, froot, fprev, firt, p->rDate
|
| ︙ | ︙ |
| ︙ | ︙ | |||
65 66 67 68 69 70 71 |
void *opaque);
void (*table_row)(struct Blob *ob, struct Blob *cells, int flags,
void *opaque);
/* span level callbacks - NULL or return 0 prints the span verbatim */
int (*autolink)(struct Blob *ob, struct Blob *link,
enum mkd_autolink type, void *opaque);
| | < | 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 |
void *opaque);
void (*table_row)(struct Blob *ob, struct Blob *cells, int flags,
void *opaque);
/* span level callbacks - NULL or return 0 prints the span verbatim */
int (*autolink)(struct Blob *ob, struct Blob *link,
enum mkd_autolink type, void *opaque);
int (*codespan)(struct Blob *ob, struct Blob *text, int nSep, void *opaque);
int (*double_emphasis)(struct Blob *ob, struct Blob *text,
char c, void *opaque);
int (*emphasis)(struct Blob *ob, struct Blob *text, char c,void*opaque);
int (*image)(struct Blob *ob, struct Blob *link, struct Blob *title,
struct Blob *alt, void *opaque);
int (*linebreak)(struct Blob *ob, void *opaque);
int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title,
struct Blob *content, void *opaque);
int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
char c, void *opaque);
/* low level callbacks - NULL copies input directly into the output */
void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque);
void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque);
/* renderer data */
const char *emph_chars; /* chars that trigger emphasis rendering */
void *opaque; /* opaque data send to every rendering callback */
};
/*********
|
| ︙ | ︙ | |||
152 153 154 155 156 157 158 |
/* render -- structure containing one particular render */
struct render {
struct mkd_renderer make;
struct Blob refs;
char_trigger active_char[256];
| | > | | 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
/* render -- structure containing one particular render */
struct render {
struct mkd_renderer make;
struct Blob refs;
char_trigger active_char[256];
int iDepth; /* Depth of recursion */
int nBlobCache; /* Number of entries in aBlobCache */
struct Blob *aBlobCache[20]; /* Cache of Blobs available for reuse */
};
/* html_tag -- structure for quick HTML tag search (inspired from discount) */
struct html_tag {
const char *text;
int size;
|
| ︙ | ︙ | |||
236 237 238 239 240 241 242 |
beg = i;
while( i<size && !(data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){
i++;
}
blob_append(id, data+beg, i-beg);
/* add a single space and skip all consecutive whitespace */
| | | 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
beg = i;
while( i<size && !(data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){
i++;
}
blob_append(id, data+beg, i-beg);
/* add a single space and skip all consecutive whitespace */
if( i<size ) blob_append_char(id, ' ');
while( i<size && (data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){ i++; }
}
/* turn upper-case ASCII into their lower-case counterparts */
id_data = blob_buffer(id);
for(i=0; i<blob_size(id); i++){
if( id_data[i]>='A' && id_data[i]<='Z' ) id_data[i] += 'a' - 'A';
|
| ︙ | ︙ | |||
298 299 300 301 302 303 304 |
return bsearch(&key,
block_tags,
count(block_tags),
sizeof block_tags[0],
cmp_html_tag);
}
| > > > | > | > | | | | < | > > | | > > | > > > | 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 |
return bsearch(&key,
block_tags,
count(block_tags),
sizeof block_tags[0],
cmp_html_tag);
}
/* return true if recursion has gone too deep */
static int too_deep(struct render *rndr){
return rndr->iDepth>200;
}
/* get a new working buffer from the cache or create one. return NULL
** if failIfDeep is true and the depth of recursion has gone too deep. */
static struct Blob *new_work_buffer(struct render *rndr){
struct Blob *ret;
rndr->iDepth++;
if( rndr->nBlobCache ){
ret = rndr->aBlobCache[--rndr->nBlobCache];
}else{
ret = fossil_malloc(sizeof(*ret));
}
*ret = empty_blob;
return ret;
}
/* release the given working buffer back to the cache */
static void release_work_buffer(struct render *rndr, struct Blob *buf){
if( !buf ) return;
rndr->iDepth--;
blob_reset(buf);
if( rndr->nBlobCache < sizeof(rndr->aBlobCache)/sizeof(rndr->aBlobCache[0]) ){
rndr->aBlobCache[rndr->nBlobCache++] = buf;
}else{
fossil_free(buf);
}
}
/****************************
* INLINE PARSING FUNCTIONS *
****************************/
|
| ︙ | ︙ | |||
419 420 421 422 423 424 425 426 427 428 429 430 431 432 |
char *data,
size_t size
){
size_t i = 0, end = 0;
char_trigger action = 0;
struct Blob work = BLOB_INITIALIZER;
while( i<size ){
/* copying inactive chars into the output */
while( end<size
&& (action = rndr->active_char[(unsigned char)data[end]])==0
){
end++;
}
| > > > > | 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 |
char *data,
size_t size
){
size_t i = 0, end = 0;
char_trigger action = 0;
struct Blob work = BLOB_INITIALIZER;
if( too_deep(rndr) ){
blob_append(ob, data, size);
return;
}
while( i<size ){
/* copying inactive chars into the output */
while( end<size
&& (action = rndr->active_char[(unsigned char)data[end]])==0
){
end++;
}
|
| ︙ | ︙ | |||
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 |
}else{
i += end;
end = i;
}
}
}
/* find_emph_char -- looks for the next emph char, skipping other constructs */
static size_t find_emph_char(char *data, size_t size, char c){
size_t i = 1;
while( i<size ){
while( i<size && data[i]!=c && data[i]!='`' && data[i]!='[' ){ i++; }
if( i>=size ) return 0;
/* not counting escaped chars */
if( i && data[i-1]=='\\' ){
i++;
continue;
}
if( data[i]==c ) return i;
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < | < < | < < < < < < < < < < < < < < < < < < | | 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 |
}else{
i += end;
end = i;
}
}
}
/*
** data[*pI] should be a "`" character that introduces a code-span.
** The code-span boundry mark can be any number of one or more "`"
** characters. We do not know the size of the boundry marker, only
** that there is at least one "`" at data[*pI].
**
** This routine increases *pI to move it past the code-span, including
** the closing boundary mark. Or, if the code-span is unterminated,
** this routine moves *pI past the opening boundary mark only.
*/
static void skip_codespan(const char *data, size_t size, size_t *pI){
size_t i = *pI;
size_t span_nb; /* Number of "`" characters in the boundary mark */
size_t bt;
assert( i<size );
assert( data[i]=='`' );
data += i;
size -= i;
/* counting the number of opening backticks */
i = 0;
span_nb = 0;
while( i<size && data[i]=='`' ){
i++;
span_nb++;
}
if( i>=size ){
*pI += span_nb;
return;
}
/* finding the matching closing sequence */
bt = 0;
while( i<size && bt<span_nb ){
if( data[i]=='`' ) bt += 1; else bt = 0;
i++;
}
*pI += (bt == span_nb) ? i : span_nb;
}
/* find_emph_char -- looks for the next emph char, skipping other constructs */
static size_t find_emph_char(char *data, size_t size, char c){
size_t i = 1;
while( i<size ){
while( i<size && data[i]!=c && data[i]!='`' && data[i]!='[' ){ i++; }
if( i>=size ) return 0;
/* not counting escaped chars */
if( i && data[i-1]=='\\' ){
i++;
continue;
}
if( data[i]==c ) return i;
if( data[i]=='`' ){ /* skip a code span */
skip_codespan(data, size, &i);
}else if( data[i]=='[' ){ /* skip a link */
size_t tmp_i = 0;
char cc;
i++;
while( i<size && data[i]!=']' ){
if( !tmp_i && data[i]==c ) tmp_i = i;
i++;
}
|
| ︙ | ︙ | |||
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 |
if( i>=size ) return tmp_i;
i++;
}
}
return 0;
}
/* parse_emph1 -- parsing single emphasis */
/* closed by a symbol not preceded by whitespace and not followed by symbol */
static size_t parse_emph1(
struct Blob *ob,
struct render *rndr,
char *data,
size_t size,
char c
){
size_t i = 0, len;
struct Blob *work = 0;
int r;
if( !rndr->make.emphasis ) return 0;
/* skipping one symbol if coming from emph3 */
if( size>1 && data[0]==c && data[1]==c ) i = 1;
while( i<size ){
len = find_emph_char(data+i, size-i, c);
if( !len ) return 0;
i += len;
if( i>=size ) return 0;
if( i+1<size && data[i+1]==c ){
i++;
continue;
}
if( data[i]==c
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | < | 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 |
if( i>=size ) return tmp_i;
i++;
}
}
return 0;
}
/* CommonMark defines separate "right-flanking" and "left-flanking"
** deliminators for emphasis. Whether a deliminator is left- or
** right-flanking, or both, or neither depends on the characters
** immediately before and after.
**
** before after example left-flanking right-flanking
** ------ ----- ------- ------------- --------------
** space space * no no
** space punct *) yes no
** space alnum *x yes no
** punct space (* no yes
** punct punct (*) yes yes
** punct alnum (*x yes no
** alnum space a* no yes
** alnum punct a*( no yes
** alnum alnum a*x yes yes
**
** The following routines determine whether a delimitor is left
** or right flanking.
*/
static int left_flanking(char before, char after){
if( fossil_isspace(after) ) return 0;
if( fossil_isalnum(after) ) return 1;
if( fossil_isalnum(before) ) return 0;
return 1;
}
static int right_flanking(char before, char after){
if( fossil_isspace(before) ) return 0;
if( fossil_isalnum(before) ) return 1;
if( fossil_isalnum(after) ) return 0;
return 1;
}
/* parse_emph1 -- parsing single emphasis */
/* closed by a symbol not preceded by whitespace and not followed by symbol */
static size_t parse_emph1(
struct Blob *ob,
struct render *rndr,
char *data,
size_t size,
char c
){
size_t i = 0, len;
struct Blob *work = 0;
int r;
char after;
if( !rndr->make.emphasis ) return 0;
/* skipping one symbol if coming from emph3 */
if( size>1 && data[0]==c && data[1]==c ) i = 1;
while( i<size ){
len = find_emph_char(data+i, size-i, c);
if( !len ) return 0;
i += len;
if( i>=size ) return 0;
if( i+1<size && data[i+1]==c ){
i++;
continue;
}
after = i+1<size ? data[i+1] : ' ';
if( data[i]==c
&& right_flanking(data[i-1],after)
&& (c!='_' || !fossil_isalnum(after))
&& !too_deep(rndr)
){
work = new_work_buffer(rndr);
parse_inline(work, rndr, data, i);
r = rndr->make.emphasis(ob, work, c, rndr->make.opaque);
release_work_buffer(rndr, work);
return r ? i+1 : 0;
}
}
return 0;
|
| ︙ | ︙ | |||
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 |
char *data,
size_t size,
char c
){
size_t i = 0, len;
struct Blob *work = 0;
int r;
if( !rndr->make.double_emphasis ) return 0;
while( i<size ){
len = find_emph_char(data+i, size-i, c);
if( !len ) return 0;
i += len;
if( i+1<size
&& data[i]==c
&& data[i+1]==c
| > > < | | | < | 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 |
char *data,
size_t size,
char c
){
size_t i = 0, len;
struct Blob *work = 0;
int r;
char after;
if( !rndr->make.double_emphasis ) return 0;
while( i<size ){
len = find_emph_char(data+i, size-i, c);
if( !len ) return 0;
i += len;
after = i+2<size ? data[i+2] : ' ';
if( i+1<size
&& data[i]==c
&& data[i+1]==c
&& right_flanking(data[i-1],after)
&& (c!='_' || !fossil_isalnum(after))
&& !too_deep(rndr)
){
work = new_work_buffer(rndr);
parse_inline(work, rndr, data, i);
r = rndr->make.double_emphasis(ob, work, c, rndr->make.opaque);
release_work_buffer(rndr, work);
return r ? i+2 : 0;
}
i++;
}
|
| ︙ | ︙ | |||
629 630 631 632 633 634 635 636 637 638 |
continue;
}
if( i+2<size
&& data[i+1]==c
&& data[i+2] == c
&& rndr->make.triple_emphasis
){
/* triple symbol found */
struct Blob *work = new_work_buffer(rndr);
| > < | 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 |
continue;
}
if( i+2<size
&& data[i+1]==c
&& data[i+2] == c
&& rndr->make.triple_emphasis
&& !too_deep(rndr)
){
/* triple symbol found */
struct Blob *work = new_work_buffer(rndr);
parse_inline(work, rndr, data, i);
r = rndr->make.triple_emphasis(ob, work, c, rndr->make.opaque);
release_work_buffer(rndr, work);
return r ? i+3 : 0;
}else if( i+1<size && data[i+1]==c ){
/* double symbol found, handing over to emph1 */
len = parse_emph1(ob, rndr, data-2, size+2, c);
|
| ︙ | ︙ | |||
660 661 662 663 664 665 666 667 668 669 |
struct Blob *ob,
struct render *rndr,
char *data,
size_t offset,
size_t size
){
char c = data[0];
size_t ret;
if( size>2 && data[1]!=c ){
| > < < | | < | | < | | | 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 |
struct Blob *ob,
struct render *rndr,
char *data,
size_t offset,
size_t size
){
char c = data[0];
char before = offset>0 ? data[-1] : ' ';
size_t ret;
if( size>2 && data[1]!=c ){
if( !left_flanking(before, data[1])
|| (c=='_' && fossil_isalnum(before))
|| (ret = parse_emph1(ob, rndr, data+1, size-1, c))==0
){
return 0;
}
return ret+1;
}
if( size>3 && data[1]==c && data[2]!=c ){
if( !left_flanking(before, data[2])
|| (c=='_' && fossil_isalnum(before))
|| (ret = parse_emph2(ob, rndr, data+2, size-2, c))==0
){
return 0;
}
return ret+2;
}
if( size>4 && data[1]==c && data[2]==c && data[3]!=c ){
if( !left_flanking(before, data[3])
|| (c=='_' && fossil_isalnum(before))
|| (ret = parse_emph3(ob, rndr, data+3, size-3, c))==0
){
return 0;
}
return ret+3;
}
return 0;
|
| ︙ | ︙ | |||
723 724 725 726 727 728 729 730 731 |
struct Blob *ob,
struct render *rndr,
char *data,
size_t offset,
size_t size
){
size_t end, nb = 0, i, f_begin, f_end;
/* counting the number of backticks in the delimiter */
| > | | | | | 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 |
struct Blob *ob,
struct render *rndr,
char *data,
size_t offset,
size_t size
){
size_t end, nb = 0, i, f_begin, f_end;
char delim = data[0];
/* counting the number of backticks in the delimiter */
while( nb<size && data[nb]==delim ){ nb++; }
/* finding the next delimiter */
i = 0;
for(end=nb; end<size && i<nb; end++){
if( data[end]==delim ) i++; else i = 0;
}
if( i<nb && end>=size ) return 0; /* no matching delimiter */
/* trimming outside whitespaces */
f_begin = nb;
while( f_begin<end && (data[f_begin]==' ' || data[f_begin]=='\t') ){
f_begin++;
}
f_end = end-nb;
while( f_end>nb && (data[f_end-1]==' ' || data[f_end-1]=='\t') ){ f_end--; }
/* real code span */
if( f_begin<f_end ){
struct Blob work = BLOB_INITIALIZER;
blob_init(&work, data+f_begin, f_end-f_begin);
if( !rndr->make.codespan(ob, &work, nb, rndr->make.opaque) ) end = 0;
}else{
if( !rndr->make.codespan(ob, 0, nb, rndr->make.opaque) ) end = 0;
}
return end;
}
/* char_escape -- '\\' backslash escape */
static size_t char_escape(
|
| ︙ | ︙ | |||
984 985 986 987 988 989 990 |
i++;
/* skip any amount of whitespace or newline */
/* (this is much more laxist than original markdown syntax) */
while( i<size && (data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){ i++; }
/* allocate temporary buffers to store content, link and title */
| | | | < | 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 |
i++;
/* skip any amount of whitespace or newline */
/* (this is much more laxist than original markdown syntax) */
while( i<size && (data[i]==' ' || data[i]=='\t' || data[i]=='\n') ){ i++; }
/* allocate temporary buffers to store content, link and title */
title = new_work_buffer(rndr);
content = new_work_buffer(rndr);
link = new_work_buffer(rndr);
ret = 0; /* error if we don't get to the callback */
/* inline style link */
if( i<size && data[i]=='(' ){
size_t span_end = i;
while( span_end<size
&& !(data[span_end]==')' && (span_end==i || data[span_end-1]!='\\'))
|
| ︙ | ︙ | |||
1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 |
/* check for initial '|' */
if( i<size && data[i]=='|') outer_sep++;
/* count the number of pipes in the line */
for(n_sep=0; i<size && data[i]!='\n'; i++){
if( is_table_sep(data, i) ) n_sep++;
}
/* march back to check for optional last '|' before blanks and EOL */
while( i && (data[i-1]==' ' || data[i-1]=='\t' || data[i-1]=='\n') ){ i--; }
if( i && is_table_sep(data, i-1) ) outer_sep += 1;
/* return the number of column or 0 if it's not a table line */
| > > > > | 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 |
/* check for initial '|' */
if( i<size && data[i]=='|') outer_sep++;
/* count the number of pipes in the line */
for(n_sep=0; i<size && data[i]!='\n'; i++){
if( is_table_sep(data, i) ) n_sep++;
if( data[i]=='`' ){
skip_codespan(data, size, &i);
i--;
}
}
/* march back to check for optional last '|' before blanks and EOL */
while( i && (data[i-1]==' ' || data[i-1]=='\t' || data[i-1]=='\n') ){ i--; }
if( i && is_table_sep(data, i-1) ) outer_sep += 1;
/* return the number of column or 0 if it's not a table line */
|
| ︙ | ︙ | |||
1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 |
static size_t prefix_code(char *data, size_t size){
if( size>0 && data[0]=='\t' ) return 1;
if( size>3 && data[0]==' ' && data[1]==' ' && data[2]==' ' && data[3]==' ' ){
return 4;
}
return 0;
}
/* prefix_oli -- returns ordered list item prefix */
static size_t prefix_oli(char *data, size_t size){
size_t i = 0;
if( i<size && data[i]==' ') i++;
if( i<size && data[i]==' ') i++;
if( i<size && data[i]==' ') i++;
| > > > > > > > > > > > > | 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 |
static size_t prefix_code(char *data, size_t size){
if( size>0 && data[0]=='\t' ) return 1;
if( size>3 && data[0]==' ' && data[1]==' ' && data[2]==' ' && data[3]==' ' ){
return 4;
}
return 0;
}
/* Return the number of characters in the delimiter of a fenced code
** block. */
static size_t prefix_fencedcode(char *data, size_t size){
char c = data[0];
int nb;
if( c!='`' && c!='~' ) return 0;
for(nb=1; nb<size-3 && data[nb]==c; nb++){}
if( nb<3 ) return 0;
if( nb>=size-nb ) return 0;
return nb;
}
/* prefix_oli -- returns ordered list item prefix */
static size_t prefix_oli(char *data, size_t size){
size_t i = 0;
if( i<size && data[i]==' ') i++;
if( i<size && data[i]==' ') i++;
if( i<size && data[i]==' ') i++;
|
| ︙ | ︙ | |||
1280 1281 1282 1283 1284 1285 1286 |
}
work_size += end-beg;
}
beg = end;
}
if( rndr->make.blockquote ){
| < | | | < > > > | > > < | < < < | < | < < < | < | | < < < < < > > > | > > | | < | 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 |
}
work_size += end-beg;
}
beg = end;
}
if( rndr->make.blockquote ){
if( !too_deep(rndr) ){
parse_block(out, rndr, work_data, work_size);
}else{
blob_append(out, work_data, work_size);
}
rndr->make.blockquote(ob, out, rndr->make.opaque);
}
release_work_buffer(rndr, out);
return end;
}
/* parse_paragraph -- handles parsing of a regular paragraph */
static size_t parse_paragraph(
struct Blob *ob,
struct render *rndr,
char *data,
size_t size
){
size_t i = 0, end = 0;
int level = 0;
char *work_data = data;
size_t work_size = 0;
while( i<size ){
char *zEnd = memchr(data+i, '\n', size-i-1);
end = zEnd==0 ? size : (int)(zEnd - (data-1));
/* The above is the same as:
** for(end=i+1; end<size && data[end-1]!='\n'; end++);
** "end" is left with a value such that data[end] is one byte
** past the first '\n' or one byte past the end of the string */
if( is_empty(data+i, size-i)
|| (level = is_headerline(data+i, size-i))!= 0
){
break;
}
if( (i && data[i]=='#') || is_hrule(data+i, size-i) ){
end = i;
break;
}
i = end;
}
work_size = i;
while( work_size && data[work_size-1]=='\n' ){ work_size--; }
if( !level ){
if( rndr->make.paragraph ){
struct Blob *tmp = new_work_buffer(rndr);
parse_inline(tmp, rndr, work_data, work_size);
rndr->make.paragraph(ob, tmp, rndr->make.opaque);
release_work_buffer(rndr, tmp);
}
}else{
if( work_size ){
size_t beg;
i = work_size;
work_size -= 1;
while( work_size && data[work_size]!='\n' ){ work_size--; }
beg = work_size+1;
while( work_size && data[work_size-1]=='\n'){ work_size--; }
if( work_size ){
struct Blob *tmp = new_work_buffer(rndr);
parse_inline(tmp, rndr, work_data, work_size);
if( rndr->make.paragraph ){
rndr->make.paragraph(ob, tmp, rndr->make.opaque);
}
release_work_buffer(rndr, tmp);
work_data += beg;
work_size = i - beg;
}else{
work_size = i;
}
}
if( rndr->make.header ){
struct Blob *span = new_work_buffer(rndr);
parse_inline(span, rndr, work_data, work_size);
rndr->make.header(ob, span, level, rndr->make.opaque);
release_work_buffer(rndr, span);
}
}
return end;
}
/* parse_blockcode -- handles parsing of a block-level code fragment */
static size_t parse_blockcode(
struct Blob *ob,
struct render *rndr,
char *data,
size_t size
){
size_t beg, end, pre;
struct Blob *work = new_work_buffer(rndr);
beg = 0;
while( beg<size ){
char *zEnd = memchr(data+beg, '\n', size-beg-1);
end = zEnd==0 ? size : (int)(zEnd - (data-1));
/* The above is the same as:
** for(end=beg+1; end<size && data[end-1]!='\n'; end++);
** "end" is left with a value such that data[end] is one byte
** past the first \n or past then end of the string. */
pre = prefix_code(data+beg, end-beg);
if( pre ){
beg += pre; /* skipping prefix */
}else if( !is_empty(data+beg, end-beg) ){
/* non-empty non-prefixed line breaks the pre */
break;
}
if( beg<end ){
/* verbatim copy to the working buffer, escaping entities */
if( is_empty(data + beg, end - beg) ){
blob_append_char(work, '\n');
}else{
blob_append(work, data+beg, end-beg);
}
}
beg = end;
}
end = blob_size(work);
while( end>0 && blob_buffer(work)[end-1]=='\n' ){ end--; }
work->nUsed = end;
blob_append_char(work, '\n');
if( work!=ob ){
if( rndr->make.blockcode ){
rndr->make.blockcode(ob, work, rndr->make.opaque);
}
release_work_buffer(rndr, work);
}
return beg;
}
/* parse_listitem -- parsing of a single list item */
/* assuming initial prefix is already removed */
static size_t parse_listitem(
struct Blob *ob,
struct render *rndr,
char *data,
size_t size,
int *flags
){
struct Blob *work = 0, *inter = 0;
size_t beg = 0, end, pre, sublist = 0, orgpre = 0, i;
int in_empty = 0, has_inside_empty = 0;
/* keeping track of the first indentation prefix */
if( size>1 && data[0]==' ' ){
orgpre = 1;
|
| ︙ | ︙ | |||
1457 1458 1459 1460 1461 1462 1463 |
/* skipping to the beginning of the following line */
end = beg;
while( end<size && data[end-1]!='\n' ){ end++; }
/* getting working buffers */
work = new_work_buffer(rndr);
inter = new_work_buffer(rndr);
| < | 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 |
/* skipping to the beginning of the following line */
end = beg;
while( end<size && data[end-1]!='\n' ){ end++; }
/* getting working buffers */
work = new_work_buffer(rndr);
inter = new_work_buffer(rndr);
/* putting the first line into the working buffer */
blob_append(work, data+beg, end-beg);
beg = end;
/* process the following lines */
while( beg<size ){
|
| ︙ | ︙ | |||
1507 1508 1509 1510 1511 1512 1513 |
if( !sublist ) sublist = blob_size(work);
/* joining only indented stuff after empty lines */
}else if( in_empty && i<4 && data[beg]!='\t' ){
*flags |= MKD_LI_END;
break;
}else if( in_empty ){
| | | < | 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 |
if( !sublist ) sublist = blob_size(work);
/* joining only indented stuff after empty lines */
}else if( in_empty && i<4 && data[beg]!='\t' ){
*flags |= MKD_LI_END;
break;
}else if( in_empty ){
blob_append_char(work, '\n');
has_inside_empty = 1;
}
in_empty = 0;
/* adding the line without prefix into the working buffer */
blob_append(work, data+beg+i, end-beg-i);
beg = end;
}
/* non-recursive fallback when working buffer stack is full */
if( !inter ){
if( rndr->make.listitem ){
rndr->make.listitem(ob, work, *flags, rndr->make.opaque);
}
release_work_buffer(rndr, work);
return beg;
}
/* render of li contents */
if( has_inside_empty ) *flags |= MKD_LI_BLOCK;
if( *flags & MKD_LI_BLOCK ){
/* intermediate render of block li */
|
| ︙ | ︙ | |||
1558 1559 1560 1561 1562 1563 1564 |
}
/* render of li itself */
if( rndr->make.listitem ){
rndr->make.listitem(ob, inter, *flags, rndr->make.opaque);
}
release_work_buffer(rndr, inter);
| | < < < | < | 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 |
}
/* render of li itself */
if( rndr->make.listitem ){
rndr->make.listitem(ob, inter, *flags, rndr->make.opaque);
}
release_work_buffer(rndr, inter);
release_work_buffer(rndr, work);
return beg;
}
/* parse_list -- parsing ordered or unordered list block */
static size_t parse_list(
struct Blob *ob,
struct render *rndr,
char *data,
size_t size,
int flags
){
struct Blob *work = new_work_buffer(rndr);
size_t i = 0, j;
while( i<size ){
j = parse_listitem(work, rndr, data+i, size-i, &flags);
i += j;
if( !j || (flags & MKD_LI_END) ) break;
}
if( rndr->make.list ) rndr->make.list(ob, work, flags, rndr->make.opaque);
release_work_buffer(rndr, work);
return i;
}
/* parse_atxheader -- parsing of atx-style headers */
static size_t parse_atxheader(
struct Blob *ob,
|
| ︙ | ︙ | |||
1615 1616 1617 1618 1619 1620 1621 |
if( end<=i ) return parse_paragraph(ob, rndr, data, size);
while( end && data[end-1]=='#' ){ end--; }
while( end && (data[end-1]==' ' || data[end-1]=='\t') ){ end--; }
if( end<=i ) return parse_paragraph(ob, rndr, data, size);
span_size = end-span_beg;
if( rndr->make.header ){
| < < < | < < < | | 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 |
if( end<=i ) return parse_paragraph(ob, rndr, data, size);
while( end && data[end-1]=='#' ){ end--; }
while( end && (data[end-1]==' ' || data[end-1]=='\t') ){ end--; }
if( end<=i ) return parse_paragraph(ob, rndr, data, size);
span_size = end-span_beg;
if( rndr->make.header ){
struct Blob *span = new_work_buffer(rndr);
parse_inline(span, rndr, data+span_beg, span_size);
rndr->make.header(ob, span, level, rndr->make.opaque);
release_work_buffer(rndr, span);
}
return skip;
}
/* htmlblock_end -- checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */
|
| ︙ | ︙ | |||
1784 1785 1786 1787 1788 1789 1790 |
static void parse_table_cell(
struct Blob *ob, /* output blob */
struct render *rndr, /* renderer description */
char *data, /* input text */
size_t size, /* input text size */
int flags /* table flags */
){
| < < < | < < < | | 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 |
static void parse_table_cell(
struct Blob *ob, /* output blob */
struct render *rndr, /* renderer description */
char *data, /* input text */
size_t size, /* input text size */
int flags /* table flags */
){
struct Blob *span = new_work_buffer(rndr);
parse_inline(span, rndr, data, size);
rndr->make.table_cell(ob, span, flags, rndr->make.opaque);
release_work_buffer(rndr, span);
}
/* parse_table_row -- parse an input line into a table row */
static size_t parse_table_row(
struct Blob *ob, /* output blob for rendering */
|
| ︙ | ︙ | |||
1830 1831 1832 1833 1834 1835 1836 |
}
/* skip blanks */
while( i<size && (data[i]==' ' || data[i]=='\t') ){ i++; }
beg = i;
/* forward to the next separator or EOL */
| | > > > > > > | 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 |
}
/* skip blanks */
while( i<size && (data[i]==' ' || data[i]=='\t') ){ i++; }
beg = i;
/* forward to the next separator or EOL */
while( i<size && !is_table_sep(data, i) && data[i]!='\n' ){
if( data[i]=='`' ){
skip_codespan(data, size, &i);
}else{
i++;
}
}
end = i;
if( i<size ){
i++;
if( data[i-1]=='\n' ) total = i;
}
/* check optional right/center align marker */
|
| ︙ | ︙ | |||
1854 1855 1856 1857 1858 1859 1860 |
/* (because it is only the optional end separator) */
if( total && end<=beg ) continue;
/* fallback on default alignment if not explicit */
if( align==0 && aligns && col<align_size ) align = aligns[col];
/* render cells */
| > | > < | < < < < < < < | | 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 |
/* (because it is only the optional end separator) */
if( total && end<=beg ) continue;
/* fallback on default alignment if not explicit */
if( align==0 && aligns && col<align_size ) align = aligns[col];
/* render cells */
if( cells && end>beg ){
parse_table_cell(cells, rndr, data+beg, end-beg, align|flags);
}
col++;
}
/* render the whole row and clean up */
rndr->make.table_row(ob, cells, flags, rndr->make.opaque);
release_work_buffer(rndr, cells);
return total ? total : size;
}
/* parse_table -- parsing of a whole table */
static size_t parse_table(
struct Blob *ob,
struct render *rndr,
char *data,
size_t size
){
size_t i = 0, head_end, col;
size_t align_size = 0;
int *aligns = 0;
struct Blob *head = 0;
struct Blob *rows = new_work_buffer(rndr);
/* skip the first (presumably header) line */
while( i<size && data[i]!='\n' ){ i++; }
head_end = i;
/* fallback on end of input */
if( i>=size ){
parse_table_row(rows, rndr, data, size, 0, 0, 0);
rndr->make.table(ob, 0, rows, rndr->make.opaque);
release_work_buffer(rndr, rows);
return i;
}
/* attempt to parse a table rule, i.e. blanks, dash, colons and sep */
i++;
col = 0;
while( i<size
|
| ︙ | ︙ | |||
1919 1920 1921 1922 1923 1924 1925 |
}
if( i<size && data[i]=='\n' ){
align_size++;
/* render the header row */
head = new_work_buffer(rndr);
| < | < | 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 |
}
if( i<size && data[i]=='\n' ){
align_size++;
/* render the header row */
head = new_work_buffer(rndr);
parse_table_row(head, rndr, data, head_end, 0, 0, MKD_CELL_HEAD);
/* parse alignments if provided */
if( col && (aligns=fossil_malloc(align_size * sizeof *aligns))!=0 ){
for(i=0; i<align_size; i++) aligns[i] = 0;
col = 0;
i = head_end+1;
|
| ︙ | ︙ | |||
1964 1965 1966 1967 1968 1969 1970 |
i += parse_table_row(rows, rndr, data+i, size-i, aligns, align_size, 0);
}
/* render the full table */
rndr->make.table(ob, head, rows, rndr->make.opaque);
/* cleanup */
| | | | > | 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 |
i += parse_table_row(rows, rndr, data+i, size-i, aligns, align_size, 0);
}
/* render the full table */
rndr->make.table(ob, head, rows, rndr->make.opaque);
/* cleanup */
release_work_buffer(rndr, head);
release_work_buffer(rndr, rows);
fossil_free(aligns);
return i;
}
/* parse_block -- parsing of one block, returning next char to parse */
static void parse_block(
struct Blob *ob, /* output blob */
struct render *rndr, /* renderer internal state */
char *data, /* input text */
size_t size /* input text size */
){
size_t beg, end, i;
char *txt_data;
int has_table = (rndr->make.table
&& rndr->make.table_row
&& rndr->make.table_cell
&& memchr(data, '|', size)!=0);
beg = 0;
while( beg<size ){
txt_data = data+beg;
end = size-beg;
if( data[beg]=='#' ){
beg += parse_atxheader(ob, rndr, txt_data, end);
|
| ︙ | ︙ | |||
2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 |
beg += parse_blockcode(ob, rndr, txt_data, end);
}else if( prefix_uli(txt_data, end) ){
beg += parse_list(ob, rndr, txt_data, end, 0);
}else if( prefix_oli(txt_data, end) ){
beg += parse_list(ob, rndr, txt_data, end, MKD_LIST_ORDERED);
}else if( has_table && is_tableline(txt_data, end) ){
beg += parse_table(ob, rndr, txt_data, end);
}else{
beg += parse_paragraph(ob, rndr, txt_data, end);
}
}
}
| > > > > | 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 |
beg += parse_blockcode(ob, rndr, txt_data, end);
}else if( prefix_uli(txt_data, end) ){
beg += parse_list(ob, rndr, txt_data, end, 0);
}else if( prefix_oli(txt_data, end) ){
beg += parse_list(ob, rndr, txt_data, end, MKD_LIST_ORDERED);
}else if( has_table && is_tableline(txt_data, end) ){
beg += parse_table(ob, rndr, txt_data, end);
}else if( prefix_fencedcode(txt_data, end)
&& (i = char_codespan(ob, rndr, txt_data, 0, end))!=0
){
beg += i;
}else{
beg += parse_paragraph(ob, rndr, txt_data, end);
}
}
}
|
| ︙ | ︙ | |||
2159 2160 2161 2162 2163 2164 2165 |
/* markdown -- parses the input buffer and renders it into the output buffer */
void markdown(
struct Blob *ob, /* output blob for rendered text */
struct Blob *ib, /* input blob in markdown */
const struct mkd_renderer *rndrer /* renderer descriptor (callbacks) */
){
struct link_ref *lr;
| < > | | < < | | 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 |
/* markdown -- parses the input buffer and renders it into the output buffer */
void markdown(
struct Blob *ob, /* output blob for rendered text */
struct Blob *ib, /* input blob in markdown */
const struct mkd_renderer *rndrer /* renderer descriptor (callbacks) */
){
struct link_ref *lr;
size_t i, beg, end = 0;
struct render rndr;
char *ib_data;
Blob text = BLOB_INITIALIZER;
/* filling the render structure */
if( !rndrer ) return;
rndr.make = *rndrer;
rndr.nBlobCache = 0;
rndr.iDepth = 0;
rndr.refs = empty_blob;
for(i=0; i<256; i++) rndr.active_char[i] = 0;
if( (rndr.make.emphasis
|| rndr.make.double_emphasis
|| rndr.make.triple_emphasis)
&& rndr.make.emph_chars
){
for(i=0; rndr.make.emph_chars[i]; i++){
|
| ︙ | ︙ | |||
2207 2208 2209 2210 2211 2212 2213 |
/* adding the line body if present */
if( end>beg ) blob_append(&text, ib_data + beg, end - beg);
while( end<blob_size(ib) && (ib_data[end]=='\n' || ib_data[end]=='\r') ){
/* add one \n per newline */
if( ib_data[end]=='\n'
|| (end+1<blob_size(ib) && ib_data[end+1]!='\n')
){
| | | 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 |
/* adding the line body if present */
if( end>beg ) blob_append(&text, ib_data + beg, end - beg);
while( end<blob_size(ib) && (ib_data[end]=='\n' || ib_data[end]=='\r') ){
/* add one \n per newline */
if( ib_data[end]=='\n'
|| (end+1<blob_size(ib) && ib_data[end+1]!='\n')
){
blob_append_char(&text, '\n');
}
end += 1;
}
beg = end;
}
}
|
| ︙ | ︙ | |||
2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 |
/* second pass: actual rendering */
if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
/* clean-up */
blob_reset(&text);
lr = (struct link_ref *)blob_buffer(&rndr.refs);
end = blob_size(&rndr.refs)/sizeof(struct link_ref);
for(i=0; i<end; i++){
blob_reset(&lr[i].id);
blob_reset(&lr[i].link);
blob_reset(&lr[i].title);
}
blob_reset(&rndr.refs);
| > | | | > | 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 |
/* second pass: actual rendering */
if( rndr.make.prolog ) rndr.make.prolog(ob, rndr.make.opaque);
parse_block(ob, &rndr, blob_buffer(&text), blob_size(&text));
if( rndr.make.epilog ) rndr.make.epilog(ob, rndr.make.opaque);
/* clean-up */
assert( rndr.iDepth==0 );
blob_reset(&text);
lr = (struct link_ref *)blob_buffer(&rndr.refs);
end = blob_size(&rndr.refs)/sizeof(struct link_ref);
for(i=0; i<end; i++){
blob_reset(&lr[i].id);
blob_reset(&lr[i].link);
blob_reset(&lr[i].title);
}
blob_reset(&rndr.refs);
for(i=0; i<rndr.nBlobCache; i++){
fossil_free(rndr.aBlobCache[i]);
}
}
|
| ︙ | ︙ | |||
24 25 26 27 28 29 30 31 | > 1. **\[display text\]\(URL\)** > 2. **\[display text\]\(URL "Title"\)** > 3. **\[display text\]\(URL 'Title'\)** > 4. **\<URL\>** > 5. **\[display text\]\[label\]** > 6. **\[display text\]\[\]** > 7. **\[display text\]** | > | | | | < > > > > > > > > > > > > > > > | 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 |
> 1. **\[display text\]\(URL\)**
> 2. **\[display text\]\(URL "Title"\)**
> 3. **\[display text\]\(URL 'Title'\)**
> 4. **\<URL\>**
> 5. **\[display text\]\[label\]**
> 6. **\[display text\]\[\]**
> 7. **\[display text\]**
> 8. **\[\]\(URL\)**
> With link formats 5, 6, and 7 ("reference links"), the URL is supplied
> elsewhere in the document, as shown below. Link formats 6 and 7 reuse
> the display text as the label. Labels are case-insensitive. The title
> may be split onto the next line with optional indenting.
> * **\[label\]: URL**
> * **\[label\]: URL "Title"**
> * **\[label\]: URL 'Title'**
> * **\[label\]: URL (Title)**
> If **URL** begins with "http:", "https:', "ftp:' or "mailto:",
> it may optionally be written **\<URL\>** (format 4).
> Other **URL** formats include:
> <ul>
> <li> A relative pathname.
> <li> A pathname starting with "/" in which case the Fossil server
> URL prefix is prepended
> <li> A wiki page name, or a wiki page name preceded by "wiki:"
> <li> An artifact or ticket hash or hash prefix
> <li> A date and time stamp: "YYYY-MM-DD HH:MM:SS" or a subset that
> includes at least the day of the month.</ul>
> In format 8, then the URL becomes the display text. This is useful for
> hyperlinks that refer to wiki pages and check-in and ticket hashes.
## Fonts ##
> * _\*italic\*_
> * *\_italic\_*
> * __\*\*bold\*\*__
> * **\_\_bold\_\_**
> * ___\*\*\*italic+bold\*\*\*___
|
| ︙ | ︙ | |||
74 75 76 77 78 79 80 |
> Begin each line of a paragraph with **>** to block quote that paragraph.
> >
> This paragraph is indented
> >
> > Double-indented paragraph
| | < < < | | | | < | > | > > < < < | 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 |
> Begin each line of a paragraph with **>** to block quote that paragraph.
> >
> This paragraph is indented
> >
> > Double-indented paragraph
## Literal/Verbatim Text - Code Blocks ##
> For inline text, you can either use \``backticks`\` or the HTML
> `<code>` tag.
>
> For blocks of text or code:
>
> 1. Indent the text using a tab character or at least four spaces.
> 2. Precede the block with an HTML `<pre>` tag and follow it with `</pre>`.
> 3. Surround the block by <tt>\`\`\`</tt> (three or more) or <tt>\~\~\~</tt> either at the
> left margin or indented no more than three spaces. The first word
> on that same line (if any) is used in a “`language-WORD`” CSS style in
> the HTML rendering of that code block and is intended for use by
> code syntax highlighters. Thus <tt>\`\`\`c</tt> would mark a block of code
> in the C programming language. Text to be rendered inside the code block
> should therefore start on the next line, not be cuddled up with the
> backticks or tildes.
> With the standard skins, verbatim text is rendered in a fixed-width font,
> but that is purely a presentation matter, controlled by the skin’s CSS.
## Tables ##
>
| Header 1 | Header 2 | Header 3 |
----------------------------------------------
| Row 1 Col 1 | Row 1 Col 2 | Row 1 Col 3 |
|:Left-aligned |:Centered :| Right-aligned:|
|
| ︙ | ︙ |
| ︙ | ︙ | |||
30 31 32 33 34 35 36 | struct Blob *output_body); #endif /* INTERFACE */ /* INTER_BLOCK -- skip a line between block level elements */ #define INTER_BLOCK(ob) \ | | | 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
struct Blob *output_body);
#endif /* INTERFACE */
/* INTER_BLOCK -- skip a line between block level elements */
#define INTER_BLOCK(ob) \
do { if( blob_size(ob)>0 ) blob_append_char(ob, '\n'); } while (0)
/* BLOB_APPEND_LITERAL -- append a string literal to a blob */
#define BLOB_APPEND_LITERAL(blob, literal) \
blob_append((blob), "" literal, (sizeof literal)-1)
/*
* The empty string in the second argument leads to a syntax error
* when the macro is not used with a string literal. Unfortunately
|
| ︙ | ︙ | |||
127 128 129 130 131 132 133 |
}
static void html_epilog(struct Blob *ob, void *opaque){
INTER_BLOCK(ob);
BLOB_APPEND_LITERAL(ob, "</div>\n");
}
| | | | 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 |
}
static void html_epilog(struct Blob *ob, void *opaque){
INTER_BLOCK(ob);
BLOB_APPEND_LITERAL(ob, "</div>\n");
}
static void html_blockhtml(struct Blob *ob, struct Blob *text, void *opaque){
char *data = blob_buffer(text);
size_t size = blob_size(text);
Blob *title = (Blob*)opaque;
while( size>0 && fossil_isspace(data[0]) ){ data++; size--; }
while( size>0 && fossil_isspace(data[size-1]) ){ size--; }
/* If the first raw block is an <h1> element, then use it as the title. */
if( blob_size(ob)<=PROLOG_SIZE
&& size>9
&& title!=0
&& sqlite3_strnicmp("<h1",data,3)==0
&& sqlite3_strnicmp("</h1>", &data[size-5],5)==0
){
int nTag = html_tag_length(data);
blob_append(title, data+nTag, size - nTag - 5);
return;
}
INTER_BLOCK(ob);
blob_append(ob, data, size);
BLOB_APPEND_LITERAL(ob, "\n");
}
|
| ︙ | ︙ | |||
296 297 298 299 300 301 302 | BLOB_APPEND_LITERAL(ob, " </tr>\n"); } /* HTML span tags */ | | < | | 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
BLOB_APPEND_LITERAL(ob, " </tr>\n");
}
/* HTML span tags */
static int html_raw_html_tag(struct Blob *ob, struct Blob *text, void *opaque){
blob_append(ob, blob_buffer(text), blob_size(text));
return 1;
}
static int html_autolink(
struct Blob *ob,
struct Blob *link,
enum mkd_autolink type,
|
| ︙ | ︙ | |||
323 324 325 326 327 328 329 |
}else{
html_escape(ob, blob_buffer(link), blob_size(link));
}
BLOB_APPEND_LITERAL(ob, "</a>");
return 1;
}
| > > > > > > > > > | > > > > > | > > > > > > > > > > > > > > > > > > > > > > > | 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 |
}else{
html_escape(ob, blob_buffer(link), blob_size(link));
}
BLOB_APPEND_LITERAL(ob, "</a>");
return 1;
}
/* Invoked for `...` blocks where there are nSep grave accents in a
** row that serve as the delimiter. According to CommonMark:
**
** * https://spec.commonmark.org/0.29/#fenced-code-blocks
** * https://spec.commonmark.org/0.29/#code-spans
**
** If nSep is 1 or 2, then this is a code-span which is inline.
** If nSep is 3 or more, then this is a fenced code block
*/
static int html_codespan(
struct Blob *ob, /* Write the output here */
struct Blob *text, /* The stuff in between the code span marks */
int nSep, /* Number of grave accents marks as delimiters */
void *opaque
){
if( text==0 ){
/* no-op */
}else if( nSep<=2 ){
/* One or two graves: an in-line code span */
BLOB_APPEND_LITERAL(ob, "<code>");
html_escape(ob, blob_buffer(text), blob_size(text));
BLOB_APPEND_LITERAL(ob, "</code>");
}else{
/* Three or more graves: a fenced code block */
int n = blob_size(text);
const char *z = blob_buffer(text);
int i;
for(i=0; i<n && z[i]!='\n'; i++){}
if( i>=n ){
blob_appendf(ob, "<pre><code>%#h</code></pre>", n, z);
}else{
int k, j;
i++;
for(k=0; k<i && fossil_isspace(z[k]); k++){}
if( k==i ){
blob_appendf(ob, "<pre><code>%#h</code></pre>", n-i, z+i);
}else{
for(j=k+1; j<i && !fossil_isspace(z[j]); j++){}
blob_appendf(ob, "<pre><code class='language-%#h'>%#h</code></pre>",
j-k, z+k, n-i, z+i);
}
}
}
return 1;
}
static int html_double_emphasis(
struct Blob *ob,
struct Blob *text,
|
| ︙ | ︙ | |||
375 376 377 378 379 380 381 |
BLOB_APPEND_LITERAL(ob, "\" title=\"");
html_quote(ob, blob_buffer(title), blob_size(title));
}
BLOB_APPEND_LITERAL(ob, "\" />");
return 1;
}
| | > > | | > > > > > > | < < < | | < | < | > | | 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 |
BLOB_APPEND_LITERAL(ob, "\" title=\"");
html_quote(ob, blob_buffer(title), blob_size(title));
}
BLOB_APPEND_LITERAL(ob, "\" />");
return 1;
}
static int html_linebreak(struct Blob *ob, void *opaque){
BLOB_APPEND_LITERAL(ob, "<br />\n");
return 1;
}
static int html_link(
struct Blob *ob,
struct Blob *link,
struct Blob *title,
struct Blob *content,
void *opaque
){
char *zLink = blob_buffer(link);
char *zTitle = title!=0 && blob_size(title)>0 ? blob_str(title) : 0;
char zClose[20];
if( zLink==0 || zLink[0]==0 ){
zClose[0] = 0;
}else{
static const int flags =
WIKI_NOBADLINKS |
WIKI_MARKDOWNLINKS
;
wiki_resolve_hyperlink(ob, flags, zLink, zClose, sizeof(zClose), 0, zTitle);
}
if( blob_size(content)==0 ){
if( link ) BLOB_APPEND_BLOB(ob, link);
}else{
BLOB_APPEND_BLOB(ob, content);
}
blob_append(ob, zClose, -1);
return 1;
}
static int html_triple_emphasis(
struct Blob *ob,
struct Blob *text,
char c,
|
| ︙ | ︙ | |||
441 442 443 444 445 446 447 |
/* prolog and epilog */
html_prolog,
html_epilog,
/* block level elements */
html_blockcode,
html_blockquote,
| | | | | | < | | | 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 |
/* prolog and epilog */
html_prolog,
html_epilog,
/* block level elements */
html_blockcode,
html_blockquote,
html_blockhtml,
html_header,
html_hrule,
html_list,
html_list_item,
html_paragraph,
html_table,
html_table_cell,
html_table_row,
/* span level elements */
html_autolink,
html_codespan,
html_double_emphasis,
html_emphasis,
html_image,
html_linebreak,
html_link,
html_raw_html_tag,
html_triple_emphasis,
/* low level elements */
0, /* entity */
html_normal_text,
/* misc. parameters */
"*_", /* emph_chars */
0 /* opaque */
};
html_renderer.opaque = output_title;
if( output_title ) blob_reset(output_title);
blob_reset(output_body);
markdown(output_body, input_markdown, &html_renderer);
}
|
| ︙ | ︙ | |||
193 194 195 196 197 198 199 | ** If the VERSION argument is omitted, then Fossil attempts to find ** a recent fork on the current branch to merge. ** ** Only file content is merged. The result continues to use the ** file and directory names from the current checkout even if those ** names might have been changed in the branch being merged in. ** | | > > > > > > > > > > > > > > | 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 |
** If the VERSION argument is omitted, then Fossil attempts to find
** a recent fork on the current branch to merge.
**
** Only file content is merged. The result continues to use the
** file and directory names from the current checkout even if those
** names might have been changed in the branch being merged in.
**
** Options:
**
** --backout Do a reverse cherrypick merge against VERSION.
** In other words, back out the changes that were
** added by VERSION.
**
** --baseline BASELINE Use BASELINE as the "pivot" of the merge instead
** of the nearest common ancestor. This allows
** a sequence of changes in a branch to be merged
** without having to merge the entire branch.
**
** --binary GLOBPATTERN Treat files that match GLOBPATTERN as binary
** and do not try to merge parallel changes. This
** option overrides the "binary-glob" setting.
**
** --case-sensitive BOOL Override the case-sensitive setting. If false,
** files whose names differ only in case are taken
** to be the same file.
**
** --cherrypick Do a cherrypick merge VERSION into the current
** checkout. A cherrypick merge pulls in the changes
** of the single check-in VERSION, rather than all
** changes back to the nearest common ancestor.
**
** -f|--force Force the merge even if it would be a no-op.
**
** --force-missing Force the merge even if there is missing content.
**
** --integrate Merged branch will be closed when committing.
**
** -K|--keep-merge-files On merge conflict, retain the temporary files
** used for merging, named *-baseline, *-original,
** and *-merge.
**
** -n|--dry-run If given, display instead of run actions
**
** -v|--verbose Show additional details of the merge
*/
void merge_cmd(void){
int vid; /* Current version "V" */
int mid; /* Version we are merging from "M" */
int pid = 0; /* The pivot version - most recent common ancestor P */
int nid = 0; /* The name pivot version "N" */
int verboseFlag; /* True if the -v|--verbose option is present */
int integrateFlag; /* True if the --integrate option is present */
int pickFlag; /* True if the --cherrypick option is present */
int backoutFlag; /* True if the --backout option is present */
int dryRunFlag; /* True if the --dry-run or -n option is present */
int forceFlag; /* True if the --force or -f option is present */
int forceMissingFlag; /* True if the --force-missing option is present */
const char *zBinGlob; /* The value of --binary */
const char *zPivot; /* The value of --baseline */
int debugFlag; /* True if --debug is present */
int keepMergeFlag; /* True if --keep-merge-files is present */
int nConflict = 0; /* Number of conflicts seen */
int nOverwrite = 0; /* Number of unmanaged files overwritten */
char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */
Stmt q;
/* Notation:
|
| ︙ | ︙ | |||
264 265 266 267 268 269 270 271 272 273 274 275 276 277 |
zBinGlob = find_option("binary",0,1);
dryRunFlag = find_option("dry-run","n",0)!=0;
if( !dryRunFlag ){
dryRunFlag = find_option("nochange",0,0)!=0; /* deprecated */
}
forceFlag = find_option("force","f",0)!=0;
zPivot = find_option("baseline",0,1);
verify_all_options();
db_must_be_within_tree();
if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
vid = db_lget_int("checkout", 0);
if( vid==0 ){
fossil_fatal("nothing is checked out");
}
| > > | 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
zBinGlob = find_option("binary",0,1);
dryRunFlag = find_option("dry-run","n",0)!=0;
if( !dryRunFlag ){
dryRunFlag = find_option("nochange",0,0)!=0; /* deprecated */
}
forceFlag = find_option("force","f",0)!=0;
zPivot = find_option("baseline",0,1);
keepMergeFlag = find_option("keep-merge-files", "K",0)!=0;
verify_all_options();
db_must_be_within_tree();
if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
vid = db_lget_int("checkout", 0);
if( vid==0 ){
fossil_fatal("nothing is checked out");
}
|
| ︙ | ︙ | |||
387 388 389 390 391 392 393 394 395 396 397 398 399 400 |
fossil_print("Merge skipped because it is a no-op. "
" Use --force to override.\n");
return;
}
if( integrateFlag && !is_a_leaf(mid)){
fossil_warning("ignoring --integrate: %s is not a leaf", g.argv[2]);
integrateFlag = 0;
}
if( verboseFlag ){
print_checkin_description(mid, 12,
integrateFlag ? "integrate:" : "merge-from:");
print_checkin_description(pid, 12, "baseline:");
}
vfile_check_signature(vid, CKSIG_ENOTFILE);
| > > > > > > > | 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
fossil_print("Merge skipped because it is a no-op. "
" Use --force to override.\n");
return;
}
if( integrateFlag && !is_a_leaf(mid)){
fossil_warning("ignoring --integrate: %s is not a leaf", g.argv[2]);
integrateFlag = 0;
}
if( integrateFlag && content_is_private(mid) ){
fossil_warning(
"ignoring --integrate: %s is on a private branch"
"\n Use \"fossil amend --close\" (after commit) to close the leaf.",
g.argv[2]);
integrateFlag = 0;
}
if( verboseFlag ){
print_checkin_description(mid, 12,
integrateFlag ? "integrate:" : "merge-from:");
print_checkin_description(pid, 12, "baseline:");
}
vfile_check_signature(vid, CKSIG_ENOTFILE);
|
| ︙ | ︙ | |||
654 655 656 657 658 659 660 661 662 663 664 665 666 667 |
content_get(ridp, &p);
content_get(ridm, &m);
if( isBinary ){
rc = -1;
blob_zero(&r);
}else{
unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0;
rc = merge_3way(&p, zFullPath, &m, &r, mergeFlags);
}
if( rc>=0 ){
if( !dryRunFlag ){
blob_write_to_file(&r, zFullPath);
file_setexe(zFullPath, isExe);
}
| > | 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 |
content_get(ridp, &p);
content_get(ridm, &m);
if( isBinary ){
rc = -1;
blob_zero(&r);
}else{
unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0;
if(keepMergeFlag!=0) mergeFlags |= MERGE_KEEP_FILES;
rc = merge_3way(&p, zFullPath, &m, &r, mergeFlags);
}
if( rc>=0 ){
if( !dryRunFlag ){
blob_write_to_file(&r, zFullPath);
file_setexe(zFullPath, isExe);
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
137 138 139 140 141 142 143 |
/*
** Text of boundary markers for merge conflicts.
*/
static const char *const mergeMarker[] = {
/*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/
"<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<<<<<\n",
| | | 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
/*
** Text of boundary markers for merge conflicts.
*/
static const char *const mergeMarker[] = {
/*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/
"<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<<<<<\n",
"||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||||||\n",
"======= MERGED IN content follows ==================================\n",
">>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
};
/*
** Do a three-way merge. Initialize pOut to contain the result.
|
| ︙ | ︙ | |||
434 435 436 437 438 439 440 441 442 443 444 445 446 447 | } #if INTERFACE /* ** Flags to the 3-way merger */ #define MERGE_DRYRUN 0x0001 #endif /* ** This routine is a wrapper around blob_merge() with the following ** enhancements: ** | > > > > > > | 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 | } #if INTERFACE /* ** Flags to the 3-way merger */ #define MERGE_DRYRUN 0x0001 /* ** The MERGE_KEEP_FILES flag specifies that merge_3way() should retain ** its temporary files on error. By default they are removed after the ** merge, regardless of success or failure. */ #define MERGE_KEEP_FILES 0x0002 #endif /* ** This routine is a wrapper around blob_merge() with the following ** enhancements: ** |
| ︙ | ︙ | |||
463 464 465 466 467 468 469 470 471 472 |
const char *zV1, /* Name of file for version merging into (mine) */
Blob *pV2, /* Version merging from (yours) */
Blob *pOut, /* Output written here */
unsigned mergeFlags /* Flags that control operation */
){
Blob v1; /* Content of zV1 */
int rc; /* Return code of subroutines and this routine */
blob_read_from_file(&v1, zV1, ExtFILE);
rc = blob_merge(pPivot, &v1, pV2, pOut);
| > > | > > < < < < < < < > > > > > | 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 |
const char *zV1, /* Name of file for version merging into (mine) */
Blob *pV2, /* Version merging from (yours) */
Blob *pOut, /* Output written here */
unsigned mergeFlags /* Flags that control operation */
){
Blob v1; /* Content of zV1 */
int rc; /* Return code of subroutines and this routine */
const char *zGMerge; /* Name of the gmerge command */
blob_read_from_file(&v1, zV1, ExtFILE);
rc = blob_merge(pPivot, &v1, pV2, pOut);
zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0);
if( (mergeFlags & MERGE_DRYRUN)==0
&& ((zGMerge!=0 && zGMerge[0]!=0)
|| (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){
char *zPivot; /* Name of the pivot file */
char *zOrig; /* Name of the original content file */
char *zOther; /* Name of the merge file */
zPivot = file_newname(zV1, "baseline", 1);
blob_write_to_file(pPivot, zPivot);
zOrig = file_newname(zV1, "original", 1);
blob_write_to_file(&v1, zOrig);
zOther = file_newname(zV1, "merge", 1);
blob_write_to_file(pV2, zOther);
if( rc>0 ){
if( zGMerge && zGMerge[0] ){
char *zOut; /* Temporary output file */
char *zCmd; /* Command to invoke */
const char *azSubst[8]; /* Strings to be substituted */
zOut = file_newname(zV1, "output", 1);
azSubst[0] = "%baseline"; azSubst[1] = zPivot;
azSubst[2] = "%original"; azSubst[3] = zOrig;
azSubst[4] = "%merge"; azSubst[5] = zOther;
azSubst[6] = "%output"; azSubst[7] = zOut;
zCmd = string_subst(zGMerge, 8, azSubst);
printf("%s\n", zCmd); fflush(stdout);
fossil_system(zCmd);
if( file_size(zOut, RepoFILE)>=0 ){
blob_read_from_file(pOut, zOut, ExtFILE);
file_delete(zOut);
}
fossil_free(zCmd);
fossil_free(zOut);
}
}
if( (mergeFlags & MERGE_KEEP_FILES)==0 ){
file_delete(zPivot);
file_delete(zOrig);
file_delete(zOther);
}
fossil_free(zPivot);
fossil_free(zOrig);
fossil_free(zOther);
}
blob_reset(&v1);
return rc;
}
|
| ︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 24 25 26 27 28 | ** ******************************************************************************* ** ** This is a stand-alone utility program that is part of the Fossil build ** process. This program reads files named on the command line and converts ** them into ANSI-C static char array variables. Output is written onto ** standard output. ** ** The makefiles use this utility to package various resources (large scripts, ** GIF images, etc) that are separate files in the source code as byte ** arrays in the resulting executable. */ #include <stdio.h> #include <stdlib.h> | > > > > > > > | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | ** ******************************************************************************* ** ** This is a stand-alone utility program that is part of the Fossil build ** process. This program reads files named on the command line and converts ** them into ANSI-C static char array variables. Output is written onto ** standard output. ** ** Additionally, the input files may be listed in a separate list file (one ** resource name per line, optionally enclosed in double quotes). Pass the list ** via '--reslist <the-list-file>' option. Both lists, from the command line and ** the list file, are merged; duplicate file names skipped from processing. ** This option is useful to get around the command line length limitations ** under some OS, like Windows. ** ** The makefiles use this utility to package various resources (large scripts, ** GIF images, etc) that are separate files in the source code as byte ** arrays in the resulting executable. */ #include <stdio.h> #include <stdlib.h> |
| ︙ | ︙ | |||
99 100 101 102 103 104 105 106 107 108 109 110 111 |
*/
typedef struct Resource Resource;
struct Resource {
char *zName;
int nByte;
int idx;
};
/*
** Compare two Resource objects for sorting purposes. They sort
** in zName order so that Fossil can search for resources using
** a binary search.
*/
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > | > | > > | > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > | > > | | > | | > > | | > | > > > > > > > > > > > | > | 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 |
*/
typedef struct Resource Resource;
struct Resource {
char *zName;
int nByte;
int idx;
};
typedef struct ResourceList ResourceList;
struct ResourceList {
Resource *aRes;
int nRes;
char *buf;
long bufsize;
};
Resource *read_reslist(char *name, ResourceList *list){
#define RESLIST_BUF_MAXBYTES (1L<<20) /* 1 MB of text */
FILE *in;
long filesize = 0L;
long linecount = 0L;
char *p = 0;
char *pb = 0;
memset(list, 0, sizeof(*list));
if( (in = fopen(name, "rb"))==0 ){
return list->aRes;
}
fseek(in, 0L, SEEK_END);
filesize = ftell(in);
rewind(in);
if( filesize > RESLIST_BUF_MAXBYTES ){
fprintf(stderr, "List file [%s] must be smaller than %ld bytes\n", name,
RESLIST_BUF_MAXBYTES);
return list->aRes;
}
list->bufsize = filesize;
list->buf = (char *)calloc((list->bufsize + 2), sizeof(list->buf[0]));
if( list->buf==0 ){
fprintf(stderr, "failed to allocated %ld bytes\n", list->bufsize + 1);
list->bufsize = 0L;
return list->aRes;
}
filesize = fread(list->buf, sizeof(list->buf[0]),list->bufsize, in);
if ( filesize!=list->bufsize ){
fprintf(stderr, "failed to read [%s]\n", name);
return list->aRes;
}
fclose(in);
/*
** append an extra newline (if missing) for a correct line count
*/
if( list->buf[list->bufsize-1]!='\n' ) list->buf[list->bufsize]='\n';
linecount = 0L;
for( p = strchr(list->buf, '\n');
p && p <= &list->buf[list->bufsize-1];
p = strchr(++p, '\n') ){
++linecount;
}
list->aRes = (Resource *)calloc(linecount+1, sizeof(list->aRes[0]));
for( pb = list->buf, p = strchr(pb, '\n');
p && p <= &list->buf[list->bufsize-1];
pb = ++p, p = strchr(pb, '\n') ){
char *path = pb;
char *pe = p - 1;
/* strip leading and trailing whitespace */
while( path < p && isspace(*path) ) ++path;
while( pe > path && isspace(*pe) ){
*pe = '\0';
--pe;
}
/* strip outer quotes */
while( path < p && *path=='\"') ++path;
while( pe > path && *pe=='\"' ){
*pe = '\0';
--pe;
}
*p = '\0';
/* skip empty path */
if( *path ){
list->aRes[list->nRes].zName = path;
++(list->nRes);
}
}
return list->aRes;
}
void free_reslist(ResourceList *list){
if( list ){
if( list->buf ) free(list->buf);
if( list->aRes) free(list->aRes);
memset(list, 0, sizeof(*list));
}
}
/*
** Compare two Resource objects for sorting purposes. They sort
** in zName order so that Fossil can search for resources using
** a binary search.
*/
typedef int (*QsortCompareFunc)(const void *, const void*);
static int compareResource(const Resource *a, const Resource *b){
return strcmp(a->zName, b->zName);
}
int remove_duplicates(ResourceList *list){
char dupNameAsc[64] = "\255";
char dupNameDesc[64] = "";
Resource dupResAsc;
Resource dupResDesc;
Resource *pDupRes;
int dupcount = 0;
int i;
if( list->nRes==0 ){
return list->nRes;
}
/*
** scan for duplicates and assign their names to a string that would sort to
** the bottom, then re-sort and truncate the duplicates
*/
memset(dupNameAsc, dupNameAsc[0], sizeof(dupNameAsc)-2);
memset(dupNameDesc, dupNameDesc[0], sizeof(dupNameDesc)-2);
memset(&dupResAsc, 0, sizeof(dupResAsc));
dupResAsc.zName = dupNameAsc;
memset(&dupResDesc, 0, sizeof(dupResDesc));
dupResDesc.zName = dupNameDesc;
pDupRes = (compareResource(&dupResAsc, &dupResDesc) > 0
? &dupResAsc : &dupResDesc);
qsort(list->aRes, list->nRes, sizeof(list->aRes[0]),
(QsortCompareFunc)compareResource);
for( i=0; i<list->nRes-1 ; ++i){
Resource *res = &list->aRes[i];
while( i<list->nRes-1
&& compareResource(res, &list->aRes[i+1])==0 ){
fprintf(stderr, "Skipped a duplicate file [%s]\n", list->aRes[i+1].zName);
memcpy(&list->aRes[i+1], pDupRes, sizeof(list->aRes[0]));
++dupcount;
++i;
}
}
if( dupcount == 0){
return list->nRes;
}
qsort(list->aRes, list->nRes, sizeof(list->aRes[0]),
(QsortCompareFunc)compareResource);
list->nRes -= dupcount;
memset(&list->aRes[list->nRes], 0, sizeof(list->aRes[0]));
return list->nRes;
}
int main(int argc, char **argv){
int i, sz;
int j, n;
ResourceList resList;
Resource *aRes;
int nRes;
unsigned char *pData;
int nErr = 0;
int nSkip;
int nPrefix = 0;
int nName;
if( argc==1 ){
fprintf(stderr, "usage\t:%s "
"[--prefix path] [--reslist file] [resource-file1 ...]\n",
argv[0]
);
return 1;
}
if( argc>3 && strcmp(argv[1],"--prefix")==0 ){
nPrefix = (int)strlen(argv[2]);
argc -= 2;
argv += 2;
}
memset(&resList, 0, sizeof(resList));
if( argc>2 && strcmp(argv[1],"--reslist")==0 ){
if( read_reslist(argv[2], &resList)==0 ){
fprintf(stderr, "Failed to load resource list from [%s]", argv[2]);
free_reslist(&resList);
return 1;
}
argc -= 2;
argv += 2;
}
if( argc>1 ){
aRes = realloc(resList.aRes, (resList.nRes+argc-1)*sizeof(resList.aRes[0]));
if( aRes==0 || aRes==resList.aRes ){
fprintf(stderr, "realloc failed\n");
free_reslist(&resList);
return 1;
}
resList.aRes = aRes;
for(i=0; i<argc-1; i++){
resList.aRes[resList.nRes].zName = argv[i+1];
++resList.nRes;
}
}
if( resList.nRes==0 ){
fprintf(stderr,"No resource files to process\n");
free_reslist(&resList);
return 1;
}
remove_duplicates(&resList);
nRes = resList.nRes;
aRes = resList.aRes;
qsort(aRes, nRes, sizeof(aRes[0]), (QsortCompareFunc)compareResource);
printf("/* Automatically generated code: Do not edit.\n**\n"
"** Rerun the \"mkbuiltin.c\" program or rerun the Fossil\n"
"** makefile to update this source file.\n"
"*/\n");
for(i=0; i<nRes; i++){
pData = read_file(aRes[i].zName, &sz);
if( pData==0 ){
|
| ︙ | ︙ | |||
202 203 204 205 206 207 208 |
while( z[0]=='.' || z[0]=='/' || z[0]=='\\' ){ z++; }
aRes[i].zName = z;
while( z[0] ){
if( z[0]=='\\' ) z[0] = '/';
z++;
}
}
| | > | 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 |
while( z[0]=='.' || z[0]=='/' || z[0]=='\\' ){ z++; }
aRes[i].zName = z;
while( z[0] ){
if( z[0]=='\\' ) z[0] = '/';
z++;
}
}
qsort(aRes, nRes, sizeof(aRes[0]), (QsortCompareFunc)compareResource);
for(i=0; i<nRes; i++){
printf(" { \"%s\", bidata%d, %d },\n",
aRes[i].zName, aRes[i].idx, aRes[i].nByte);
}
printf("};\n");
free_reslist(&resList);
return nErr;
}
|
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
| ︙ | ︙ | |||
388 389 390 391 392 393 394 395 396 397 398 399 400 401 |
/*
** Build the binary search table.
*/
void build_table(void){
int i;
int nWeb = 0;
qsort(aEntry, nFixed, sizeof(aEntry[0]), e_compare);
printf(
"/* Automatically generated code\n"
"** DO NOT EDIT!\n"
"**\n"
| > > | 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 |
/*
** Build the binary search table.
*/
void build_table(void){
int i;
int nWeb = 0;
int mxLen = 0;
int len;
qsort(aEntry, nFixed, sizeof(aEntry[0]), e_compare);
printf(
"/* Automatically generated code\n"
"** DO NOT EDIT!\n"
"**\n"
|
| ︙ | ︙ | |||
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 |
}
/* Generate the aCommand[] table */
printf("static const CmdOrPage aCommand[] = {\n");
for(i=0; i<nFixed; i++){
const char *z = aEntry[i].zPath;
int n = strlen(z);
if( aEntry[i].zIf ){
printf("%s", aEntry[i].zIf);
}else if( (aEntry[i].eType & CMDFLAG_WEBPAGE)!=0 ){
nWeb++;
}
printf(" { \"%.*s\",%*s%s,%*szHelp%03d, 0x%03x },\n",
n, z,
25-n, "",
aEntry[i].zFunc,
(int)(29-strlen(aEntry[i].zFunc)), "",
aEntry[i].iHelp,
aEntry[i].eType
);
if( aEntry[i].zIf ) printf("#endif\n");
}
printf("};\n");
printf("#define FOSSIL_FIRST_CMD %d\n", nWeb);
/* Generate the aSetting[] table */
printf("const Setting aSetting[] = {\n");
for(i=0; i<nFixed; i++){
const char *z;
const char *zVar;
const char *zDef;
| > > > | 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 |
}
/* Generate the aCommand[] table */
printf("static const CmdOrPage aCommand[] = {\n");
for(i=0; i<nFixed; i++){
const char *z = aEntry[i].zPath;
int n = strlen(z);
if( n>mxLen ) mxLen = n;
if( aEntry[i].zIf ){
printf("%s", aEntry[i].zIf);
}else if( (aEntry[i].eType & CMDFLAG_WEBPAGE)!=0 ){
nWeb++;
}
printf(" { \"%.*s\",%*s%s,%*szHelp%03d, 0x%03x },\n",
n, z,
25-n, "",
aEntry[i].zFunc,
(int)(29-strlen(aEntry[i].zFunc)), "",
aEntry[i].iHelp,
aEntry[i].eType
);
if( aEntry[i].zIf ) printf("#endif\n");
}
printf("};\n");
printf("#define FOSSIL_FIRST_CMD %d\n", nWeb);
printf("#define FOSSIL_MX_CMDNAME %d /* max length of any command name */\n",
mxLen);
/* Generate the aSetting[] table */
printf("const Setting aSetting[] = {\n");
for(i=0; i<nFixed; i++){
const char *z;
const char *zVar;
const char *zDef;
|
| ︙ | ︙ | |||
511 512 513 514 515 516 517 518 519 520 521 522 523 524 |
}
fclose(in);
nUsed = nFixed;
}
int main(int argc, char **argv){
int i;
for(i=1; i<argc; i++){
zFile = argv[i];
process_file();
}
build_table();
return nErr;
}
| > | 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 |
}
fclose(in);
nUsed = nFixed;
}
int main(int argc, char **argv){
int i;
memset(aEntry, 0, sizeof(Entry) * N_ENTRY);
for(i=1; i<argc; i++){
zFile = argv[i];
process_file();
}
build_table();
return nErr;
}
|
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 |
/*
** This C program generates the "VERSION.h" header file from information
** extracted out of the "manifest", "manifest.uuid", and "VERSION" files.
** Call this program with three arguments:
**
** ./a.out manifest.uuid manifest VERSION
**
** Note that the manifest.uuid and manifest files are generated by Fossil.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static FILE *open_for_reading(const char *zFilename){
FILE *f = fopen(zFilename, "r");
if( f==0 ){
fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename);
exit(1);
}
return f;
}
int main(int argc, char *argv[]){
FILE *m,*u,*v;
char *z;
#if defined(__DMC__) /* e.g. 0x857 */
int i = 0;
#endif
int j = 0, x = 0, d = 0;
int vn[3];
char b[1000];
char vx[1000];
if( argc!=4 ){
fprintf(stderr, "Usage: %s manifest.uuid manifest VERSION\n", argv[0]);
exit(1);
}
memset(b,0,sizeof(b));
memset(vx,0,sizeof(vx));
u = open_for_reading(argv[1]);
if( fgets(b, sizeof(b)-1,u)==0 ){
fprintf(stderr, "malformed manifest.uuid file: %s\n", argv[1]);
exit(1);
}
fclose(u);
for(z=b; z[0] && z[0]!='\r' && z[0]!='\n'; z++){}
*z = 0;
printf("#define MANIFEST_UUID \"%s\"\n",b);
printf("#define MANIFEST_VERSION \"[%10.10s]\"\n",b);
m = open_for_reading(argv[2]);
while(b == fgets(b, sizeof(b)-1,m)){
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > | | > > > > > > > > > > > > > | 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 |
/*
** This C program generates the "VERSION.h" header file from information
** extracted out of the "manifest", "manifest.uuid", and "VERSION" files.
** Call this program with three arguments:
**
** ./a.out manifest.uuid manifest VERSION
**
** Note that the manifest.uuid and manifest files are generated by Fossil.
**
** The output becomes the "VERSION.h" file. The output is a C-language
** header that contains #defines for various properties of the build:
**
** MANIFEST_UUID These values are text strings that
** MANIFEST_VERSION identify the Fossil check-in to which
** the source tree belongs. They do not
** take into account any uncommitted edits.
**
** FOSSIL_BUILD_HASH A hexadecimal string that is a strong hash
** of the MANIFEST_UUID together with the
** current time of the build. We normally want
** this to be different on each build, as the
** value is used to expire ETag: fields in
** HTTP requests. But if you need to do
** repeatable byte-for-byte identical builds,
** add the -DFOSSIL_BUILD_EPOCH=n option.
**
** MANIFEST_DATE The date/time of the source-code check-in
** MANIFEST_YEAR in various formats.
** MANIFEST_NUMERIC_DATE
** MANIFEST_NUMERIC_TIME
**
** RELEASE_VERSION The version number (from the VERSION source
** RELEASE_VERSION_NUMBER file) in various format.
** RELEASE_RESOURCE_VERSION
**
** New #defines may be added in the future.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
static FILE *open_for_reading(const char *zFilename){
FILE *f = fopen(zFilename, "r");
if( f==0 ){
fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename);
exit(1);
}
return f;
}
/*
** Given an arbitrary-length input string key zIn, generate
** an N-byte hexadecimal hash of that string into zOut.
*/
static void hash(const char *zIn, int N, char *zOut){
unsigned char i, j, t;
int m, n;
unsigned char s[256];
for(m=0; m<256; m++){ s[m] = m; }
for(j=0, m=n=0; m<256; m++, n++){
j += s[m] + zIn[n];
if( zIn[n]==0 ){ n = -1; }
t = s[j];
s[j] = s[m];
s[m] = t;
}
i = j = 0;
for(n=0; n<N-2; n+=2){
i++;
t = s[i];
j += t;
s[i] = s[j];
s[j] = t;
t += s[i];
zOut[n] = "0123456789abcdef"[(t>>4)&0xf];
zOut[n+1] = "0123456789abcdef"[t&0xf];
}
zOut[n] = 0;
}
int main(int argc, char *argv[]){
FILE *m,*u,*v;
char *z;
#if defined(__DMC__) /* e.g. 0x857 */
int i = 0;
#endif
int j = 0, x = 0, d = 0;
size_t n;
int vn[3];
char b[1000];
char vx[1000];
if( argc!=4 ){
fprintf(stderr, "Usage: %s manifest.uuid manifest VERSION\n", argv[0]);
exit(1);
}
memset(b,0,sizeof(b));
memset(vx,0,sizeof(vx));
u = open_for_reading(argv[1]);
if( fgets(b, sizeof(b)-1,u)==0 ){
fprintf(stderr, "malformed manifest.uuid file: %s\n", argv[1]);
exit(1);
}
fclose(u);
for(z=b; z[0] && z[0]!='\r' && z[0]!='\n'; z++){}
*z = 0;
printf("#define MANIFEST_UUID \"%s\"\n",b);
printf("#define MANIFEST_VERSION \"[%10.10s]\"\n",b);
n = strlen(b);
if( n + 50 < sizeof(b) ){
#ifdef FOSSIL_BUILD_EPOCH
#define str(s) #s
sprintf(b+n, "%d", (int)strtoll(str(FOSSIL_BUILD_EPOCH), 0, 10));
#else
const char *zEpoch = getenv("SOURCE_DATE_EPOCH");
if( zEpoch && isdigit(zEpoch[0]) ){
sprintf(b+n, "%d", (int)strtoll(zEpoch, 0, 10));
}else{
sprintf(b+n, "%d", (int)time(0));
}
#endif
hash(b,33,vx);
printf("#define FOSSIL_BUILD_HASH \"%s\"\n", vx);
}
m = open_for_reading(argv[2]);
while(b == fgets(b, sizeof(b)-1,m)){
if(0 == strncmp("D ",b,2)){
int k, n;
char zDateNum[30];
printf("#define MANIFEST_DATE \"%.10s %.8s\"\n",b+2,b+13);
printf("#define MANIFEST_YEAR \"%.4s\"\n",b+2);
n = 0;
for(k=0; k<10; k++){
if( isdigit(b[k+2]) ) zDateNum[n++] = b[k+2];
}
zDateNum[n] = 0;
printf("#define MANIFEST_NUMERIC_DATE %s\n", zDateNum);
n = 0;
for(k=0; k<8; k++){
if( isdigit(b[k+13]) ) zDateNum[n++] = b[k+13];
}
zDateNum[n] = 0;
for(k=0; zDateNum[k]=='0'; k++){}
printf("#define MANIFEST_NUMERIC_TIME %s\n", zDateNum+k);
}
}
fclose(m);
v = open_for_reading(argv[3]);
if( fgets(b, sizeof(b)-1,v)==0 ){
fprintf(stderr, "malformed VERSION file: %s\n", argv[3]);
exit(1);
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
145 146 147 148 149 150 151 | } db_end_transaction(0); } /* ** Approve an object held for moderation. */ | | | > | 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 |
}
db_end_transaction(0);
}
/*
** Approve an object held for moderation.
*/
void moderation_approve(char class, int rid){
if( !moderation_pending(rid) ) return;
db_begin_transaction();
db_multi_exec(
"DELETE FROM private WHERE rid=%d;"
"INSERT OR IGNORE INTO unclustered VALUES(%d);"
"INSERT OR IGNORE INTO unsent VALUES(%d);",
rid, rid, rid
);
db_multi_exec("DELETE FROM modreq WHERE objid=%d", rid);
admin_log("Approved moderation of rid %c-%d.", class, rid);
if( class!='a' ) search_doc_touch(class, rid, 0);
db_end_transaction(0);
}
/*
** WEBPAGE: modreq
**
** Show all pending moderation request
|
| ︙ | ︙ | |||
182 183 184 185 186 187 188 |
if( moderation_table_exists() ){
blob_init(&sql, timeline_query_for_www(), -1);
blob_append_sql(&sql,
" AND event.objid IN (SELECT objid FROM modreq)"
" ORDER BY event.mtime DESC"
);
db_prepare(&q, "%s", blob_sql_text(&sql));
| | | 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
if( moderation_table_exists() ){
blob_init(&sql, timeline_query_for_www(), -1);
blob_append_sql(&sql,
" AND event.objid IN (SELECT objid FROM modreq)"
" ORDER BY event.mtime DESC"
);
db_prepare(&q, "%s", blob_sql_text(&sql));
www_print_timeline(&q, 0, 0, 0, 0, 0, 0, 0);
db_finalize(&q);
}
style_footer();
}
/*
** Disapproves any entries in the modreq table which belong to any
|
| ︙ | ︙ |
| ︙ | ︙ | |||
103 104 105 106 107 108 109 110 111 112 |
if( bVerifyNotAHash ){
if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'",zIn) ) return 0;
}
/* It looks like this may be a date. Return it with punctuation added. */
return zEDate;
}
/*
** Return the RID that is the "root" of the branch that contains
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > > > > > > > | > | < < < < | | | | > > > > > | | 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 |
if( bVerifyNotAHash ){
if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'",zIn) ) return 0;
}
/* It looks like this may be a date. Return it with punctuation added. */
return zEDate;
}
/*
** The data-time string in the argument is going to be used as an
** upper bound like this: mtime<=julianday(zDate,'localtime').
** But if the zDate parameter omits the fractional seconds or the
** seconds, or the time, that might mess up the == part of the
** comparison. So add in missing factional seconds or seconds or time.
**
** The returned string is held in a static buffer that is overwritten
** with each call, or else is just a copy of its input if there are
** no changes.
*/
const char *fossil_roundup_date(const char *zDate){
static char zUp[24];
int n = (int)strlen(zDate);
if( n==19 ){ /* YYYY-MM-DD HH:MM:SS */
memcpy(zUp, zDate, 19);
memcpy(zUp+19, ".999", 5);
return zUp;
}
if( n==16 ){ /* YYYY-MM-DD HH:MM */
memcpy(zUp, zDate, 16);
memcpy(zUp+16, ":59.999", 8);
return zUp;
}
if( n==10 ){ /* YYYY-MM-DD */
memcpy(zUp, zDate, 10);
memcpy(zUp+10, " 23:59:59.999", 14);
return zUp;
}
return zDate;
}
/*
** Return the RID that is the "root" of the branch that contains
** check-in "rid". Details depending on eType:
**
** eType==0 The check-in of the parent branch off of which
** the branch containing RID originally diverged.
**
** eType==1 The first check-in of the branch that contains RID.
**
** eType==2 The youngest ancestor of RID that is on the branch
** from which the branch containing RID diverged.
*/
int start_of_branch(int rid, int eType){
Stmt q;
int rc;
int ans = rid;
char *zBr = branch_of_rid(rid);
db_prepare(&q,
"SELECT pid, EXISTS(SELECT 1 FROM tagxref"
" WHERE tagid=%d AND tagtype>0"
" AND value=%Q AND rid=plink.pid)"
" FROM plink"
" WHERE cid=:cid AND isprim",
TAG_BRANCH, zBr
);
fossil_free(zBr);
do{
db_reset(&q);
db_bind_int(&q, ":cid", ans);
rc = db_step(&q);
if( rc!=SQLITE_ROW ) break;
if( eType==1 && db_column_int(&q,1)==0 ) break;
ans = db_column_int(&q, 0);
}while( db_column_int(&q, 1)==1 && ans>0 );
db_finalize(&q);
if( eType==2 && ans>0 ){
zBr = branch_of_rid(ans);
ans = compute_youngest_ancestor_in_branch(rid, zBr);
fossil_free(zBr);
}
return ans;
}
/*
** Convert a symbolic name into a RID. Acceptable forms:
**
** * artifact hash (optionally enclosed in [...])
** * 4-character or larger prefix of a artifact
|
| ︙ | ︙ | |||
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 |
** Return the RID of the matching artifact. Or return 0 if the name does not
** match any known object. Or return -1 if the name is ambiguous.
**
** The zType parameter specifies the type of artifact: ci, t, w, e, g, f.
** If zType is NULL or "" or "*" then any type of artifact will serve.
** If zType is "br" then find the first check-in of the named branch
** rather than the last.
** zType is "ci" in most use cases since we are usually searching for
** a check-in.
**
** Note that the input zTag for types "t" and "e" is the artifact hash of
** the ticket-change or technote-change artifact, not the randomly generated
** hexadecimal identifier assigned to tickets and events. Those identifiers
** live in a separate namespace.
*/
int symbolic_name_to_rid(const char *zTag, const char *zType){
int vid;
int rid = 0;
int nTag;
int i;
int startOfBranch = 0;
const char *zXTag; /* zTag with optional [...] removed */
int nXTag; /* Size of zXTag */
const char *zDate; /* Expanded date-time string */
if( zType==0 || zType[0]==0 ){
zType = "*";
}else if( zType[0]=='b' ){
zType = "ci";
startOfBranch = 1;
}
| > > | 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 |
** Return the RID of the matching artifact. Or return 0 if the name does not
** match any known object. Or return -1 if the name is ambiguous.
**
** The zType parameter specifies the type of artifact: ci, t, w, e, g, f.
** If zType is NULL or "" or "*" then any type of artifact will serve.
** If zType is "br" then find the first check-in of the named branch
** rather than the last.
**
** zType is "ci" in most use cases since we are usually searching for
** a check-in.
**
** Note that the input zTag for types "t" and "e" is the artifact hash of
** the ticket-change or technote-change artifact, not the randomly generated
** hexadecimal identifier assigned to tickets and events. Those identifiers
** live in a separate namespace.
*/
int symbolic_name_to_rid(const char *zTag, const char *zType){
int vid;
int rid = 0;
int nTag;
int i;
int startOfBranch = 0;
const char *zXTag; /* zTag with optional [...] removed */
int nXTag; /* Size of zXTag */
const char *zDate; /* Expanded date-time string */
const char *zTagPrefix = "sym";
if( zType==0 || zType[0]==0 ){
zType = "*";
}else if( zType[0]=='b' ){
zType = "ci";
startOfBranch = 1;
}
|
| ︙ | ︙ | |||
222 223 224 225 226 227 228 |
if( memcmp(zTag, "date:", 5)==0 ){
zDate = fossil_expand_datetime(&zTag[5],0);
if( zDate==0 ) zDate = &zTag[5];
rid = db_int(0,
"SELECT objid FROM event"
" WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'"
" ORDER BY mtime DESC LIMIT 1",
| | | | | | > > > > > | | > > > > > | | > > | 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 |
if( memcmp(zTag, "date:", 5)==0 ){
zDate = fossil_expand_datetime(&zTag[5],0);
if( zDate==0 ) zDate = &zTag[5];
rid = db_int(0,
"SELECT objid FROM event"
" WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'"
" ORDER BY mtime DESC LIMIT 1",
fossil_roundup_date(zDate), zType);
return rid;
}
if( fossil_isdate(zTag) ){
rid = db_int(0,
"SELECT objid FROM event"
" WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'"
" ORDER BY mtime DESC LIMIT 1",
fossil_roundup_date(zTag), zType);
if( rid) return rid;
}
/* Deprecated date & time formats: "local:" + date-time and
** "utc:" + date-time */
if( memcmp(zTag, "local:", 6)==0 ){
rid = db_int(0,
"SELECT objid FROM event"
" WHERE mtime<=julianday(%Q) AND type GLOB '%q'"
" ORDER BY mtime DESC LIMIT 1",
&zTag[6], zType);
return rid;
}
if( memcmp(zTag, "utc:", 4)==0 ){
rid = db_int(0,
"SELECT objid FROM event"
" WHERE mtime<=julianday('%qz') AND type GLOB '%q'"
" ORDER BY mtime DESC LIMIT 1",
fossil_roundup_date(&zTag[4]), zType);
return rid;
}
/* "tag:" + symbolic-name */
if( memcmp(zTag, "tag:", 4)==0 ){
rid = db_int(0,
"SELECT event.objid, max(event.mtime)"
" FROM tag, tagxref, event"
" WHERE tag.tagname='sym-%q' "
" AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
" AND event.objid=tagxref.rid "
" AND event.type GLOB '%q'",
&zTag[4], zType
);
if( startOfBranch ) rid = start_of_branch(rid,1);
return rid;
}
/* root:BR -> The origin of the branch named BR */
if( strncmp(zTag, "root:", 5)==0 ){
rid = symbolic_name_to_rid(zTag+5, zType);
return start_of_branch(rid, 0);
}
/* rootx:BR -> Most recent merge-in for the branch name BR */
if( strncmp(zTag, "merge-in:", 9)==0 ){
rid = symbolic_name_to_rid(zTag+9, zType);
return start_of_branch(rid, 2);
}
/* symbolic-name ":" date-time */
nTag = strlen(zTag);
for(i=0; i<nTag-8 && zTag[i]!=':'; i++){}
if( zTag[i]==':'
&& (fossil_isdate(&zTag[i+1]) || fossil_expand_datetime(&zTag[i+1],0)!=0)
){
char *zDate = mprintf("%s", &zTag[i+1]);
char *zTagBase = mprintf("%.*s", i, zTag);
char *zXDate;
int nDate = strlen(zDate);
if( sqlite3_strnicmp(&zDate[nDate-3],"utc",3)==0 ){
zDate[nDate-3] = 'z';
zDate[nDate-2] = 0;
}
zXDate = fossil_expand_datetime(zDate,0);
if( zXDate==0 ) zXDate = zDate;
rid = db_int(0,
"SELECT event.objid, max(event.mtime)"
" FROM tag, tagxref, event"
" WHERE tag.tagname='sym-%q' "
" AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
" AND event.objid=tagxref.rid "
" AND event.mtime<=julianday(%Q,fromLocal())"
" AND event.type GLOB '%q'",
zTagBase, fossil_roundup_date(zXDate), zType
);
fossil_free(zDate);
fossil_free(zTagBase);
return rid;
}
/* Remove optional [...] */
zXTag = zTag;
nXTag = nTag;
if( zXTag[0]=='[' ){
|
| ︙ | ︙ | |||
322 323 324 325 326 327 328 |
canonical16(zUuid, nXTag);
rid = 0;
if( zType[0]=='*' ){
db_prepare(&q, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUuid);
}else{
db_prepare(&q,
"SELECT blob.rid"
| | > > > | | > | | 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 |
canonical16(zUuid, nXTag);
rid = 0;
if( zType[0]=='*' ){
db_prepare(&q, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUuid);
}else{
db_prepare(&q,
"SELECT blob.rid"
" FROM blob CROSS JOIN event"
" WHERE blob.uuid GLOB '%q*'"
" AND event.objid=blob.rid"
" AND event.type GLOB '%q'",
zUuid, zType
);
}
if( db_step(&q)==SQLITE_ROW ){
rid = db_column_int(&q, 0);
if( db_step(&q)==SQLITE_ROW ) rid = -1;
}
db_finalize(&q);
if( rid ) return rid;
}
if( zType[0]=='w' ){
zTagPrefix = "wiki";
}
/* Symbolic name */
rid = db_int(0,
"SELECT event.objid, max(event.mtime)"
" FROM tag, tagxref, event"
" WHERE tag.tagname='%q-%q' "
" AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
" AND event.objid=tagxref.rid "
" AND event.type GLOB '%q'",
zTagPrefix, zTag, zType
);
if( rid>0 ){
if( startOfBranch ) rid = start_of_branch(rid,1);
return rid;
}
/* Pure numeric date/time */
zDate = fossil_expand_datetime(zTag, 0);
if( zDate ){
rid = db_int(0,
"SELECT objid FROM event"
" WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'"
" ORDER BY mtime DESC LIMIT 1",
fossil_roundup_date(zDate), zType);
if( rid) return rid;
}
/* Undocumented: numeric tags get translated directly into the RID */
if( memcmp(zTag, "rid:", 4)==0 ){
zTag += 4;
|
| ︙ | ︙ | |||
384 385 386 387 388 389 390 |
}
}
}
return rid;
}
/*
| | > > > | > | | < > > > | 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 |
}
}
}
return rid;
}
/*
** This routine takes a user-entered string and tries to convert it to
** an artifact hash.
**
** We first try to treat the string as an artifact hash, or at least a
** unique prefix of an artifact hash. The input may be in mixed case.
** If we are passed such a string, this routine has the effect of
** converting the hash [prefix] to canonical form.
**
** If the input is not a hash or a hash prefix, then try to resolve
** the name as a tag. If multiple tags match, pick the latest.
** A caller can force this routine to skip the hash case above by
** prefixing the string with "tag:", a useful property when the tag
** may be misinterpreted as a hex ASCII string. (e.g. "decade" or "facade")
**
** If the input is not a tag, then try to match it as an ISO-8601 date
** string YYYY-MM-DD HH:MM:SS and pick the nearest check-in to that date.
** If the input is of the form "date:*" then always resolve the name as
** a date. The forms "utc:*" and "local:" are deprecated.
**
** Return 0 on success. Return 1 if the name cannot be resolved.
|
| ︙ | ︙ | |||
419 420 421 422 423 424 425 |
return 0;
}
}
/*
** This routine is similar to name_to_uuid() except in the form it
** takes its parameters and returns its value, and in that it does not
| | | | | > | | 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 |
return 0;
}
}
/*
** This routine is similar to name_to_uuid() except in the form it
** takes its parameters and returns its value, and in that it does not
** treat errors as fatal. zName must be an artifact hash or prefix of
** a hash. zType is also as described for name_to_uuid(). If
** zName does not resolve, 0 is returned. If it is ambiguous, a
** negative value is returned. On success the rid is returned and
** pUuid (if it is not NULL) is set to a newly-allocated string,
** the full hash, which must eventually be free()d by the caller.
*/
int name_to_uuid2(const char *zName, const char *zType, char **pUuid){
int rid = symbolic_name_to_rid(zName, zType);
if((rid>0) && pUuid){
*pUuid = db_text(NULL, "SELECT uuid FROM blob WHERE rid=%d", rid);
}
return rid;
}
/*
** name_collisions searches through events, blobs, and tickets for
** collisions of a given hash based on its length, counting only
** hashes greater than or equal to 4 hex ASCII characters (16 bits)
** in length.
*/
int name_collisions(const char *zName){
int c = 0; /* count of collisions for zName */
int nLen; /* length of zName */
nLen = strlen(zName);
if( nLen>=4 && nLen<=HNAME_MAX && validate16(zName, nLen) ){
c = db_int(0,
|
| ︙ | ︙ | |||
463 464 465 466 467 468 469 | } return c; } /* ** COMMAND: test-name-to-id ** | > > | > > > > > > > > | | | | | | | | | > | 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 |
}
return c;
}
/*
** COMMAND: test-name-to-id
**
** Usage: %fossil test-name-to-id [--count N] NAME
**
** Convert a NAME to a full artifact ID. Repeat the conversion N
** times (for timing purposes) if the --count option is given.
*/
void test_name_to_id(void){
int i;
int n = 0;
Blob name;
db_must_be_within_tree();
for(i=2; i<g.argc; i++){
if( strcmp(g.argv[i],"--count")==0 && i+1<g.argc ){
i++;
n = atoi(g.argv[i]);
continue;
}
do{
blob_init(&name, g.argv[i], -1);
fossil_print("%s -> ", g.argv[i]);
if( name_to_uuid(&name, 1, "*") ){
fossil_print("ERROR: %s\n", g.zErrMsg);
fossil_error_reset();
}else{
fossil_print("%s\n", blob_buffer(&name));
}
blob_reset(&name);
}while( n-- > 0 );
}
}
/*
** Convert a name to a rid. If the name can be any of the various forms
** accepted:
**
|
| ︙ | ︙ | |||
519 520 521 522 523 524 525 526 527 528 529 |
/*
** WEBPAGE: ambiguous
** URL: /ambiguous?name=NAME&src=WEBPAGE
**
** The NAME given by the name parameter is ambiguous. Display a page
** that shows all possible choices and let the user select between them.
*/
void ambiguous_page(void){
Stmt q;
const char *zName = P("name");
| > > > | | | | | | | 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 |
/*
** WEBPAGE: ambiguous
** URL: /ambiguous?name=NAME&src=WEBPAGE
**
** The NAME given by the name parameter is ambiguous. Display a page
** that shows all possible choices and let the user select between them.
**
** The src= query parameter is optional. If omitted it defaults
** to "info".
*/
void ambiguous_page(void){
Stmt q;
const char *zName = P("name");
const char *zSrc = PD("src","info");
char *z;
if( zName==0 || zName[0]==0 || zSrc==0 || zSrc[0]==0 ){
fossil_redirect_home();
}
style_header("Ambiguous Artifact ID");
@ <p>The artifact hash prefix <b>%h(zName)</b> is ambiguous and might
@ mean any of the following:
@ <ol>
z = mprintf("%s", zName);
canonical16(z, strlen(z));
db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
while( db_step(&q)==SQLITE_ROW ){
const char *zUuid = db_column_text(&q, 0);
int rid = db_column_int(&q, 1);
@ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
@ %s(zUuid)</a> -
object_description(rid, 0, 0, 0);
@ </p></li>
}
db_finalize(&q);
db_prepare(&q,
" SELECT tkt_rid, tkt_uuid, title"
" FROM ticket, ticketchng"
" WHERE ticket.tkt_id = ticketchng.tkt_id"
" AND tkt_uuid GLOB '%q*'"
" GROUP BY tkt_uuid"
" ORDER BY tkt_ctime DESC", z);
while( db_step(&q)==SQLITE_ROW ){
int rid = db_column_int(&q, 0);
const char *zUuid = db_column_text(&q, 1);
const char *zTitle = db_column_text(&q, 2);
@ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
@ %s(zUuid)</a> -
@ <ul></ul>
@ Ticket
hyperlink_to_version(zUuid);
@ - %h(zTitle).
@ <ul><li>
object_description(rid, 0, 0, 0);
@ </li></ul>
@ </p></li>
}
db_finalize(&q);
db_prepare(&q,
"SELECT rid, uuid FROM"
" (SELECT tagxref.rid AS rid, substr(tagname, 7) AS uuid"
" FROM tagxref, tag WHERE tagxref.tagid = tag.tagid"
" AND tagname GLOB 'event-%q*') GROUP BY uuid", z);
while( db_step(&q)==SQLITE_ROW ){
int rid = db_column_int(&q, 0);
const char* zUuid = db_column_text(&q, 1);
@ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
@ %s(zUuid)</a> -
@ <ul><li>
object_description(rid, 0, 0, 0);
@ </li></ul>
@ </p></li>
}
@ </ol>
db_finalize(&q);
style_footer();
}
|
| ︙ | ︙ | |||
889 890 891 892 893 894 895 896 | static const char zDescTab[] = @ CREATE TEMP TABLE IF NOT EXISTS description( @ rid INTEGER PRIMARY KEY, -- RID of the object @ uuid TEXT, -- hash of the object @ ctime DATETIME, -- Time of creation @ isPrivate BOOLEAN DEFAULT 0, -- True for unpublished artifacts @ type TEXT, -- file, checkin, wiki, ticket, etc. @ summary TEXT, -- Summary comment for the object | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | > | | | | > | | | | | | | | > | | | | > | | | > | | > > > > > > | 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 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 |
static const char zDescTab[] =
@ CREATE TEMP TABLE IF NOT EXISTS description(
@ rid INTEGER PRIMARY KEY, -- RID of the object
@ uuid TEXT, -- hash of the object
@ ctime DATETIME, -- Time of creation
@ isPrivate BOOLEAN DEFAULT 0, -- True for unpublished artifacts
@ type TEXT, -- file, checkin, wiki, ticket, etc.
@ rcvid INT, -- When the artifact was received
@ summary TEXT, -- Summary comment for the object
@ ref TEXT -- hash of an object to link against
@ );
@ CREATE INDEX IF NOT EXISTS desctype
@ ON description(summary) WHERE summary='unknown';
;
/*
** Attempt to describe all phantom artifacts. The artifacts are
** already loaded into the description table and have summary='unknown'.
** This routine attempts to generate a better summary, and possibly
** fill in the ref field.
*/
static void describe_unknown_artifacts(){
/* Try to figure out the origin of unknown artifacts */
db_multi_exec(
"REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n"
" SELECT description.rid, description.uuid, isPrivate, type,\n"
" CASE WHEN plink.isprim THEN '' ELSE 'merge ' END ||\n"
" 'parent of check-in', blob.uuid\n"
" FROM description, plink, blob\n"
" WHERE description.summary='unknown'\n"
" AND plink.pid=description.rid\n"
" AND blob.rid=plink.cid;"
);
db_multi_exec(
"REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n"
" SELECT description.rid, description.uuid, isPrivate, type,\n"
" 'child of check-in', blob.uuid\n"
" FROM description, plink, blob\n"
" WHERE description.summary='unknown'\n"
" AND plink.cid=description.rid\n"
" AND blob.rid=plink.pid;"
);
db_multi_exec(
"REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n"
" SELECT description.rid, description.uuid, isPrivate, type,\n"
" 'check-in referenced by \"'||tag.tagname ||'\" tag',\n"
" blob.uuid\n"
" FROM description, tagxref, tag, blob\n"
" WHERE description.summary='unknown'\n"
" AND tagxref.origid=description.rid\n"
" AND tag.tagid=tagxref.tagid\n"
" AND blob.rid=tagxref.srcid;"
);
db_multi_exec(
"REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n"
" SELECT description.rid, description.uuid, isPrivate, type,\n"
" 'file \"'||filename.name||'\"',\n"
" blob.uuid\n"
" FROM description, mlink, filename, blob\n"
" WHERE description.summary='unknown'\n"
" AND mlink.fid=description.rid\n"
" AND blob.rid=mlink.mid\n"
" AND filename.fnid=mlink.fnid;"
);
if( !db_exists("SELECT 1 FROM description WHERE summary='unknown'") ){
return;
}
add_content_sql_commands(g.db);
db_multi_exec(
"REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n"
" SELECT description.rid, description.uuid, isPrivate, type,\n"
" 'referenced by cluster', blob.uuid\n"
" FROM description, tagxref, blob\n"
" WHERE description.summary='unknown'\n"
" AND tagxref.tagid=(SELECT tagid FROM tag WHERE tagname='cluster')\n"
" AND blob.rid=tagxref.rid\n"
" AND content(blob.uuid) GLOB ('*M '||blob.uuid||'*');"
);
}
/*
** Create the description table if it does not already exists.
** Populate fields of this table with descriptions for all artifacts
** whose RID matches the SQL expression in zWhere.
*/
void describe_artifacts(const char *zWhere){
db_multi_exec("%s", zDescTab/*safe-for-%s*/);
/* Describe check-ins */
db_multi_exec(
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
"SELECT blob.rid, blob.uuid, blob.rcvid, event.mtime, 'checkin',\n"
" 'check-in on ' || strftime('%%Y-%%m-%%d %%H:%%M',event.mtime)\n"
" FROM event, blob\n"
" WHERE (event.objid %s) AND event.type='ci'\n"
" AND event.objid=blob.rid;",
zWhere /*safe-for-%s*/
);
/* Describe files */
db_multi_exec(
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
"SELECT blob.rid, blob.uuid, blob.rcvid, event.mtime,"
" 'file', 'file '||filename.name\n"
" FROM mlink, blob, event, filename\n"
" WHERE (mlink.fid %s)\n"
" AND mlink.mid=event.objid\n"
" AND filename.fnid=mlink.fnid\n"
" AND mlink.fid=blob.rid;",
zWhere /*safe-for-%s*/
);
/* Describe tags */
db_multi_exec(
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
"SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'tag',\n"
" 'tag '||substr((SELECT uuid FROM blob WHERE rid=tagxref.rid),1,16)\n"
" FROM tagxref, blob\n"
" WHERE (tagxref.srcid %s) AND tagxref.srcid!=tagxref.rid\n"
" AND tagxref.srcid=blob.rid;",
zWhere /*safe-for-%s*/
);
/* Cluster artifacts */
db_multi_exec(
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
"SELECT blob.rid, blob.uuid, blob.rcvid, rcvfrom.mtime,"
" 'cluster', 'cluster'\n"
" FROM tagxref, blob, rcvfrom\n"
" WHERE (tagxref.rid %s)\n"
" AND tagxref.tagid=(SELECT tagid FROM tag WHERE tagname='cluster')\n"
" AND blob.rid=tagxref.rid"
" AND rcvfrom.rcvid=blob.rcvid;",
zWhere /*safe-for-%s*/
);
/* Ticket change artifacts */
db_multi_exec(
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
"SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'ticket',\n"
" 'ticket '||substr(tag.tagname,5,21)\n"
" FROM tagxref, tag, blob\n"
" WHERE (tagxref.rid %s)\n"
" AND tag.tagid=tagxref.tagid\n"
" AND tag.tagname GLOB 'tkt-*'"
" AND blob.rid=tagxref.rid;",
zWhere /*safe-for-%s*/
);
/* Wiki edit artifacts */
db_multi_exec(
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
"SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'wiki',\n"
" printf('wiki \"%%s\"',substr(tag.tagname,6))\n"
" FROM tagxref, tag, blob\n"
" WHERE (tagxref.rid %s)\n"
" AND tag.tagid=tagxref.tagid\n"
" AND tag.tagname GLOB 'wiki-*'"
" AND blob.rid=tagxref.rid;",
zWhere /*safe-for-%s*/
);
/* Event edit artifacts */
db_multi_exec(
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
"SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'event',\n"
" 'event '||substr(tag.tagname,7)\n"
" FROM tagxref, tag, blob\n"
" WHERE (tagxref.rid %s)\n"
" AND tag.tagid=tagxref.tagid\n"
" AND tag.tagname GLOB 'event-*'"
" AND blob.rid=tagxref.rid;",
zWhere /*safe-for-%s*/
);
/* Attachments */
db_multi_exec(
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
"SELECT blob.rid, blob.uuid, blob.rcvid, attachment.mtime,"
" 'attach-control',\n"
" 'attachment-control for '||attachment.filename\n"
" FROM attachment, blob\n"
" WHERE (attachment.attachid %s)\n"
" AND blob.rid=attachment.attachid",
zWhere /*safe-for-%s*/
);
db_multi_exec(
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
"SELECT blob.rid, blob.uuid, blob.rcvid, attachment.mtime, 'attachment',\n"
" 'attachment '||attachment.filename\n"
" FROM attachment, blob\n"
" WHERE (blob.rid %s)\n"
" AND blob.rid NOT IN (SELECT rid FROM description)\n"
" AND blob.uuid=attachment.src",
zWhere /*safe-for-%s*/
);
/* Forum posts */
if( db_table_exists("repository","forumpost") ){
db_multi_exec(
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
"SELECT postblob.rid, postblob.uuid, postblob.rcvid,"
" forumpost.fmtime, 'forumpost',\n"
" CASE WHEN fpid=froot THEN 'forum-post '\n"
" ELSE 'forum-reply-to ' END || substr(rootblob.uuid,1,14)\n"
" FROM forumpost, blob AS postblob, blob AS rootblob\n"
" WHERE (forumpost.fpid %s)\n"
" AND postblob.rid=forumpost.fpid"
" AND rootblob.rid=forumpost.froot",
zWhere /*safe-for-%s*/
);
}
/* Mark all other artifacts as "unknown" for now */
db_multi_exec(
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,type,summary)\n"
"SELECT blob.rid, blob.uuid,blob.rcvid,\n"
" CASE WHEN EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)\n"
" THEN 'phantom' ELSE '' END,\n"
" 'unknown'\n"
" FROM blob\n"
" WHERE (blob.rid %s)\n"
" AND (blob.rid NOT IN (SELECT rid FROM description));",
zWhere /*safe-for-%s*/
);
/* Mark private elements */
db_multi_exec(
"UPDATE description SET isPrivate=1 WHERE rid IN private"
);
if( db_exists("SELECT 1 FROM description WHERE summary='unknown'") ){
describe_unknown_artifacts();
}
}
/*
** Print the content of the description table on stdout.
**
** The description table is computed using the WHERE clause zWhere if
** the zWhere parameter is not NULL. If zWhere is NULL, then this
|
| ︙ | ︙ | |||
1062 1063 1064 1065 1066 1067 1068 |
);
while( db_step(&q)==SQLITE_ROW ){
if( zLabel ){
fossil_print("%s\n", zLabel);
zLabel = 0;
}
fossil_print(" %.16s %s", db_column_text(&q,0), db_column_text(&q,1));
| | | 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 |
);
while( db_step(&q)==SQLITE_ROW ){
if( zLabel ){
fossil_print("%s\n", zLabel);
zLabel = 0;
}
fossil_print(" %.16s %s", db_column_text(&q,0), db_column_text(&q,1));
if( db_column_int(&q,2) ) fossil_print(" (private)");
fossil_print("\n");
cnt++;
}
db_finalize(&q);
if( zWhere!=0 ) db_multi_exec("DELETE FROM description;");
return cnt;
}
|
| ︙ | ︙ | |||
1099 1100 1101 1102 1103 1104 1105 | /* ** WEBPAGE: bloblist ** ** Return a page showing all artifacts in the repository. Query parameters: ** ** n=N Show N artifacts ** s=S Start with artifact number S | | > | > > > > > > > > > > > | | | > > | > | > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 |
/*
** WEBPAGE: bloblist
**
** Return a page showing all artifacts in the repository. Query parameters:
**
** n=N Show N artifacts
** s=S Start with artifact number S
** priv Show only unpublished or private artifacts
** phan Show only phantom artifacts
** hclr Color code hash types (SHA1 vs SHA3)
*/
void bloblist_page(void){
Stmt q;
int s = atoi(PD("s","0"));
int n = atoi(PD("n","5000"));
int mx = db_int(0, "SELECT max(rid) FROM blob");
int privOnly = PB("priv");
int phantomOnly = PB("phan");
int hashClr = PB("hclr");
char *zRange;
char *zSha1Bg;
char *zSha3Bg;
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
style_header("List Of Artifacts");
style_submenu_element("250 Largest", "bigbloblist");
if( g.perm.Admin ){
style_submenu_element("Artifact Log", "rcvfromlist");
}
if( !phantomOnly ){
style_submenu_element("Phantoms", "bloblist?phan");
}
if( g.perm.Private || g.perm.Admin ){
if( !privOnly ){
style_submenu_element("Private", "bloblist?priv");
}
}else{
privOnly = 0;
}
if( g.perm.Write ){
style_submenu_element("Artifact Stats", "artifact_stats");
}
if( !privOnly && !phantomOnly && mx>n && P("s")==0 ){
int i;
@ <p>Select a range of artifacts to view:</p>
@ <ul>
for(i=1; i<=mx; i+=n){
@ <li> %z(href("%R/bloblist?s=%d&n=%d",i,n))
@ %d(i)..%d(i+n-1<mx?i+n-1:mx)</a>
}
@ </ul>
style_footer();
return;
}
if( phantomOnly || privOnly || mx>n ){
style_submenu_element("Index", "bloblist");
}
if( privOnly ){
zRange = mprintf("IN private");
}else if( phantomOnly ){
zRange = mprintf("IN phantom");
}else{
zRange = mprintf("BETWEEN %d AND %d", s, s+n-1);
}
describe_artifacts(zRange);
fossil_free(zRange);
db_prepare(&q,
"SELECT rid, uuid, summary, isPrivate, type='phantom', rcvid, ref"
" FROM description ORDER BY rid"
);
if( skin_detail_boolean("white-foreground") ){
zSha1Bg = "#714417";
zSha3Bg = "#177117";
}else{
zSha1Bg = "#ebffb0";
zSha3Bg = "#b0ffb0";
}
@ <table cellpadding="2" cellspacing="0" border="1">
if( g.perm.Admin ){
@ <tr><th>RID<th>Hash<th>Rcvid<th>Description<th>Ref<th>Remarks
}else{
@ <tr><th>RID<th>Hash<th>Description<th>Ref<th>Remarks
}
while( db_step(&q)==SQLITE_ROW ){
int rid = db_column_int(&q,0);
const char *zUuid = db_column_text(&q, 1);
const char *zDesc = db_column_text(&q, 2);
int isPriv = db_column_int(&q,3);
int isPhantom = db_column_int(&q,4);
const char *zRef = db_column_text(&q,6);
if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){
/* Don't show private artifacts to users without Private (x) permission */
continue;
}
if( hashClr ){
const char *zClr = db_column_bytes(&q,1)>40 ? zSha3Bg : zSha1Bg;
@ <tr style='background-color:%s(zClr);'><td align="right">%d(rid)</td>
}else{
@ <tr><td align="right">%d(rid)</td>
}
@ <td> %z(href("%R/info/%!S",zUuid))%S(zUuid)</a> </td>
if( g.perm.Admin ){
int rcvid = db_column_int(&q,5);
if( rcvid<=0 ){
@ <td>
}else{
@ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%d(rcvid)</a>
}
}
@ <td align="left">%h(zDesc)</td>
if( zRef && zRef[0] ){
@ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a>
}else{
@ <td>
}
if( isPriv || isPhantom ){
if( isPriv==0 ){
@ <td>phantom</td>
}else if( isPhantom==0 ){
@ <td>private</td>
}else{
@ <td>private,phantom</td>
}
}else{
@ <td>
}
@ </tr>
}
@ </table>
db_finalize(&q);
style_footer();
}
/*
** Output HTML that shows a table of all public phantoms.
*/
void table_of_public_phantoms(void){
Stmt q;
char *zRange;
zRange = mprintf("IN (SELECT rid FROM phantom EXCEPT"
" SELECT rid FROM private)");
describe_artifacts(zRange);
fossil_free(zRange);
db_prepare(&q,
"SELECT rid, uuid, summary, ref"
" FROM description ORDER BY rid"
);
@ <table cellpadding="2" cellspacing="0" border="1">
@ <tr><th>RID<th>Description<th>Source
while( db_step(&q)==SQLITE_ROW ){
int rid = db_column_int(&q,0);
const char *zUuid = db_column_text(&q, 1);
const char *zDesc = db_column_text(&q, 2);
const char *zRef = db_column_text(&q,3);
@ <tr><td valign="top">%d(rid)</td>
@ <td valign="top" align="left">%h(zUuid)<br>%h(zDesc)</td>
if( zRef && zRef[0] ){
@ <td valign="top">%z(href("%R/info/%!S",zRef))%!S(zRef)</a>
}else{
@ <td>
}
@ </tr>
}
@ </table>
db_finalize(&q);
}
/*
** WEBPAGE: phantoms
**
** Show a list of all "phantom" artifacts that are not marked as "private".
**
** A "phantom" artifact is an artifact whose hash named appears in some
** artifact but whose content is unknown. For example, if a manifest
** references a particular SHA3 hash of a file, but that SHA3 hash is
** not on the shunning list and is not in the database, then the file
** is a phantom. We know it exists, but we do not know its content.
**
** Whenever a sync occurs, both each party looks at its phantom list
** and for every phantom that is not also marked private, it asks the
** other party to send it the content. This mechanism helps keep all
** repositories synced up.
**
** This page is similar to the /bloblist page in that it lists artifacts.
** But this page is a special case in that it only shows phantoms that
** are not private. In other words, this page shows all phantoms that
** generate extra network traffic on every sync request.
*/
void phantom_list_page(void){
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
style_header("Public Phantom Artifacts");
if( g.perm.Admin ){
style_submenu_element("Artifact Log", "rcvfromlist");
style_submenu_element("Artifact List", "bloblist");
}
if( g.perm.Write ){
style_submenu_element("Artifact Stats", "artifact_stats");
}
table_of_public_phantoms();
style_footer();
}
/*
** WEBPAGE: bigbloblist
**
** Return a page showing the largest artifacts in the repository in order
|
| ︙ | ︙ | |||
1337 1338 1339 1340 1341 1342 1343 |
@ <p>Collisions of length %d(i):
}else{
@ <p>First 25 collisions of length %d(i):
}
for(j=0; j<aCollide[i].cnt && j<MAX_COLLIDE; j++){
char *zId = aCollide[i].azHit[j];
if( zId==0 ) continue;
| | | 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 |
@ <p>Collisions of length %d(i):
}else{
@ <p>First 25 collisions of length %d(i):
}
for(j=0; j<aCollide[i].cnt && j<MAX_COLLIDE; j++){
char *zId = aCollide[i].azHit[j];
if( zId==0 ) continue;
@ %z(href("%R/ambiguous/%s",zId))%h(zId)</a>
}
}
for(i=4; i<count(aCollide); i++){
for(j=0; j<aCollide[i].cnt && j<MAX_COLLIDE; j++){
fossil_free(aCollide[i].azHit[j]);
}
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
32 33 34 35 36 37 38 | ** %S Prefix of a length appropriate for human display ** ** The following macros help determine those lengths. FOSSIL_HASH_DIGITS ** is the default number of digits to display to humans. This value can ** be overridden using the hash-digits setting. FOSSIL_HASH_DIGITS_URL ** is the minimum number of digits to be used in URLs. The number used ** will always be at least 6 more than the number used for human output, | | | | | 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 |
** %S Prefix of a length appropriate for human display
**
** The following macros help determine those lengths. FOSSIL_HASH_DIGITS
** is the default number of digits to display to humans. This value can
** be overridden using the hash-digits setting. FOSSIL_HASH_DIGITS_URL
** is the minimum number of digits to be used in URLs. The number used
** will always be at least 6 more than the number used for human output,
** or HNAME_MAX, whichever is least.
*/
#ifndef FOSSIL_HASH_DIGITS
# define FOSSIL_HASH_DIGITS 10 /* For %S (human display) */
#endif
#ifndef FOSSIL_HASH_DIGITS_URL
# define FOSSIL_HASH_DIGITS_URL 16 /* For %!S (embedded in URLs) */
#endif
/*
** Return the number of artifact hash digits to display. The number is for
** human output if the bForUrl is false and is destined for a URL if
** bForUrl is false.
*/
int hash_digits(int bForUrl){
static int nDigitHuman = 0;
static int nDigitUrl = 0;
if( nDigitHuman==0 ){
nDigitHuman = db_get_int("hash-digits", FOSSIL_HASH_DIGITS);
if( nDigitHuman < 6 ) nDigitHuman = 6;
if( nDigitHuman > HNAME_MAX ) nDigitHuman = HNAME_MAX;
nDigitUrl = nDigitHuman + 6;
if( nDigitUrl < FOSSIL_HASH_DIGITS_URL ) nDigitUrl = FOSSIL_HASH_DIGITS_URL;
if( nDigitUrl > HNAME_MAX ) nDigitUrl = HNAME_MAX;
}
return bForUrl ? nDigitUrl : nDigitHuman;
}
/*
** Return the number of characters in a %S output.
*/
|
| ︙ | ︙ | |||
95 96 97 98 99 100 101 | #define etPOINTER 16 /* The %p conversion */ #define etHTMLIZE 17 /* Make text safe for HTML */ #define etHTTPIZE 18 /* Make text safe for HTTP. "/" encoded as %2f */ #define etURLIZE 19 /* Make text safe for HTTP. "/" not encoded */ #define etFOSSILIZE 20 /* The fossil header encoding format. */ #define etPATH 21 /* Path type */ #define etWIKISTR 22 /* Timeline comment text rendered from a char*: %W */ | | | > > > | 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
#define etPOINTER 16 /* The %p conversion */
#define etHTMLIZE 17 /* Make text safe for HTML */
#define etHTTPIZE 18 /* Make text safe for HTTP. "/" encoded as %2f */
#define etURLIZE 19 /* Make text safe for HTTP. "/" not encoded */
#define etFOSSILIZE 20 /* The fossil header encoding format. */
#define etPATH 21 /* Path type */
#define etWIKISTR 22 /* Timeline comment text rendered from a char*: %W */
#define etSTRINGID 23 /* String with length limit for a hash prefix: %S */
#define etROOT 24 /* String value of g.zTop: %R */
#define etJSONSTR 25 /* String encoded as a JSON string literal: %j
Use %!j to include double-quotes around it. */
#define etSHELLESC 26 /* Escape a filename for use in a shell command: %$
See blob_append_escaped_arg() for details */
/*
** An "etByte" is an 8-bit unsigned value.
*/
typedef unsigned char etByte;
|
| ︙ | ︙ | |||
164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
{ '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 count(fmtinfo)
/*
** "*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 |
{ '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 },
{ '$', 0, 0, etSHELLESC, 0, 0 },
};
#define etNINFO count(fmtinfo)
/*
** "*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.
|
| ︙ | ︙ | |||
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
/*
** Find the length of a string as long as that length does not
** exceed N bytes. If no zero terminator is seen in the first
** N bytes then return N. If N is negative, then this routine
** is an alias for strlen().
*/
static int StrNLen32(const char *z, int N){
int n = 0;
while( (N-- != 0) && *(z++)!=0 ){ n++; }
return n;
}
/*
** Return an appropriate set of flags for wiki_convert() for displaying
** comments on a timeline. These flag settings are determined by
** configuration parameters.
**
** The altForm2 argument is true for "%!W" (with the "!" alternate-form-2
| > > > > | 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
/*
** Find the length of a string as long as that length does not
** exceed N bytes. If no zero terminator is seen in the first
** N bytes then return N. If N is negative, then this routine
** is an alias for strlen().
*/
#if _XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L
# define StrNLen32(Z,N) (int)strnlen(Z,N)
#else
static int StrNLen32(const char *z, int N){
int n = 0;
while( (N-- != 0) && *(z++)!=0 ){ n++; }
return n;
}
#endif
/*
** Return an appropriate set of flags for wiki_convert() for displaying
** comments on a timeline. These flag settings are determined by
** configuration parameters.
**
** The altForm2 argument is true for "%!W" (with the "!" alternate-form-2
|
| ︙ | ︙ | |||
792 793 794 795 796 797 798 |
char *zMem = va_arg(ap,char*);
if( limit!=0 ){
/* Ignore the limit flag, if set, for JSON string
** output. This block exists to squelch the associated
** "unused variable" compiler warning. */
}
if( zMem==0 ) zMem = "";
| | | > > > > > > | 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 |
char *zMem = va_arg(ap,char*);
if( limit!=0 ){
/* Ignore the limit flag, if set, for JSON string
** output. This block exists to squelch the associated
** "unused variable" compiler warning. */
}
if( zMem==0 ) zMem = "";
zExtra = bufpt =
encode_json_string_literal(zMem, flag_altform2, &length);
if( precision>=0 && precision<length ) length = precision;
break;
}
case etWIKISTR: {
int limit = flag_alternateform ? va_arg(ap,int) : -1;
char *zWiki = va_arg(ap, char*);
Blob wiki;
blob_init(&wiki, zWiki, limit);
wiki_convert(&wiki, pBlob, wiki_convert_flags(flag_altform2));
blob_reset(&wiki);
length = width = 0;
break;
}
case etSHELLESC: {
char *zArg = va_arg(ap, char*);
blob_append_escaped_arg(pBlob, zArg);
length = width = 0;
break;
}
case etERROR:
buf[0] = '%';
buf[1] = c;
errorflag = 0;
idx = 1+(c!=0);
blob_append(pBlob,"%",idx);
|
| ︙ | ︙ | |||
1060 1061 1062 1063 1064 1065 1066 1067 |
/*
** Write error message output
*/
static int fossil_print_error(int rc, const char *z){
#ifdef FOSSIL_ENABLE_JSON
if( g.json.isJsonMode ){
json_err( 0, z, 1 );
| > > > > > > | > > > > > | 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 |
/*
** Write error message output
*/
static int fossil_print_error(int rc, const char *z){
#ifdef FOSSIL_ENABLE_JSON
if( g.json.isJsonMode ){
/*
** Avoid calling into the JSON support subsystem if it
** has not yet been initialized, e.g. early SQLite log
** messages, etc.
*/
if( !json_is_main_boostrapped() ) json_main_bootstrap();
json_err( 0, z, 1 );
if( g.isHTTP && !g.json.preserveRc ){
rc = 0 /* avoid HTTP 500 */;
}
if( g.cgiOutput==1 ){
g.cgiOutput = 2;
cgi_reply();
}
}
else
#endif
if( g.cgiOutput==1 && g.db ){
g.cgiOutput = 2;
cgi_reset_content();
cgi_set_content_type("text/html");
style_header("Bad Request");
etag_cancel();
@ <p class="generalError">%h(z)</p>
cgi_set_status(400, "Bad Request");
style_footer();
cgi_reply();
}else if( !g.fQuiet ){
fossil_force_newline();
fossil_trace("%s\n", z);
|
| ︙ | ︙ | |||
1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 |
va_list ap;
va_start(ap, zFormat);
z = vmprintf(zFormat, ap);
va_end(ap);
fossil_errorlog("warning: %s", z);
#ifdef FOSSIL_ENABLE_JSON
if(g.json.isJsonMode){
json_warn( FSL_JSON_W_UNKNOWN, "%s", z );
}else
#endif
{
if( g.cgiOutput==1 ){
cgi_printf("<p class=\"generalError\">\n%h\n</p>\n", z);
}else{
fossil_force_newline();
fossil_trace("%s\n", z);
}
}
free(z);
| > > > > > > > | 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 |
va_list ap;
va_start(ap, zFormat);
z = vmprintf(zFormat, ap);
va_end(ap);
fossil_errorlog("warning: %s", z);
#ifdef FOSSIL_ENABLE_JSON
if(g.json.isJsonMode){
/*
** Avoid calling into the JSON support subsystem if it
** has not yet been initialized, e.g. early SQLite log
** messages, etc.
*/
if( !json_is_main_boostrapped() ) json_main_bootstrap();
json_warn( FSL_JSON_W_UNKNOWN, "%s", z );
}else
#endif
{
if( g.cgiOutput==1 ){
etag_cancel();
cgi_printf("<p class=\"generalError\">\n%h\n</p>\n", z);
}else{
fossil_force_newline();
fossil_trace("%s\n", z);
}
}
free(z);
|
| ︙ | ︙ |
1 2 3 4 5 6 | /* ** Copyright (c) 2014 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /* ** Copyright (c) 2014 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** ** 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. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ |
| ︙ | ︙ | |||
447 448 449 450 451 452 453 | ** COMMAND: purge* ** ** The purge command removes content from a repository and stores that content ** in a "graveyard". The graveyard exists so that content can be recovered ** using the "fossil purge undo" command. The "fossil purge obliterate" ** command empties the graveyard, making the content unrecoverable. ** | | | | | | | | | | | | | | | | | | | | | | | | | | 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 |
** COMMAND: purge*
**
** The purge command removes content from a repository and stores that content
** in a "graveyard". The graveyard exists so that content can be recovered
** using the "fossil purge undo" command. The "fossil purge obliterate"
** command empties the graveyard, making the content unrecoverable.
**
** WARNING: This command can potentially destroy historical data and
** leave your repository in a goofy state. Know what you are doing!
** Make a backup of your repository before using this command!
**
** FURTHER WARNING: This command is a work-in-progress and may yet
** contain bugs.
**
** > fossil purge artifacts HASH... ?OPTIONS?
**
** Move arbitrary artifacts identified by the HASH list into the
** graveyard.
**
** > fossil purge cat HASH...
**
** Write the content of one or more artifacts in the graveyard onto
** standard output.
**
** > fossil purge checkins TAGS... ?OPTIONS?
**
** Move the check-ins or branches identified by TAGS and all of
** their descendants out of the repository and into the graveyard.
** If TAGS includes a branch name then it means all the check-ins
** on the most recent occurrence of that branch.
**
** > fossil purge files NAME ... ?OPTIONS?
**
** Move all instances of files called NAME into the graveyard.
** NAME should be the name of the file relative to the root of the
** repository. If NAME is a directory, then all files within that
** directory are moved.
**
** > fossil purge list|ls ?-l?
**
** Show the graveyard of prior purges. The -l option gives more
** detail in the output.
**
** > fossil purge obliterate ID... ?--force?
**
** Remove one or more purge events from the graveyard. Once a purge
** event is obliterated, it can no longer be undone. The --force
** option suppresses the confirmation prompt.
**
** > fossil purge tickets NAME ... ?OPTIONS?
**
** TBD...
**
** > fossil purge undo ID
**
** Restore the content previously removed by purge ID.
**
** > fossil purge wiki NAME ... ?OPTIONS?
**
** TBD...
**
** COMMON OPTIONS:
**
** --explain Make no changes, but show what would happen.
** --dry-run An alias for --explain
**
** SUMMARY:
** * fossil purge artifacts HASH.. [OPTIONS]
** * fossil purge cat HASH...
** * fossil purge checkins TAGS... [OPTIONS]
** * fossil purge files FILENAME... [OPTIONS]
** * fossil purge list
** * fossil purge obliterate ID...
** * fossil purge tickets NAME... [OPTIONS]
** * fossil purge undo ID
** * fossil purge wiki NAME... [OPTIONS]
*/
void purge_cmd(void){
int purgeFlags = PURGE_MOVETO_GRAVEYARD | PURGE_PRINT_SUMMARY;
const char *zSubcmd;
int n;
int i;
Stmt q;
|
| ︙ | ︙ | |||
545 546 547 548 549 550 551 |
}
describe_artifacts_to_stdout("IN ok", 0);
purge_artifact_list("ok", "", purgeFlags);
db_end_transaction(0);
}else if( strncmp(zSubcmd, "cat", n)==0 ){
int i, piid;
Blob content;
| | | 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 |
}
describe_artifacts_to_stdout("IN ok", 0);
purge_artifact_list("ok", "", purgeFlags);
db_end_transaction(0);
}else if( strncmp(zSubcmd, "cat", n)==0 ){
int i, piid;
Blob content;
if( g.argc<4 ) usage("cat HASH...");
for(i=3; i<g.argc; i++){
piid = db_int(0, "SELECT piid FROM purgeitem WHERE uuid LIKE '%q%%'",
g.argv[i]);
if( piid==0 ) fossil_fatal("no such item: %s", g.argv[3]);
purge_extract_item(piid, &content);
blob_write_to_file(&content, "-");
blob_reset(&content);
|
| ︙ | ︙ |
| ︙ | ︙ | |||
142 143 144 145 146 147 148 |
/*
** Update the repository schema for Fossil version 2.0. (2017-02-28)
** (1) Change the CHECK constraint on BLOB.UUID so that the length
** is greater than or equal to 40, not exactly equal to 40.
*/
void rebuild_schema_update_2_0(void){
| | | | | | | | > > > > > > > | 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 |
/*
** Update the repository schema for Fossil version 2.0. (2017-02-28)
** (1) Change the CHECK constraint on BLOB.UUID so that the length
** is greater than or equal to 40, not exactly equal to 40.
*/
void rebuild_schema_update_2_0(void){
char *z = db_text(0, "SELECT sql FROM repository.sqlite_schema"
" WHERE name='blob'");
if( z ){
/* Search for: length(uuid)==40
** 0123456789 12345 */
int i;
for(i=10; z[i]; i++){
if( z[i]=='=' && strncmp(&z[i-6],"(uuid)==40",10)==0 ){
z[i] = '>';
db_multi_exec(
"PRAGMA writable_schema=ON;"
"UPDATE repository.sqlite_schema SET sql=%Q WHERE name LIKE 'blob';"
"PRAGMA writable_schema=OFF;",
z
);
break;
}
}
fossil_free(z);
}
db_multi_exec(
"CREATE VIEW IF NOT EXISTS "
" repository.artifact(rid,rcvid,size,atype,srcid,hash,content) AS "
" SELECT blob.rid,rcvid,size,1,srcid,uuid,content"
" FROM blob LEFT JOIN delta ON (blob.rid=delta.rid);"
);
}
/*
** Variables used to store state information about an on-going "rebuild"
** or "deconstruct".
*/
static int totalSize; /* Total number of artifacts to process */
static int processCnt; /* Number processed so far */
static int ttyOutput; /* Do progress output */
static Bag bagDone = Bag_INIT; /* Bag of records rebuilt */
static char *zFNameFormat; /* Format string for filenames on deconstruct */
static int cchFNamePrefix; /* Length of directory prefix in zFNameFormat */
static const char *zDestDir;/* Destination directory on deconstruct */
static int prefixLength; /* Length of directory prefix for deconstruct */
static int fKeepRid1; /* Flag to preserve RID=1 on de- and reconstruct */
/*
** Draw the percent-complete message.
** The input is actually the permill complete.
*/
static void percent_complete(int permill){
static int lastOutput = -1;
if( permill>lastOutput ){
fossil_print(" %d.%d%% complete...\r", permill/10, permill%10);
fflush(stdout);
lastOutput = permill;
}
}
/*
** Frees rebuild-level cached state. Intended only to be called by the
** app-level atexit() handler.
*/
void rebuild_clear_cache(){
bag_clear(&bagDone);
}
/*
** Called after each artifact is processed
*/
static void rebuild_step_done(int rid){
/* assert( bag_find(&bagDone, rid)==0 ); */
bag_insert(&bagDone, rid);
|
| ︙ | ︙ | |||
364 365 366 367 368 369 370 |
*/
int rebuild_db(int randomize, int doOut, int doClustering){
Stmt s, q;
int errCnt = 0;
int incrSize;
Blob sql;
| | | | 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 |
*/
int rebuild_db(int randomize, int doOut, int doClustering){
Stmt s, q;
int errCnt = 0;
int incrSize;
Blob sql;
bag_clear(&bagDone);
ttyOutput = doOut;
processCnt = 0;
if (ttyOutput && !g.fQuiet) {
percent_complete(0);
}
alert_triggers_disable();
rebuild_update_schema();
blob_init(&sql, 0, 0);
db_prepare(&q,
"SELECT name FROM sqlite_schema /*scan*/"
" WHERE type='table'"
" AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
"'config','shun','private','reportfmt',"
"'concealed','accesslog','modreq',"
"'purgeevent','purgeitem','unversioned',"
"'subscriber','pending_alert','alert_bounce')"
" AND name NOT GLOB 'sqlite_*'"
|
| ︙ | ︙ | |||
1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 |
zFile,zHashPolicy);
free(zFile);
free(zFNameFormat);
zFNameFormat = 0;
cchFNamePrefix = 0;
}
#endif
/*
** COMMAND: reconstruct*
**
** Usage: %fossil reconstruct ?OPTIONS? FILENAME DIRECTORY
**
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | < | | | > > > > | 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 |
zFile,zHashPolicy);
free(zFile);
free(zFNameFormat);
zFNameFormat = 0;
cchFNamePrefix = 0;
}
#endif
/*
** Helper functions used by the `deconstruct' and `reconstruct' commands to
** save and restore the contents of the PRIVATE table.
*/
void private_export(char *zFileName)
{
Stmt q;
Blob fctx = empty_blob;
blob_append(&fctx, "# The hashes of private artifacts\n", -1);
db_prepare(&q,
"SELECT uuid FROM blob WHERE rid IN ( SELECT rid FROM private );");
while( db_step(&q)==SQLITE_ROW ){
const char *zUuid = db_column_text(&q, 0);
blob_append(&fctx, zUuid, -1);
blob_append(&fctx, "\n", -1);
}
db_finalize(&q);
blob_write_to_file(&fctx, zFileName);
blob_reset(&fctx);
}
void private_import(char *zFileName)
{
Blob fctx;
if( blob_read_from_file(&fctx, zFileName, ExtFILE)!=-1 ){
Blob line, value;
while( blob_line(&fctx, &line)>0 ){
char *zUuid;
int nUuid;
if( blob_token(&line, &value)==0 ) continue; /* Empty line */
if( blob_buffer(&value)[0]=='#' ) continue; /* Comment */
blob_trim(&value);
zUuid = blob_buffer(&value);
nUuid = blob_size(&value);
zUuid[nUuid] = 0;
if( hname_validate(zUuid, nUuid)!=HNAME_ERROR ){
canonical16(zUuid, nUuid);
db_multi_exec(
"INSERT OR IGNORE INTO private"
" SELECT rid FROM blob WHERE uuid = %Q;",
zUuid);
}
}
blob_reset(&fctx);
}
}
/*
** COMMAND: reconstruct*
**
** Usage: %fossil reconstruct ?OPTIONS? FILENAME DIRECTORY
**
** This command studies the artifacts (files) in DIRECTORY and reconstructs the
** Fossil record from them. It places the new Fossil repository in FILENAME.
** Subdirectories are read, files with leading '.' in the filename are ignored.
**
** Options:
** -K|--keep-rid1 Read the filename of the artifact with RID=1 from the
** file .rid in DIRECTORY.
** -P|--keep-private Mark the artifacts listed in the file .private in
** DIRECTORY as private in the new Fossil repository.
**
** See also: deconstruct, rebuild
*/
void reconstruct_cmd(void) {
char *zPassword;
int fKeepPrivate;
fKeepRid1 = find_option("keep-rid1","K",0)!=0;
fKeepPrivate = find_option("keep-private","P",0)!=0;
if( g.argc!=4 ){
usage("FILENAME DIRECTORY");
}
if( file_isdir(g.argv[3], ExtFILE)!=1 ){
fossil_print("\"%s\" is not a directory\n\n", g.argv[3]);
usage("FILENAME DIRECTORY");
}
|
| ︙ | ︙ | |||
1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 |
db_initial_setup(0, 0, 0);
fossil_print("Reading files from directory \"%s\"...\n", g.argv[3]);
recon_read_dir(g.argv[3]);
fossil_print("\nBuilding the Fossil repository...\n");
rebuild_db(0, 1, 1);
reconstruct_private_table();
/* Skip the verify_before_commit() step on a reconstruct. Most artifacts
** will have been changed and verification therefore takes a really, really
** long time.
*/
verify_cancel();
db_end_transaction(0);
fossil_print("project-id: %s\n", db_get("project-code", 0));
fossil_print("server-id: %s\n", db_get("server-code", 0));
zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
fossil_print("admin-user: %s (initial password is \"%s\")\n", g.zLogin, zPassword);
}
/*
** COMMAND: deconstruct*
**
** Usage %fossil deconstruct ?OPTIONS? DESTINATION
**
| > > > > > > > > < | | | | | | > > > | > > > | 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 |
db_initial_setup(0, 0, 0);
fossil_print("Reading files from directory \"%s\"...\n", g.argv[3]);
recon_read_dir(g.argv[3]);
fossil_print("\nBuilding the Fossil repository...\n");
rebuild_db(0, 1, 1);
/* Backwards compatibility: Mark check-ins with "+private" tags as private. */
reconstruct_private_table();
/* Newer method: Import the list of private artifacts to the PRIVATE table. */
if( fKeepPrivate ){
char *zFnDotPrivate = mprintf("%s/.private", g.argv[3]);
private_import(zFnDotPrivate);
free(zFnDotPrivate);
}
/* Skip the verify_before_commit() step on a reconstruct. Most artifacts
** will have been changed and verification therefore takes a really, really
** long time.
*/
verify_cancel();
db_end_transaction(0);
fossil_print("project-id: %s\n", db_get("project-code", 0));
fossil_print("server-id: %s\n", db_get("server-code", 0));
zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
fossil_print("admin-user: %s (initial password is \"%s\")\n", g.zLogin, zPassword);
}
/*
** COMMAND: deconstruct*
**
** Usage %fossil deconstruct ?OPTIONS? DESTINATION
**
** This command exports all artifacts of a given repository and writes all
** artifacts to the file system. The DESTINATION directory will be populated
** with subdirectories AA and files AA/BBBBBBBBB.., where AABBBBBBBBB.. is the
** 40+ character artifact ID, AA the first 2 characters.
** If -L|--prefixlength is given, the length (default 2) of the directory prefix
** can be set to 0,1,..,9 characters.
**
** Options:
** -R|--repository REPOSITORY Deconstruct given REPOSITORY.
** -K|--keep-rid1 Save the filename of the artifact with RID=1 to
** the file .rid1 in the DESTINATION directory.
** -L|--prefixlength N Set the length of the names of the DESTINATION
** subdirectories to N.
** --private Include private artifacts.
** -P|--keep-private Save the list of private artifacts to the file
** .private in the DESTINATION directory (implies
** the --private option).
**
** See also: reconstruct, rebuild
*/
void deconstruct_cmd(void){
const char *zPrefixOpt;
Stmt s;
int privateFlag;
int fKeepPrivate;
fKeepRid1 = find_option("keep-rid1","K",0)!=0;
/* get and check prefix length argument and build format string */
zPrefixOpt=find_option("prefixlength","L",1);
if( !zPrefixOpt ){
prefixLength = 2;
}else{
if( zPrefixOpt[0]>='0' && zPrefixOpt[0]<='9' && !zPrefixOpt[1] ){
prefixLength = (int)(*zPrefixOpt-'0');
}else{
fossil_fatal("N(%s) is not a valid prefix length!",zPrefixOpt);
}
}
/* open repository and open query for all artifacts */
db_find_and_open_repository(OPEN_ANY_SCHEMA, 0);
privateFlag = find_option("private",0,0)!=0;
fKeepPrivate = find_option("keep-private","P",0)!=0;
if( fKeepPrivate ) privateFlag = 1;
verify_all_options();
/* check number of arguments */
if( g.argc!=3 ){
usage ("?OPTIONS? DESTINATION");
}
/* get and check argument destination directory */
zDestDir = g.argv[g.argc-1];
|
| ︙ | ︙ | |||
1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 |
Blob content;
content_get(rid, &content);
rebuild_step(rid, size, &content);
}
}
}
db_finalize(&s);
if(!g.fQuiet && ttyOutput ){
fossil_print("\n");
}
/* free filename format string */
free(zFNameFormat);
zFNameFormat = 0;
}
| > > > > > > > > | 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 |
Blob content;
content_get(rid, &content);
rebuild_step(rid, size, &content);
}
}
}
db_finalize(&s);
/* Export the list of private artifacts. */
if( fKeepPrivate ){
char *zFnDotPrivate = mprintf("%s/.private", zDestDir);
private_export(zFnDotPrivate);
free(zFnDotPrivate);
}
if(!g.fQuiet && ttyOutput ){
fossil_print("\n");
}
/* free filename format string */
free(zFNameFormat);
zFNameFormat = 0;
}
|
| ︙ | ︙ | |||
105 106 107 108 109 110 111 |
if( (c&0xe0)==0xc0 && p->i<p->mx && (p->z[p->i]&0xc0)==0x80 ){
c = (c&0x1f)<<6 | (p->z[p->i++]&0x3f);
if( c<0x80 ) c = 0xfffd;
}else if( (c&0xf0)==0xe0 && p->i+1<p->mx && (p->z[p->i]&0xc0)==0x80
&& (p->z[p->i+1]&0xc0)==0x80 ){
c = (c&0x0f)<<12 | ((p->z[p->i]&0x3f)<<6) | (p->z[p->i+1]&0x3f);
p->i += 2;
| | | 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
if( (c&0xe0)==0xc0 && p->i<p->mx && (p->z[p->i]&0xc0)==0x80 ){
c = (c&0x1f)<<6 | (p->z[p->i++]&0x3f);
if( c<0x80 ) c = 0xfffd;
}else if( (c&0xf0)==0xe0 && p->i+1<p->mx && (p->z[p->i]&0xc0)==0x80
&& (p->z[p->i+1]&0xc0)==0x80 ){
c = (c&0x0f)<<12 | ((p->z[p->i]&0x3f)<<6) | (p->z[p->i+1]&0x3f);
p->i += 2;
if( c<=0x7ff || (c>=0xd800 && c<=0xdfff) ) c = 0xfffd;
}else if( (c&0xf8)==0xf0 && p->i+3<p->mx && (p->z[p->i]&0xc0)==0x80
&& (p->z[p->i+1]&0xc0)==0x80 && (p->z[p->i+2]&0xc0)==0x80 ){
c = (c&0x07)<<18 | ((p->z[p->i]&0x3f)<<12) | ((p->z[p->i+1]&0x3f)<<6)
| (p->z[p->i+2]&0x3f);
p->i += 3;
if( c<=0xffff || c>0x10ffff ) c = 0xfffd;
}else{
|
| ︙ | ︙ | |||
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 |
}
}
/*
** Flags for grep_buffer()
*/
#define GREP_EXISTS 0x001 /* If any match, print only the name and stop */
/*
** Run a "grep" over a text file
*/
static int grep_buffer(
ReCompiled *pRe,
const char *zName,
const char *z,
u32 flags
){
int i, j, n, ln, cnt;
for(i=j=ln=cnt=0; z[i]; i=j+1){
for(j=i; z[j] && z[j]!='\n'; j++){}
n = j - i;
ln++;
if( re_match(pRe, (const unsigned char*)(z+i), j-i) ){
cnt++;
if( flags & GREP_EXISTS ){
| > | > > | > > > | 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 |
}
}
/*
** Flags for grep_buffer()
*/
#define GREP_EXISTS 0x001 /* If any match, print only the name and stop */
#define GREP_QUIET 0x002 /* Return code only */
/*
** Run a "grep" over a text file
*/
static int grep_buffer(
ReCompiled *pRe,
const char *zName,
const char *z,
u32 flags
){
int i, j, n, ln, cnt;
for(i=j=ln=cnt=0; z[i]; i=j+1){
for(j=i; z[j] && z[j]!='\n'; j++){}
n = j - i;
ln++;
if( re_match(pRe, (const unsigned char*)(z+i), j-i) ){
cnt++;
if( flags & GREP_EXISTS ){
if( (flags & GREP_QUIET)==0 && zName ) fossil_print("%s\n", zName);
break;
}
if( (flags & GREP_QUIET)==0 ){
if( cnt==1 && zName ){
fossil_print("== %s\n", zName);
}
fossil_print("%d:%.*s\n", ln, n, z+i);
}
}
}
return cnt;
}
/*
** COMMAND: test-grep
|
| ︙ | ︙ | |||
785 786 787 788 789 790 791 | } re_free(pRe); } /* ** COMMAND: grep ** | | | > > > > > | > > | | > > > > > | > > > > > > > > > | > > > > > > > > > > > | > > > > | | | | < | > > > > > > > > > > > > | | > > > | > | | | > | | | < | | > > > > > > > > > | > | > > > > > > > > > > > > > > | > > > > > > | 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 |
}
re_free(pRe);
}
/*
** COMMAND: grep
**
** Usage: %fossil grep [OPTIONS] PATTERN FILENAME ...
**
** Attempt to match the given POSIX extended regular expression PATTERN
** historic versions of FILENAME. The search begins with the most recent
** version of the file and moves backwards in time. Multiple FILENAMEs can
** be specified, in which case all named files are searched in reverse
** chronological order.
**
** For details of the supported regular expression dialect, see
** https://fossil-scm.org/fossil/doc/trunk/www/grep.md
**
** Options:
**
** -c|--count Suppress normal output; instead print a count
** of the number of matching files
** -i|--ignore-case Ignore case
** -l|--files-with-matches List only hash for each match
** --once Stop searching after the first match
** -s|--no-messages Suppress error messages about nonexistant
** or unreadable files
** -v|--invert-match Invert the sense of matching. Show only
** files that have no matches. Implies -l
** --verbose Show each file as it is analyzed
*/
void re_grep_cmd(void){
u32 flags = 0;
int bVerbose = 0;
ReCompiled *pRe;
const char *zErr;
int ignoreCase = 0;
Blob fullName;
int ii;
int nMatch = 0;
int bNoMsg;
int cntFlag;
int bOnce;
int bInvert;
int nSearch = 0;
Stmt q;
if( find_option("ignore-case","i",0)!=0 ) ignoreCase = 1;
if( find_option("files-with-matches","l",0)!=0 ) flags |= GREP_EXISTS;
if( find_option("verbose",0,0)!=0 ) bVerbose = 1;
if( find_option("quiet","q",0) ) flags |= GREP_QUIET|GREP_EXISTS;
bNoMsg = find_option("no-messages","s",0)!=0;
bOnce = find_option("once",0,0)!=0;
bInvert = find_option("invert-match","v",0)!=0;
if( bInvert ){
flags |= GREP_QUIET|GREP_EXISTS;
}
cntFlag = find_option("count","c",0)!=0;
if( cntFlag ){
flags |= GREP_QUIET|GREP_EXISTS;
}
db_find_and_open_repository(0, 0);
verify_all_options();
if( g.argc<4 ){
usage("REGEXP FILENAME ...");
}
zErr = re_compile(&pRe, g.argv[2], ignoreCase);
if( zErr ) fossil_fatal("%s", zErr);
add_content_sql_commands(g.db);
db_multi_exec("CREATE TEMP TABLE arglist(iname,fname,fnid);");
for(ii=3; ii<g.argc; ii++){
const char *zTarget = g.argv[ii];
if( file_tree_name(zTarget, &fullName, 0, 1) ){
int fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q",
blob_str(&fullName));
if( !fnid ){
if( bNoMsg ) continue;
if( file_size(zTarget, ExtFILE)<0 ){
fossil_fatal("no such file: %s", zTarget);
}
fossil_fatal("not a managed file: %s", zTarget);
}else{
db_multi_exec(
"INSERT INTO arglist(iname,fname,fnid) VALUES(%Q,%Q,%d)",
zTarget, blob_str(&fullName), fnid);
}
}
blob_reset(&fullName);
}
db_prepare(&q,
" SELECT"
" A.uuid," /* file hash */
" A.rid," /* file rid */
" B.uuid," /* check-in hash */
" datetime(min(event.mtime))," /* check-in time */
" arglist.iname" /* file name */
" FROM arglist, mlink, blob A, blob B, event"
" WHERE mlink.mid=event.objid"
" AND mlink.fid=A.rid"
" AND mlink.mid=B.rid"
" AND mlink.fnid=arglist.fnid"
" GROUP BY A.uuid"
" ORDER BY min(event.mtime) DESC;"
);
while( db_step(&q)==SQLITE_ROW ){
const char *zFileHash = db_column_text(&q,0);
int rid = db_column_int(&q,1);
const char *zCkinHash = db_column_text(&q,2);
const char *zDate = db_column_text(&q,3);
const char *zFN = db_column_text(&q,4);
char *zLabel;
Blob cx;
content_get(rid, &cx);
zLabel = mprintf("%.16s %s %S checkin %S", zDate, zFN,zFileHash,zCkinHash);
if( bVerbose ) fossil_print("Scanning: %s\n", zLabel);
nSearch++;
nMatch += grep_buffer(pRe, zLabel, blob_str(&cx), flags);
blob_reset(&cx);
if( bInvert && cntFlag==0 ){
if( nMatch==0 ){
fossil_print("== %s\n", zLabel);
if( bOnce ) nMatch = 1;
}else{
nMatch = 0;
}
}
fossil_free(zLabel);
if( nMatch ){
if( (flags & GREP_QUIET)!=0 ) break;
if( bOnce ) break;
}
}
db_finalize(&q);
re_free(pRe);
if( cntFlag ){
if( bInvert ){
fossil_print("%d\n", nSearch-nMatch);
}else{
fossil_print("%d\n", nMatch);
}
}
}
|
| ︙ | ︙ | |||
94 95 96 97 98 99 100 | ** processing is disallowed for chroot jails because g.zRepositoryName ** is always "/" inside a chroot jail and so it cannot be used as a flag ** to signal the special processing in that case. The special case ** processing is intended for the "fossil all ui" command which never ** runs in a chroot jail anyhow. ** ** Or, if no repositories can be located beneath g.zRepositoryName, | | | 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
** processing is disallowed for chroot jails because g.zRepositoryName
** is always "/" inside a chroot jail and so it cannot be used as a flag
** to signal the special processing in that case. The special case
** processing is intended for the "fossil all ui" command which never
** runs in a chroot jail anyhow.
**
** Or, if no repositories can be located beneath g.zRepositoryName,
** close g.db and return 0.
*/
int repo_list_page(void){
Blob base; /* document root for all repositories */
int n = 0; /* Number of repositories found */
int allRepo; /* True if running "fossil ui all".
** False if a directory scan of base for repos */
Blob html; /* Html for the body of the repository list */
|
| ︙ | ︙ | |||
138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
vfile_scan(&base, blob_size(&base), 0, 0, 0, ExtFILE);
db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'");
allRepo = 0;
}
n = db_int(0, "SELECT count(*) FROM sfile");
if( n==0 ){
sqlite3_close(g.db);
return 0;
}else{
Stmt q;
double rNow;
blob_append_sql(&html,
"<table border='0' class='sortable' data-init-sort='1'"
" data-column-types='txtxk'><thead>\n"
| > > > | 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
vfile_scan(&base, blob_size(&base), 0, 0, 0, ExtFILE);
db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'");
allRepo = 0;
}
n = db_int(0, "SELECT count(*) FROM sfile");
if( n==0 ){
sqlite3_close(g.db);
g.db = 0;
g.repositoryOpen = 0;
g.localOpen = 0;
return 0;
}else{
Stmt q;
double rNow;
blob_append_sql(&html,
"<table border='0' class='sortable' data-init-sort='1'"
" data-column-types='txtxk'><thead>\n"
|
| ︙ | ︙ |
| ︙ | ︙ | |||
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
"filename",
"mlink",
"plink",
"event",
"tag",
"tagxref",
"unversioned",
};
int i;
if( fossil_strncmp(zArg1, "fx_", 3)==0 ){
break;
}
for(i=0; i<count(azAllowed); i++){
if( fossil_stricmp(zArg1, azAllowed[i])==0 ) break;
}
if( i>=count(azAllowed) ){
| > > > > > > > > | 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
"filename",
"mlink",
"plink",
"event",
"tag",
"tagxref",
"unversioned",
"backlink",
};
int i;
if( zArg1==0 ){
/* Some legacy versions of SQLite will sometimes send spurious
** READ authorizations that have no table name. These can be
** ignored. */
rc = SQLITE_IGNORE;
break;
}
if( fossil_strncmp(zArg1, "fx_", 3)==0 ){
break;
}
for(i=0; i<count(azAllowed); i++){
if( fossil_stricmp(zArg1, azAllowed[i])==0 ) break;
}
if( i>=count(azAllowed) ){
|
| ︙ | ︙ | |||
501 502 503 504 505 506 507 |
/*
** Output a bunch of text that provides information about report
** formats
*/
static void report_format_hints(void){
char *zSchema;
| | | | 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 |
/*
** 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_schema WHERE name='ticket'");
if( zSchema==0 ){
zSchema = db_text(0,"SELECT sql FROM repository.sqlite_schema"
" WHERE name='ticket'");
}
@ <hr /><h3>TICKET Schema</h3>
@ <blockquote><pre>
@ %h(zSchema)
@ </pre></blockquote>
@ <h3>Notes</h3>
|
| ︙ | ︙ | |||
661 662 663 664 665 666 667 |
void *pUser, /* Pointer to output state */
int nArg, /* Number of columns in this result row */
const char **azArg, /* Text of data in all columns */
const char **azName /* Names of the columns */
){
struct GenerateHTML *pState = (struct GenerateHTML*)pUser;
int i;
| | | 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 |
void *pUser, /* Pointer to output state */
int nArg, /* Number of columns in this result row */
const char **azArg, /* Text of data in all columns */
const char **azName /* Names of the columns */
){
struct GenerateHTML *pState = (struct GenerateHTML*)pUser;
int i;
const char *zTid; /* Ticket hash. (value of column named '#') */
const char *zBg = 0; /* Use this background color */
/* Do initialization
*/
if( pState->nCount==0 ){
/* Turn off the authorizer. It is no longer doing anything since the
** query has already been prepared.
|
| ︙ | ︙ |
| ︙ | ︙ | |||
20 21 22 23 24 25 26 | #include "config.h" #include <time.h> #include "rss.h" #include <assert.h> /* ** WEBPAGE: timeline.rss | | | | > | | 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 | #include "config.h" #include <time.h> #include "rss.h" #include <assert.h> /* ** WEBPAGE: timeline.rss ** URL: /timeline.rss?y=TYPE&n=LIMIT&tkt=HASH&tag=TAG&wiki=NAME&name=FILENAME ** ** Produce an RSS feed of the timeline. ** ** TYPE may be: all, ci (show check-ins only), t (show ticket changes only), ** w (show wiki only), e (show tech notes only), f (show forum posts only), ** g (show tag/branch changes only). ** ** LIMIT is the number of items to show. ** ** tkt=HASH filters for only those events for the specified ticket. tag=TAG ** filters for a tag, and wiki=NAME for a wiki page. Only one may be used. ** ** In addition, name=FILENAME filters for a specific file. This may be ** combined with one of the other filters (useful for looking at a specific ** branch). */ |
| ︙ | ︙ | |||
221 222 223 224 225 226 227 | ** COMMAND: rss* ** ** Usage: %fossil rss ?OPTIONS? ** ** The CLI variant of the /timeline.rss page, this produces an RSS ** feed of the timeline to stdout. Options: ** | < | | < | < | < | < | < | > | < | | | 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 |
** COMMAND: rss*
**
** Usage: %fossil rss ?OPTIONS?
**
** The CLI variant of the /timeline.rss page, this produces an RSS
** feed of the timeline to stdout. Options:
**
** -type|y FLAG may be: all (default), ci (show check-ins only),
** t (show tickets only), w (show wiki only).
**
** -limit|n LIMIT The maximum number of items to show.
**
** -tkt HASH Filters for only those events for the specified ticket.
**
** -tag TAG filters for a tag
**
** -wiki NAME Filters on a specific wiki page.
**
** Only one of -tkt, -tag, or -wiki may be used.
**
** -name FILENAME filters for a specific file. This may be combined
** with one of the other filters (useful for looking
** at a specific branch).
**
** -url STRING Sets the RSS feed's root URL to the given string.
** The default is "URL-PLACEHOLDER" (without quotes).
*/
void cmd_timeline_rss(void){
Stmt q;
int nLine=0;
char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0;
Blob bSQL;
const char *zType = find_option("type","y",1); /* Type of events. All if NULL */
|
| ︙ | ︙ |
1 2 3 4 5 6 7 8 9 10 |
/* The javascript in this file was added by Joel Bruick on 2013-07-06,
** originally as in-line javascript. It does some kind of setup for
** side-by-side diff display, but I'm not really sure what.
*/
(function(){
var SCROLL_LEN = 25;
function initSbsDiff(diff){
var txtCols = diff.querySelectorAll('.difftxtcol');
var txtPres = diff.querySelectorAll('.difftxtcol pre');
var width = Math.max(txtPres[0].scrollWidth, txtPres[1].scrollWidth);
| > | | | > | > | | 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 |
/* The javascript in this file was added by Joel Bruick on 2013-07-06,
** originally as in-line javascript. It does some kind of setup for
** side-by-side diff display, but I'm not really sure what.
*/
(function(){
var SCROLL_LEN = 25;
function initSbsDiff(diff){
var txtCols = diff.querySelectorAll('.difftxtcol');
var txtPres = diff.querySelectorAll('.difftxtcol pre');
var width = Math.max(txtPres[0].scrollWidth, txtPres[1].scrollWidth);
var i;
for(i=0; i<2; i++){
txtPres[i].style.width = width + 'px';
txtCols[i].onscroll = function(e){
txtCols[0].scrollLeft = txtCols[1].scrollLeft = this.scrollLeft;
};
}
diff.tabIndex = 0;
diff.onkeydown = function(e){
e = e || event;
var len = {37: -SCROLL_LEN, 39: SCROLL_LEN}[e.keyCode];
if( !len ) return;
txtCols[0].scrollLeft += len;
return false;
};
}
document.querySelectorAll('.sbsdiffcols').forEach(initSbsDiff);
if(window.fossil && fossil.page){
fossil.page.tweakSbsDiffs = function(){
document.querySelectorAll('.sbsdiffcols').forEach(initSbsDiff);
};
}
})();
|
| ︙ | ︙ | |||
156 157 158 159 160 161 162 163 164 165 166 167 168 169 | @ mtime DATE, -- When added. seconds since 1970 @ scom TEXT -- Optional text explaining why the shun occurred @ ); @ @ -- Artifacts that should not be pushed are stored in the "private" @ -- table. Private artifacts are omitted from the "unclustered" and @ -- "unsent" tables. @ -- @ CREATE TABLE private(rid INTEGER PRIMARY KEY); @ @ -- An entry in this table describes a database query that generates a @ -- table of tickets. @ -- @ CREATE TABLE reportfmt( | > > > > > > | 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | @ mtime DATE, -- When added. seconds since 1970 @ scom TEXT -- Optional text explaining why the shun occurred @ ); @ @ -- Artifacts that should not be pushed are stored in the "private" @ -- table. Private artifacts are omitted from the "unclustered" and @ -- "unsent" tables. @ -- @ -- A phantom artifact (that is, an artifact with BLOB.SIZE<0 - an artifact @ -- for which we do not know the content) might also be marked as private. @ -- This comes about when an artifact is named in a manifest or tag but @ -- the content of that artifact is held privately by some other peer @ -- repository. @ -- @ CREATE TABLE private(rid INTEGER PRIMARY KEY); @ @ -- An entry in this table describes a database query that generates a @ -- table of tickets. @ -- @ CREATE TABLE reportfmt( |
| ︙ | ︙ | |||
258 259 260 261 262 263 264 | @ -- @ CREATE TABLE mlink( @ mid INTEGER, -- Check-in that contains fid @ fid INTEGER, -- New file content. 0 if deleted @ pmid INTEGER, -- Check-in that contains pid @ pid INTEGER, -- Prev file content. 0 if new. -1 merge @ fnid INTEGER REFERENCES filename, -- Name of the file | | | 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 | @ -- @ CREATE TABLE mlink( @ mid INTEGER, -- Check-in that contains fid @ fid INTEGER, -- New file content. 0 if deleted @ pmid INTEGER, -- Check-in that contains pid @ pid INTEGER, -- Prev file content. 0 if new. -1 merge @ fnid INTEGER REFERENCES filename, -- Name of the file @ pfnid INTEGER, -- Previous name. 0 if unchanged @ mperm INTEGER, -- File permissions. 1==exec @ isaux BOOLEAN DEFAULT 0 -- TRUE if pmid is the primary @ ); @ CREATE INDEX mlink_i1 ON mlink(mid); @ CREATE INDEX mlink_i2 ON mlink(fnid); @ CREATE INDEX mlink_i3 ON mlink(fid); @ CREATE INDEX mlink_i4 ON mlink(pid); |
| ︙ | ︙ | |||
313 314 315 316 317 318 319 | @ comment TEXT, -- Comment describing the event @ brief TEXT, -- Short comment when tagid already seen @ omtime DATETIME -- Original unchanged date+time, or NULL @ ); @ CREATE INDEX event_i1 ON event(mtime); @ @ -- A record of phantoms. A phantom is a record for which we know the | | | 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 | @ comment TEXT, -- Comment describing the event @ brief TEXT, -- Short comment when tagid already seen @ omtime DATETIME -- Original unchanged date+time, or NULL @ ); @ CREATE INDEX event_i1 ON event(mtime); @ @ -- A record of phantoms. A phantom is a record for which we know the @ -- file hash but we do not (yet) know the file content. @ -- @ CREATE TABLE phantom( @ rid INTEGER PRIMARY KEY -- Record ID of the phantom @ ); @ @ -- A record of orphaned delta-manifests. An orphan is a delta-manifest @ -- for which we have content, but its baseline-manifest is a phantom. |
| ︙ | ︙ | |||
354 355 356 357 358 359 360 | @ rid INTEGER PRIMARY KEY -- Record ID of the phantom @ ); @ @ -- Each artifact 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 | | | | 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 | @ rid INTEGER PRIMARY KEY -- Record ID of the phantom @ ); @ @ -- Each artifact 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-HASH" where @ -- HASH is the indentifier of the ticket. Tags used to assign symbolic @ -- names to baselines are branches are of the form "sym-NAME" where @ -- NAME is the symbolic name. @ -- @ CREATE TABLE tag( @ tagid INTEGER PRIMARY KEY, -- Numeric tag ID @ tagname TEXT UNIQUE -- Tag name. @ ); |
| ︙ | ︙ | |||
399 400 401 402 403 404 405 | @ -- When a hyperlink occurs from one artifact to another (for example @ -- when a check-in comment refers to a ticket) an entry is made in @ -- the following table for that hyperlink. This table is used to @ -- facilitate the display of "back links". @ -- @ CREATE TABLE backlink( @ target TEXT, -- Where the hyperlink points to | | | | | | 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 | @ -- When a hyperlink occurs from one artifact to another (for example @ -- when a check-in comment refers to a ticket) an entry is made in @ -- the following table for that hyperlink. This table is used to @ -- facilitate the display of "back links". @ -- @ CREATE TABLE backlink( @ target TEXT, -- Where the hyperlink points to @ srctype INT, -- 0=comment 1=ticket 2=wiki. See BKLNK_* below. @ srcid INT, -- EVENT.OBJID for the source document @ mtime TIMESTAMP, -- time that the hyperlink was added. Julian day. @ UNIQUE(target, srctype, srcid) @ ); @ CREATE INDEX backlink_src ON backlink(srcid, srctype); @ @ -- Each attachment is an entry in the following table. Only @ -- the most recent attachment (identified by the D card) is saved. @ -- @ CREATE TABLE attachment( @ attachid INTEGER PRIMARY KEY, -- Local id for this attachment @ isLatest BOOLEAN DEFAULT 0, -- True if this is the one to use @ mtime TIMESTAMP, -- Last changed. Julian day. @ src TEXT, -- Hash of the attachment. NULL to delete @ target TEXT, -- Object attached to. Wikiname or Tkt hash @ filename TEXT, -- Filename for the attachment @ comment TEXT, -- Comment associated with this attachment @ user TEXT -- Name of user adding attachment @ ); @ CREATE INDEX attachment_idx1 ON attachment(target, filename, mtime); @ CREATE INDEX attachment_idx2 ON attachment(src); @ |
| ︙ | ︙ | |||
468 469 470 471 472 473 474 475 476 477 478 479 480 481 | @ childid INT, @ isExclude BOOLEAN DEFAULT false, @ PRIMARY KEY(parentid, childid) @ ) WITHOUT ROWID; @ CREATE INDEX cherrypick_cid ON cherrypick(childid); ; /* ** Predefined tagid values */ #if INTERFACE # define TAG_BGCOLOR 1 /* Set the background color for display */ # define TAG_COMMENT 2 /* The check-in comment */ # define TAG_USER 3 /* User who made a checking */ | > > > > > > > > > > > > | 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 | @ childid INT, @ isExclude BOOLEAN DEFAULT false, @ PRIMARY KEY(parentid, childid) @ ) WITHOUT ROWID; @ CREATE INDEX cherrypick_cid ON cherrypick(childid); ; /* ** Allowed values for backlink.srctype */ #if INTERFACE # define BKLNK_COMMENT 0 /* Check-in comment */ # define BKLNK_TICKET 1 /* Ticket body or title */ # define BKLNK_WIKI 2 /* Wiki */ # define BKLNK_EVENT 3 /* Technote */ # define BKLNK_FORUM 4 /* Forum post */ # define ValidBklnk(X) (X>=0 && X<=4) /* True if backlink.srctype is valid */ #endif /* ** Predefined tagid values */ #if INTERFACE # define TAG_BGCOLOR 1 /* Set the background color for display */ # define TAG_COMMENT 2 /* The check-in comment */ # define TAG_USER 3 /* User who made a checking */ |
| ︙ | ︙ |
| ︙ | ︙ | |||
12 13 14 15 16 17 18 | ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file contains code to implement a search functions | | > | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file contains code to implement a search functions ** against timeline comments, check-in content, wiki pages, tickets, ** and/or forum posts. ** ** The search can be either a per-query "grep"-like search that scans ** the entire corpus. Or it can use the FTS4 or FTS5 search engine of ** SQLite. The choice is a administrator configuration option. ** ** The first option is referred to as "full-scan search". The second ** option is called "indexed search". |
| ︙ | ︙ | |||
329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
/*
** COMMAND: test-match
**
** Usage: %fossil test-match SEARCHSTRING FILE1 FILE2 ...
**
** Run the full-scan search algorithm using SEARCHSTRING against
** the text of the files listed. Output matches and snippets.
*/
void test_match_cmd(void){
Search *p;
int i;
Blob x;
int score;
char *zDoc;
| > > > > > > > > | 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
/*
** COMMAND: test-match
**
** Usage: %fossil test-match SEARCHSTRING FILE1 FILE2 ...
**
** Run the full-scan search algorithm using SEARCHSTRING against
** the text of the files listed. Output matches and snippets.
**
** Options:
**
** --begin TEXT Text to insert before each match
** --end TEXT Text to insert after each match
** --gap TEXT Text to indicate elided content
** --html Input is HTML
** --static Use the static Search object
*/
void test_match_cmd(void){
Search *p;
int i;
Blob x;
int score;
char *zDoc;
|
| ︙ | ︙ | |||
370 371 372 373 374 375 376 | ** ** search_init(PATTERN,BEGIN,END,GAP,FLAGS) ** ** All arguments are optional. PATTERN is the search pattern. If it ** is omitted, then the global search pattern is reset. BEGIN and END ** and GAP are the strings used to construct snippets. FLAGS is an ** integer bit pattern containing the various SRCH_CKIN, SRCH_DOC, | | > | 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 |
**
** search_init(PATTERN,BEGIN,END,GAP,FLAGS)
**
** All arguments are optional. PATTERN is the search pattern. If it
** is omitted, then the global search pattern is reset. BEGIN and END
** and GAP are the strings used to construct snippets. FLAGS is an
** integer bit pattern containing the various SRCH_CKIN, SRCH_DOC,
** SRCH_TKT, SRCH_FORUM, or SRCH_ALL bits to determine what is to be
** searched.
*/
static void search_init_sqlfunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
const char *zPattern = 0;
|
| ︙ | ︙ | |||
405 406 407 408 409 410 411 | } } /* search_match(TEXT, TEXT, ....) ** ** Using the full-scan search engine created by the most recent call ** to search_init(), match the input the TEXT arguments. | | | 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 |
}
}
/* search_match(TEXT, TEXT, ....)
**
** Using the full-scan search engine created by the most recent call
** to search_init(), match the input the TEXT arguments.
** Remember the results in the global full-scan search object.
** Return non-zero on a match and zero on a miss.
*/
static void search_match_sqlfunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
|
| ︙ | ︙ | |||
528 529 530 531 532 533 534 535 |
/*
** Register the various SQL functions (defined above) needed to implement
** full-scan search.
*/
void search_sql_setup(sqlite3 *db){
static int once = 0;
if( once++ ) return;
| > | | | | | | | | > > > > > | 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 |
/*
** Register the various SQL functions (defined above) needed to implement
** full-scan search.
*/
void search_sql_setup(sqlite3 *db){
static int once = 0;
static const int enc = SQLITE_UTF8|SQLITE_INNOCUOUS;
if( once++ ) return;
sqlite3_create_function(db, "search_match", -1, enc, 0,
search_match_sqlfunc, 0, 0);
sqlite3_create_function(db, "search_score", 0, enc, 0,
search_score_sqlfunc, 0, 0);
sqlite3_create_function(db, "search_snippet", 0, enc, 0,
search_snippet_sqlfunc, 0, 0);
sqlite3_create_function(db, "search_init", -1, enc, 0,
search_init_sqlfunc, 0, 0);
sqlite3_create_function(db, "stext", 3, enc, 0,
search_stext_sqlfunc, 0, 0);
sqlite3_create_function(db, "title", 3, enc, 0,
search_title_sqlfunc, 0, 0);
sqlite3_create_function(db, "body", 3, enc, 0,
search_body_sqlfunc, 0, 0);
sqlite3_create_function(db, "urlencode", 1, enc, 0,
search_urlencode_sqlfunc, 0, 0);
}
/*
** Testing the search function.
**
** COMMAND: search*
**
** Usage: %fossil search [-all|-a] [-limit|-n #] [-width|-W #] pattern...
**
** Search for timeline entries matching all words provided on the
** command line. Whole-word matches scope more highly than partial
** matches.
**
** Note: The command only search the EVENT table. So it will only
** display check-in comments or other comments that appear on an
** unaugmented timeline. It does not search document text or forum
** messages.
**
** Outputs, by default, some top-N fraction of the results. The -all
** option can be used to output all matches, regardless of their search
** score. The -limit option can be used to limit the number of entries
** returned. The -width option can be used to set the output width used
** when printing matches.
**
|
| ︙ | ︙ | |||
642 643 644 645 646 647 648 | #define SRCH_TECHNOTE 0x0010 /* Search over tech notes */ #define SRCH_FORUM 0x0020 /* Search over forum messages */ #define SRCH_ALL 0x003f /* Search over everything */ #endif /* ** Remove bits from srchFlags which are disallowed by either the | | > | 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 |
#define SRCH_TECHNOTE 0x0010 /* Search over tech notes */
#define SRCH_FORUM 0x0020 /* Search over forum messages */
#define SRCH_ALL 0x003f /* Search over everything */
#endif
/*
** Remove bits from srchFlags which are disallowed by either the
** current server configuration or by user permissions. Return
** the revised search flags mask.
*/
unsigned int search_restrict(unsigned int srchFlags){
static unsigned int knownGood = 0;
static unsigned int knownBad = 0;
static const struct { unsigned m; const char *zKey; } aSetng[] = {
{ SRCH_CKIN, "search-ci" },
{ SRCH_DOC, "search-doc" },
|
| ︙ | ︙ | |||
902 903 904 905 906 907 908 909 |
** The companion full-scan search routine is search_fullscan().
*/
static void search_indexed(
const char *zPattern, /* The query pattern */
unsigned int srchFlags /* What to search over */
){
Blob sql;
if( srchFlags==0 ) return;
| > > | > > > | > | 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 |
** The companion full-scan search routine is search_fullscan().
*/
static void search_indexed(
const char *zPattern, /* The query pattern */
unsigned int srchFlags /* What to search over */
){
Blob sql;
char *zPat = mprintf("%s",zPattern);
int i;
if( srchFlags==0 ) return;
sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8|SQLITE_INNOCUOUS, 0,
search_rank_sqlfunc, 0, 0);
for(i=0; zPat[i]; i++){
if( zPat[i]=='-' || zPat[i]=='"' ) zPat[i] = ' ';
}
blob_init(&sql, 0, 0);
blob_appendf(&sql,
"INSERT INTO x(label,url,score,id,date,snip) "
" SELECT ftsdocs.label,"
" ftsdocs.url,"
" rank(matchinfo(ftsidx,'pcsx')),"
" ftsdocs.type || ftsdocs.rid,"
" datetime(ftsdocs.mtime),"
" snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)"
" FROM ftsidx CROSS JOIN ftsdocs"
" WHERE ftsidx MATCH %Q"
" AND ftsdocs.rowid=ftsidx.docid",
zPat
);
fossil_free(zPat);
if( srchFlags!=SRCH_ALL ){
const char *zSep = " AND (";
static const struct { unsigned m; char c; } aMask[] = {
{ SRCH_CKIN, 'c' },
{ SRCH_DOC, 'd' },
{ SRCH_TKT, 't' },
{ SRCH_WIKI, 'w' },
|
| ︙ | ︙ | |||
1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 |
int search_run_and_output(
const char *zPattern, /* The query pattern */
unsigned int srchFlags, /* What to search over */
int fDebug /* Extra debugging output */
){
Stmt q;
int nRow = 0;
srchFlags = search_restrict(srchFlags);
if( srchFlags==0 ) return 0;
search_sql_setup(g.db);
add_content_sql_commands(g.db);
db_multi_exec(
"CREATE TEMP TABLE x(label,url,score,id,date,snip);"
);
if( !search_index_exists() ){
search_fullscan(zPattern, srchFlags); /* Full-scan search */
}else{
search_update_index(srchFlags); /* Update the index, if necessary */
search_indexed(zPattern, srchFlags); /* Indexed search */
}
| > > > > | > | > > > > > | 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 |
int search_run_and_output(
const char *zPattern, /* The query pattern */
unsigned int srchFlags, /* What to search over */
int fDebug /* Extra debugging output */
){
Stmt q;
int nRow = 0;
int nLimit = db_get_int("search-limit", 100);
if( P("searchlimit")!=0 ){
nLimit = atoi(P("searchlimit"));
}
srchFlags = search_restrict(srchFlags);
if( srchFlags==0 ) return 0;
search_sql_setup(g.db);
add_content_sql_commands(g.db);
db_multi_exec(
"CREATE TEMP TABLE x(label,url,score,id,date,snip);"
);
if( !search_index_exists() ){
search_fullscan(zPattern, srchFlags); /* Full-scan search */
}else{
search_update_index(srchFlags); /* Update the index, if necessary */
search_indexed(zPattern, srchFlags); /* Indexed search */
}
db_prepare(&q, "SELECT url, snip, label, score, id, substr(date,1,10)"
" FROM x"
" ORDER BY score DESC, date DESC;");
while( db_step(&q)==SQLITE_ROW ){
const char *zUrl = db_column_text(&q, 0);
const char *zSnippet = db_column_text(&q, 1);
const char *zLabel = db_column_text(&q, 2);
const char *zDate = db_column_text(&q, 5);
if( nRow==0 ){
@ <ol>
}
nRow++;
@ <li><p><a href='%R%s(zUrl)'>%h(zLabel)</a>
if( fDebug ){
@ (%e(db_column_double(&q,3)), %s(db_column_text(&q,4))
}
@ <br /><span class='snippet'>%z(cleanSnippet(zSnippet)) \
if( zDate && zDate[0] && strstr(zLabel,zDate)==0 ){
@ <small>(%h(zDate))</small>
}
@ </span></li>
if( nLimit && nRow>=nLimit ) break;
}
db_finalize(&q);
if( nRow ){
@ </ol>
}
return nRow;
}
|
| ︙ | ︙ | |||
1566 1567 1568 1569 1570 1571 1572 |
/*
** The document described by cType,rid,zName is about to be added or
** updated. If the document has already been indexed, then unindex it
** now while we still have access to the old content. Add the document
** to the queue of documents that need to be indexed or reindexed.
*/
void search_doc_touch(char cType, int rid, const char *zName){
| | | 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 |
/*
** The document described by cType,rid,zName is about to be added or
** updated. If the document has already been indexed, then unindex it
** now while we still have access to the old content. Add the document
** to the queue of documents that need to be indexed or reindexed.
*/
void search_doc_touch(char cType, int rid, const char *zName){
if( search_index_exists() && !content_is_private(rid) ){
char zType[2];
zType[0] = cType;
zType[1] = 0;
search_sql_setup(g.db);
db_multi_exec(
"DELETE FROM ftsidx WHERE docid IN"
" (SELECT rowid FROM ftsdocs WHERE type=%Q AND rid=%d AND idxed)",
|
| ︙ | ︙ | |||
1833 1834 1835 1836 1837 1838 1839 |
** stemmer (on|off) Turn the Porter stemmer on or off for indexed
** search. (Unindexed search is never stemmed.)
**
** The current search settings are displayed after any changes are applied.
** Run this command with no arguments to simply see the settings.
*/
void fts_config_cmd(void){
| | > > > | > > > > | 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 |
** stemmer (on|off) Turn the Porter stemmer on or off for indexed
** search. (Unindexed search is never stemmed.)
**
** The current search settings are displayed after any changes are applied.
** Run this command with no arguments to simply see the settings.
*/
void fts_config_cmd(void){
static const struct {
int iCmd;
const char *z;
} aCmd[] = {
{ 1, "reindex" },
{ 2, "index" },
{ 3, "disable" },
{ 4, "enable" },
{ 5, "stemmer" },
};
static const struct {
const char *zSetting;
const char *zName;
const char *zSw;
} aSetng[] = {
{ "search-ci", "check-in search:", "c" },
{ "search-doc", "document search:", "d" },
{ "search-tkt", "ticket search:", "t" },
{ "search-wiki", "wiki search:", "w" },
{ "search-technote", "tech note search:", "e" },
{ "search-forum", "forum search:", "f" },
};
|
| ︙ | ︙ | |||
1934 1935 1936 1937 1938 1939 1940 |
*/
void search_data_page(void){
Stmt q;
const char *zId = P("id");
const char *zType = P("y");
const char *zIdxed = P("ixed");
int id;
| | > > > > > > | | > > > > > > > > > > > > > > > > > > > > > > > > | | | | > | | > > > > | > > | > > > > > > > | > | > | 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 |
*/
void search_data_page(void){
Stmt q;
const char *zId = P("id");
const char *zType = P("y");
const char *zIdxed = P("ixed");
int id;
int cnt1 = 0, cnt2 = 0, cnt3 = 0;
login_check_credentials();
if( !g.perm.Admin ){ login_needed(0); return; }
if( !search_index_exists() ){
@ <p>Indexed search is disabled
style_footer();
return;
}
search_sql_setup(g.db);
style_submenu_element("Setup","%R/srchsetup");
if( zId!=0 && (id = atoi(zId))>0 ){
/* Show information about a single ftsdocs entry */
style_header("Information about ftsdoc entry %d", id);
style_submenu_element("Summary","%R/test-ftsdocs");
db_prepare(&q,
"SELECT type||rid, name, idxed, label, url, datetime(mtime)"
" FROM ftsdocs WHERE rowid=%d", id
);
if( db_step(&q)==SQLITE_ROW ){
const char *zUrl = db_column_text(&q,4);
const char *zDocId = db_column_text(&q,0);
char *zName;
char *z;
@ <table border=0>
@ <tr><td align='right'>docid:<td> <td>%d(id)
@ <tr><td align='right'>id:<td><td>%s(zDocId)
@ <tr><td align='right'>name:<td><td>%h(db_column_text(&q,1))
@ <tr><td align='right'>idxed:<td><td>%d(db_column_int(&q,2))
@ <tr><td align='right'>label:<td><td>%h(db_column_text(&q,3))
@ <tr><td align='right'>url:<td><td>
@ <a href='%R%s(zUrl)'>%h(zUrl)</a>
@ <tr><td align='right'>mtime:<td><td>%s(db_column_text(&q,5))
z = db_text(0, "SELECT title FROM ftsidx WHERE docid=%d",id);
if( z && z[0] ){
@ <tr><td align="right">title:<td><td>%h(z)
fossil_free(z);
}
z = db_text(0, "SELECT body FROM ftsidx WHERE docid=%d",id);
if( z && z[0] ){
@ <tr><td align="right" valign="top">body:<td><td>%h(z)
fossil_free(z);
}
@ </table>
zName = mprintf("Indexed '%c' docs",zDocId[0]);
style_submenu_element(zName,"%R/test-ftsdocs?y=%c&ixed=1",zDocId[0]);
zName = mprintf("Unindexed '%c' docs",zDocId[0]);
style_submenu_element(zName,"%R/test-ftsdocs?y=%c&ixed=0",zDocId[0]);
}
db_finalize(&q);
style_footer();
return;
}
if( zType!=0 && zType[0]!=0 && zType[1]==0 &&
zIdxed!=0 && (zIdxed[0]=='1' || zIdxed[0]=='0') && zIdxed[1]==0
){
int ixed = zIdxed[0]=='1';
char *zName;
style_header("List of '%c' documents that are%s indexed",
zType[0], ixed ? "" : " not");
style_submenu_element("Summary","%R/test-ftsdocs");
if( ixed==0 ){
zName = mprintf("Indexed '%c' docs",zType[0]);
style_submenu_element(zName,"%R/test-ftsdocs?y=%c&ixed=1",zType[0]);
}else{
zName = mprintf("Unindexed '%c' docs",zType[0]);
style_submenu_element(zName,"%R/test-ftsdocs?y=%c&ixed=0",zType[0]);
}
db_prepare(&q,
"SELECT rowid, type||rid ||' '|| coalesce(label,'')"
" FROM ftsdocs WHERE type='%c' AND %s idxed",
zType[0], ixed ? "" : "NOT"
);
@ <ul>
while( db_step(&q)==SQLITE_ROW ){
@ <li> <a href='test-ftsdocs?id=%d(db_column_int(&q,0))'>
@ %h(db_column_text(&q,1))</a>
}
@ </ul>
db_finalize(&q);
style_footer();
return;
}
style_header("Summary of ftsdocs");
db_prepare(&q,
"SELECT type, sum(idxed IS TRUE), sum(idxed IS FALSE), count(*)"
" FROM ftsdocs"
" GROUP BY 1 ORDER BY 4 DESC"
);
@ <table border=1 cellpadding=3 cellspacing=0>
@ <thead>
@ <tr><th>Type<th>Indexed<th>Unindexed<th>Total
@ </thead>
@ <tbody>
while( db_step(&q)==SQLITE_ROW ){
const char *zType = db_column_text(&q,0);
int nIndexed = db_column_int(&q, 1);
int nUnindexed = db_column_int(&q, 2);
int nTotal = db_column_int(&q, 3);
@ <tr><td>%h(zType)
if( nIndexed>0 ){
@ <td align="right"><a href='%R/test-ftsdocs?y=%s(zType)&ixed=1'>\
@ %d(nIndexed)</a>
}else{
@ <td align="right">0
}
if( nUnindexed>0 ){
@ <td align="right"><a href='%R/test-ftsdocs?y=%s(zType)&ixed=0'>\
@ %d(nUnindexed)</a>
}else{
@ <td align="right">0
}
@ <td align="right">%d(nTotal)
@ </tr>
cnt1 += nIndexed;
cnt2 += nUnindexed;
cnt3 += nTotal;
}
db_finalize(&q);
@ </tbody><tfooter>
@ <tr><th>Total<th align="right">%d(cnt1)<th align="right">%d(cnt2)
@ <th align="right">%d(cnt3)
@ </tfooter>
@ </table>
style_footer();
}
|
| ︙ | ︙ | |||
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 |
while( zTest[0] ){
if( strchr(zCap, zTest[0]) ) return 1;
zTest++;
}
return 0;
}
/*
** WEBPAGE: secaudit0
**
** Run a security audit of the current Fossil setup, looking
** for configuration problems that might allow unauthorized
** access to the repository.
**
** This page requires administrator access. It is usually
** accessed using the Admin/Security-Audit menu option
** from any of the default skins.
*/
void secaudit0_page(void){
const char *zAnonCap; /* Capabilities of user "anonymous" and "nobody" */
const char *zPubPages; /* GLOB pattern for public pages */
const char *zSelfCap; /* Capabilities of self-registered users */
char *z;
int n;
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
style_header("Security Audit");
@ <ol>
/* Step 1: Determine if the repository is public or private. "Public"
** means that any anonymous user on the internet can access all content.
** "Private" repos require (non-anonymous) login to access all content,
** though some content may be accessible anonymously.
*/
zAnonCap = db_text("", "SELECT fullcap(NULL)");
zPubPages = db_get("public-pages",0);
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < | | | | < < < | | 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 |
while( zTest[0] ){
if( strchr(zCap, zTest[0]) ) return 1;
zTest++;
}
return 0;
}
/*
** Parse the content-security-policy
** into separate fields, and return a pointer to a null-terminated
** array of pointers to strings, one entry for each field. Or return
** a NULL pointer if no CSP could be located in the header.
**
** Memory to hold the returned array and of the strings is obtained from
** a single memory allocation, which the caller should free to avoid a
** memory leak.
*/
static char **parse_content_security_policy(void){
char **azCSP = 0;
int nCSP = 0;
char *zAll;
char *zCopy;
int nAll = 0;
int jj;
int nSemi;
zAll = style_csp(0);
nAll = (int)strlen(zAll);
for(jj=nSemi=0; jj<nAll; jj++){ if( zAll[jj]==';' ) nSemi++; }
azCSP = fossil_malloc( nAll+1+(nSemi+2)*sizeof(char*) );
zCopy = (char*)&azCSP[nSemi+2];
memcpy(zCopy,zAll,nAll);
zCopy[nAll] = 0;
while( fossil_isspace(zCopy[0]) || zCopy[0]==';' ){ zCopy++; }
azCSP[0] = zCopy;
nCSP = 1;
for(jj=0; zCopy[jj]; jj++){
if( zCopy[jj]==';' ){
int k;
for(k=jj-1; k>0 && fossil_isspace(zCopy[k]); k--){ zCopy[k] = 0; }
zCopy[jj] = 0;
while( jj+1<nAll
&& (fossil_isspace(zCopy[jj+1]) || zCopy[jj+1]==';')
){
jj++;
}
assert( nCSP<nSemi+1 );
azCSP[nCSP++] = zCopy+jj;
}
}
assert( nCSP<=nSemi+2 );
azCSP[nCSP] = 0;
fossil_free(zAll);
return azCSP;
}
/*
** WEBPAGE: secaudit0
**
** Run a security audit of the current Fossil setup, looking
** for configuration problems that might allow unauthorized
** access to the repository.
**
** This page requires administrator access. It is usually
** accessed using the Admin/Security-Audit menu option
** from any of the default skins.
*/
void secaudit0_page(void){
const char *zAnonCap; /* Capabilities of user "anonymous" and "nobody" */
const char *zDevCap; /* Capabilities of user group "developer" */
const char *zReadCap; /* Capabilities of user group "reader" */
const char *zPubPages; /* GLOB pattern for public pages */
const char *zSelfCap; /* Capabilities of self-registered users */
int hasSelfReg = 0; /* True if able to self-register */
char *z;
int n;
CapabilityString *pCap;
char **azCSP; /* Parsed content security policy */
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
style_header("Security Audit");
@ <ol>
/* Step 1: Determine if the repository is public or private. "Public"
** means that any anonymous user on the internet can access all content.
** "Private" repos require (non-anonymous) login to access all content,
** though some content may be accessible anonymously.
*/
zAnonCap = db_text("", "SELECT fullcap(NULL)");
zDevCap = db_text("", "SELECT fullcap('v')");
zReadCap = db_text("", "SELECT fullcap('u')");
zPubPages = db_get("public-pages",0);
hasSelfReg = db_get_boolean("self-register",0);
pCap = capability_add(0, db_get("default-perms","u"));
capability_expand(pCap);
zSelfCap = capability_string(pCap);
capability_free(pCap);
if( hasAnyCap(zAnonCap,"as") ){
@ <li><p>This repository is <big><b>Wildly INSECURE</b></big> because
@ it grants administrator privileges to anonymous users. You
@ should <a href="takeitprivate">take this repository private</a>
@ immediately! Or, at least remove the Setup and Admin privileges
@ for users "anonymous" and "login" on the
@ <a href="setup_ulist">User Configuration</a> page.
}else if( hasAnyCap(zSelfCap,"as") && hasSelfReg ){
@ <li><p>This repository is <big><b>Wildly INSECURE</b></big> because
@ it grants administrator privileges to self-registered users. You
@ should <a href="takeitprivate">take this repository private</a>
@ and/or disable self-registration
@ immediately! Or, at least remove the Setup and Admin privileges
@ from the default permissions for new users.
}else if( hasAnyCap(zAnonCap,"y") ){
|
| ︙ | ︙ | |||
103 104 105 106 107 108 109 |
@ <p>Fix this by <a href="takeitprivate">taking the repository private</a>
@ or by removing the "y" permission from the default permissions or
@ by disabling self-registration.
}else if( hasAnyCap(zAnonCap,"goz") ){
@ <li><p>This repository is <big><b>PUBLIC</b></big>. All
@ checked-in content can be accessed by anonymous users.
@ <a href="takeitprivate">Take it private</a>.<p>
| | | > | | | | | | | | > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
@ <p>Fix this by <a href="takeitprivate">taking the repository private</a>
@ or by removing the "y" permission from the default permissions or
@ by disabling self-registration.
}else if( hasAnyCap(zAnonCap,"goz") ){
@ <li><p>This repository is <big><b>PUBLIC</b></big>. All
@ checked-in content can be accessed by anonymous users.
@ <a href="takeitprivate">Take it private</a>.<p>
}else if( hasAnyCap(zSelfCap,"goz") && hasSelfReg ){
@ <li><p>This repository is <big><b>PUBLIC</b></big> because all
@ checked-in content can be accessed by self-registered users.
@ This repostory would be private if you disabled self-registration.</p>
}else if( !hasAnyCap(zAnonCap, "jrwy234567")
&& (!hasSelfReg || !hasAnyCap(zSelfCap, "jrwy234567"))
&& (zPubPages==0 || zPubPages[0]==0) ){
@ <li><p>This repository is <big><b>Completely PRIVATE</b></big>.
@ A valid login and password is required to access any content.
}else{
@ <li><p>This repository is <big><b>Mostly PRIVATE</b></big>.
@ A valid login and password is usually required, however some
@ content can be accessed either anonymously or by self-registered
@ users:
@ <ul>
if( hasSelfReg ){
if( hasAnyCap(zAnonCap,"j") || hasAnyCap(zSelfCap,"j") ){
@ <li> Wiki pages
}
if( hasAnyCap(zAnonCap,"r") || hasAnyCap(zSelfCap,"r") ){
@ <li> Tickets
}
if( hasAnyCap(zAnonCap,"234567") || hasAnyCap(zSelfCap,"234567") ){
@ <li> Forum posts
}
}
if( zPubPages && zPubPages[0] ){
Glob *pGlob = glob_create(zPubPages);
int i;
@ <li> "Public Pages" are URLs that match any of these GLOB patterns:
@ <p><ul>
for(i=0; i<pGlob->nPattern; i++){
@ <li> %h(pGlob->azPattern[i])
}
@ </ul>
@ <p>Anoymous users are vested with capabilities "%h(zSelfCap)" on
@ public pages. See the "Public Pages" entry in the
@ "User capability summary" below.
}
@ </ul>
if( zPubPages && zPubPages[0] ){
@ <p>Change GLOB patterns exceptions using the "Public pages" setting
@ on the <a href="setup_access">Access Settings</a> page.</p>
}
}
/* Make sure the HTTPS is required for login, at least, so that the
** password does not go across the Internet in the clear.
*/
if( db_get_int("redirect-to-https",0)==0 ){
@ <li><p><b>WARNING:</b>
@ Sensitive material such as login passwords can be sent over an
@ unencrypted connection.
@ <p>Fix this by changing the "Redirect to HTTPS" setting on the
@ <a href="setup_access">Access Control</a> page. If you were using
@ the old "Redirect to HTTPS on Login Page" setting, switch to the
@ new setting: it has a more secure implementation.
}
#ifdef FOSSIL_ENABLE_TH1_DOCS
/* The use of embedded TH1 is dangerous. Warn if it is possible.
*/
if( !Th_AreDocsEnabled() ){
@ <li><p>
@ This server is compiled with -DFOSSIL_ENABLE_TH1_DOCS. TH1 docs
@ are disabled for this particular repository, so you are safe for
@ now. However, to prevent future problems caused by accidentally
@ enabling TH1 docs in the future, it is recommended that you
@ recompile Fossil without the -DFOSSIL_ENABLE_TH1_DOCS flag.</p>
}else{
@ <li><p><b>DANGER:</b>
@ This server is compiled with -DFOSSIL_ENABLE_TH1_DOCS and TH1 docs
@ are enabled for this repository. Anyone who can check-in or push
@ to this repository can create a malicious TH1 script and then cause
@ that script to be run on the server. This is a serious security concern.
@ TH1 docs should only be enabled for repositories with a very limited
@ number of trusted committers, and the repository should be monitored
@ closely to ensure no hostile content sneaks in. If a bad TH1 script
@ does make it into the repository, the only want to prevent it from
@ being run is to shun it.</p>
@
@ <p>Disable TH1 docs by recompiling Fossil without the
@ -DFOSSIL_ENABLE_TH1_DOCS flag, and/or clear the th1-docs setting
@ and ensure that the TH1_ENABLE_DOCS environment variable does not
@ exist in the environment.</p>
}
#endif
/* Anonymous users should not be able to harvest email addresses
** from tickets.
*/
if( hasAnyCap(zAnonCap, "e") ){
@ <li><p><b>WARNING:</b>
@ Anonymous users can view email addresses and other personally
|
| ︙ | ︙ | |||
195 196 197 198 199 200 201 |
@ forum posts. This defeats the whole purpose of moderation.
@ <p>Fix this by removing the "Mod-Wiki", "Mod-Tkt", and "Mod-Forum"
@ privileges (<a href="%R/setup_ucap_list">capabilities</a> "fq5")
@ from users "anonymous" and "nobody"
@ on the <a href="setup_ulist">User Configuration</a> page.
}
| < | < | > > | > | < | > | 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
@ forum posts. This defeats the whole purpose of moderation.
@ <p>Fix this by removing the "Mod-Wiki", "Mod-Tkt", and "Mod-Forum"
@ privileges (<a href="%R/setup_ucap_list">capabilities</a> "fq5")
@ from users "anonymous" and "nobody"
@ on the <a href="setup_ulist">User Configuration</a> page.
}
/* Obsolete: */
if( hasAnyCap(zAnonCap, "d") ||
hasAnyCap(zDevCap, "d") ||
hasAnyCap(zReadCap, "d") ){
@ <li><p><b>WARNING:</b>
@ One or more users has the <a
@ href="https://fossil-scm.org/forum/forumpost/43c78f4bef">obsolete</a>
@ "d" capability. You should remove it using the
@ <a href="setup_ulist">User Configuration</a> page in case we
@ ever reuse the letter for another purpose.
}
/* If anonymous users are allowed to create new Wiki, then
** wiki moderation should be activated to pervent spam.
*/
if( hasAnyCap(zAnonCap, "fk") ){
if( db_get_boolean("modreq-wiki",0)==0 ){
|
| ︙ | ︙ | |||
378 379 380 381 382 383 384 |
@ <li><p>
@ Unable to get the system load average. This can prevent Fossil
@ from throttling expensive operations during peak demand.
@ <p>If running in a chroot jail on Linux, verify that the /proc
@ filesystem is mounted within the jail, so that the load average
@ can be obtained from the /proc/loadavg file.
}else {
| | | 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 |
@ <li><p>
@ Unable to get the system load average. This can prevent Fossil
@ from throttling expensive operations during peak demand.
@ <p>If running in a chroot jail on Linux, verify that the /proc
@ filesystem is mounted within the jail, so that the load average
@ can be obtained from the /proc/loadavg file.
}else {
double r = atof(db_get("max-loadavg", 0));
if( r<=0.0 ){
@ <li><p>
@ Load average limiting is turned off. This can cause the server
@ to bog down if many requests for expensive services (such as
@ large diffs or tarballs) arrive at about the same time.
@ <p>To fix this, set the "Server Load Average Limit" on the
@ <a href="setup_access">Access Control</a> page to approximately
|
| ︙ | ︙ | |||
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 |
ext_files();
nFile = db_int(0, "SELECT count(*) FROM sfile");
nCgi = nFile==0 ? 0 : db_int(0,"SELECT count(*) FROM sfile WHERE isexe");
@ <li><p> CGI Extensions are enabled with a document root
@ at <a href='%R/extfilelist'>%h(g.zExtRoot)</a> holding
@ %d(nCgi) CGIs and %d(nFile-nCgi) static content and data files.
}
@ <li><p> User capability summary:
capability_summary();
if( alert_enabled() ){
@ <li><p> Email alert configuration summary:
@ <table class="label-value">
stats_for_email();
@ </table>
}else{
@ <li><p> Email alerts are disabled
}
@ </ol>
style_footer();
}
/*
** WEBPAGE: takeitprivate
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
ext_files();
nFile = db_int(0, "SELECT count(*) FROM sfile");
nCgi = nFile==0 ? 0 : db_int(0,"SELECT count(*) FROM sfile WHERE isexe");
@ <li><p> CGI Extensions are enabled with a document root
@ at <a href='%R/extfilelist'>%h(g.zExtRoot)</a> holding
@ %d(nCgi) CGIs and %d(nFile-nCgi) static content and data files.
}
if( fileedit_glob()!=0 ){
@ <li><p><a href='%R/fileedit'>Online File Editing</a> is enabled
@ for this repository. Clear the
@ <a href='%R/setup_settings'>"fileedit-glob" setting</a> to
@ disable online editing.</p>
}
@ <li><p> User capability summary:
capability_summary();
azCSP = parse_content_security_policy();
if( azCSP==0 ){
@ <li><p> WARNING: No Content Security Policy (CSP) is specified in the
@ header. Though not required, a strong CSP is recommended. Fossil will
@ automatically insert an appropriate CSP if you let it generate the
@ HTML <tt><head></tt> element by omitting <tt><body></tt>
@ from the header configuration in your customized skin.
@
}else{
int ii;
@ <li><p> Content Security Policy:
@ <ol type="a">
for(ii=0; azCSP[ii]; ii++){
@ <li>%h(azCSP[ii])
}
@ </ol>
}
fossil_free(azCSP);
if( alert_enabled() ){
@ <li><p> Email alert configuration summary:
@ <table class="label-value">
stats_for_email();
@ </table>
}else{
@ <li><p> Email alerts are disabled
}
n = db_int(0,"SELECT count(*) FROM ("
"SELECT rid FROM phantom EXCEPT SELECT rid FROM private)");
if( n>0 ){
@ <li><p>\
@ There exists public phantom artifacts in this repository, shown below.
@ Phantom artifacts are artifacts whose hash name is referenced by some
@ other artifact but whose content is unknown. Some phantoms are marked
@ private and those are ignored. But public phantoms cause unnecessary
@ sync traffic and might represent malicious attempts to corrupt the
@ repository structure.
@ </p>
table_of_public_phantoms();
@ </li>
}
@ </ol>
style_footer();
}
/*
** WEBPAGE: takeitprivate
|
| ︙ | ︙ | |||
556 557 558 559 560 561 562 |
return;
}
@ <p>The server error log at "%h(g.zErrlog)" is %,lld(szFile) bytes in size.
style_submenu_element("Download", "%R/errorlog?download");
style_submenu_element("Truncate", "%R/errorlog?truncate");
in = fossil_fopen(g.zErrlog, "rb");
if( in==0 ){
| | | 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 |
return;
}
@ <p>The server error log at "%h(g.zErrlog)" is %,lld(szFile) bytes in size.
style_submenu_element("Download", "%R/errorlog?download");
style_submenu_element("Truncate", "%R/errorlog?truncate");
in = fossil_fopen(g.zErrlog, "rb");
if( in==0 ){
@ <p class='generalError'>Unable to open that file for reading!</p>
style_footer();
return;
}
if( szFile>MXSHOWLOG && P("all")==0 ){
@ <form action="%R/errorlog" method="POST">
@ <p>Only the last %,d(MXSHOWLOG) bytes are shown.
@ <input type="submit" name="all" value="Show All">
|
| ︙ | ︙ |
| ︙ | ︙ | |||
194 195 196 197 198 199 200 201 202 203 204 205 |
zQ = "off";
}
if( zQ ){
int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
if( iQ!=iVal ){
login_verify_csrf_secret();
db_set(zVar, iQ ? "1" : "0", 0);
admin_log("Set option [%q] to [%q].",
zVar, iQ ? "on" : "off");
iVal = iQ;
}
}
| > | > | | > > | < | 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 |
zQ = "off";
}
if( zQ ){
int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
if( iQ!=iVal ){
login_verify_csrf_secret();
db_set(zVar, iQ ? "1" : "0", 0);
setup_incr_cfgcnt();
admin_log("Set option [%q] to [%q].",
zVar, iQ ? "on" : "off");
iVal = iQ;
}
}
@ <label><input type="checkbox" name="%s(zQParm)" \
@ aria-label="%s(zLabel[0]?zLabel:zQParm)" \
if( iVal ){
@ checked="checked" \
}
if( disabled ){
@ disabled="disabled" \
}
@ /> <b>%s(zLabel)</b></label>
}
/*
** Generate an entry box for an attribute.
*/
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 */
int disabled /* 1 if disabled */
){
const char *zVal = db_get(zVar, zDflt);
const char *zQ = P(zQParm);
if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
const int nZQ = (int)strlen(zQ);
login_verify_csrf_secret();
setup_incr_cfgcnt();
db_set(zVar, zQ, 0);
admin_log("Set entry_attribute %Q to: %.*s%s",
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
zVal = zQ;
}
@ <input aria-label="%h(zLabel[0]?zLabel:zQParm)" type="text" \
@ id="%s(zQParm)" name="%s(zQParm)" value="%h(zVal)" size="%d(width)" \
if( disabled ){
@ disabled="disabled" \
}
@ /> <b>%s(zLabel)</b>
}
/*
|
| ︙ | ︙ | |||
256 257 258 259 260 261 262 263 264 265 266 267 |
){
const char *z = db_get(zVar, zDflt);
const char *zQ = P(zQP);
if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){
const int nZQ = (int)strlen(zQ);
login_verify_csrf_secret();
db_set(zVar, zQ, 0);
admin_log("Set textarea_attribute %Q to: %.*s%s",
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
z = zQ;
}
if( rows>0 && cols>0 ){
| > | > | | | 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 |
){
const char *z = db_get(zVar, zDflt);
const char *zQ = P(zQP);
if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){
const int nZQ = (int)strlen(zQ);
login_verify_csrf_secret();
db_set(zVar, zQ, 0);
setup_incr_cfgcnt();
admin_log("Set textarea_attribute %Q to: %.*s%s",
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
z = zQ;
}
if( rows>0 && cols>0 ){
@ <textarea id="id%s(zQP)" name="%s(zQP)" rows="%d(rows)" \
@ aria-label="%h(zLabel[0]?zLabel:zQP)" \
if( disabled ){
@ disabled="disabled" \
}
@ cols="%d(cols)">%h(z)</textarea>
if( *zLabel ){
@ <span class="textareaLabel">%s(zLabel)</span>
}
}
return z;
}
/*
|
| ︙ | ︙ | |||
291 292 293 294 295 296 297 298 299 300 301 |
const char *z = db_get(zVar, zDflt);
const char *zQ = P(zQP);
int i;
if( zQ && fossil_strcmp(zQ,z)!=0){
const int nZQ = (int)strlen(zQ);
login_verify_csrf_secret();
db_set(zVar, zQ, 0);
admin_log("Set multiple_choice_attribute %Q to: %.*s%s",
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
z = zQ;
}
| > | | 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
const char *z = db_get(zVar, zDflt);
const char *zQ = P(zQP);
int i;
if( zQ && fossil_strcmp(zQ,z)!=0){
const int nZQ = (int)strlen(zQ);
login_verify_csrf_secret();
db_set(zVar, zQ, 0);
setup_incr_cfgcnt();
admin_log("Set multiple_choice_attribute %Q to: %.*s%s",
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
z = zQ;
}
@ <select aria-label="%h(zLabel)" size="1" name="%s(zQP)" id="id%s(zQP)">
for(i=0; i<nChoice*2; i+=2){
const char *zSel = fossil_strcmp(azChoice[i],z)==0 ? " selected" : "";
@ <option value="%h(azChoice[i])"%s(zSel)>%h(azChoice[i+1])</option>
}
@ </select> <b>%h(zLabel)</b>
}
|
| ︙ | ︙ | |||
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 |
onoff_attribute("Enable /test_env",
"test_env_enable", "test_env_enable", 0, 0);
@ <p>When enabled, the %h(g.zBaseURL)/test_env URL is available to all
@ users. When disabled (the default) only users Admin and Setup can visit
@ the /test_env page.
@ (Property: "test_env_enable")
@ </p>
@
@ <hr />
onoff_attribute("Allow REMOTE_USER authentication",
"remote_user_ok", "remote_user_ok", 0, 0);
@ <p>When enabled, if the REMOTE_USER environment variable is set to the
@ login name of a valid user and no other login credentials are available,
@ then the REMOTE_USER is accepted as an authenticated user.
@ (Property: "remote_user_ok")
@ </p>
@
@ <hr />
onoff_attribute("Allow HTTP_AUTHENTICATION authentication",
"http_authentication_ok", "http_authentication_ok", 0, 0);
@ <p>When enabled, allow the use of the HTTP_AUTHENTICATION environment
@ variable or the "Authentication:" HTTP header to find the username and
| > > > > > > > > > | < < < < < < < < < | 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 |
onoff_attribute("Enable /test_env",
"test_env_enable", "test_env_enable", 0, 0);
@ <p>When enabled, the %h(g.zBaseURL)/test_env URL is available to all
@ users. When disabled (the default) only users Admin and Setup can visit
@ the /test_env page.
@ (Property: "test_env_enable")
@ </p>
@
@ <hr />
onoff_attribute("Enable /artifact_stats",
"artifact_stats_enable", "artifact_stats_enable", 0, 0);
@ <p>When enabled, the %h(g.zBaseURL)/artifact_stats URL is available to all
@ users. When disabled (the default) only users with check-in privilege may
@ access the /artifact_stats page.
@ (Property: "artifact_stats_enable")
@ </p>
@
@ <hr />
onoff_attribute("Allow REMOTE_USER authentication",
"remote_user_ok", "remote_user_ok", 0, 0);
@ <p>When enabled, if the REMOTE_USER environment variable is set to the
@ login name of a valid user and no other login credentials are available,
@ then the REMOTE_USER is accepted as an authenticated user.
@ (Property: "remote_user_ok")
@ </p>
@
@ <hr />
onoff_attribute("Allow HTTP_AUTHENTICATION authentication",
"http_authentication_ok", "http_authentication_ok", 0, 0);
@ <p>When enabled, allow the use of the HTTP_AUTHENTICATION environment
@ variable or the "Authentication:" HTTP header to find the username and
@ password. This is another way of supporting Basic Authentication.
@ (Property: "http_authentication_ok")
@ </p>
@
@ <hr />
entry_attribute("Login expiration time", 6, "cookie-expire", "cex",
"8766", 0);
@ <p>The number of hours for which a login is valid. This must be a
@ positive number. The default is 8766 hours which is approximately equal
@ to a year.
@ (Property: "cookie-expire")</p>
|
| ︙ | ︙ | |||
486 487 488 489 490 491 492 |
@ for users who are not logged in. (Property: "require-captcha")</p>
@ <hr />
entry_attribute("Public pages", 30, "public-pages",
"pubpage", "", 0);
@ <p>A comma-separated list of glob patterns for pages that are accessible
@ without needing a login and using the privileges given by the
| | > > > | | | | > > > | > > > > > > > > > | > > > > > > > | > > > > > > > > | | 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 |
@ for users who are not logged in. (Property: "require-captcha")</p>
@ <hr />
entry_attribute("Public pages", 30, "public-pages",
"pubpage", "", 0);
@ <p>A comma-separated list of glob patterns for pages that are accessible
@ without needing a login and using the privileges given by the
@ "Default privileges" setting below.
@
@ <p>Example use case: Set this field to "/doc/trunk/www/*" and set
@ the "Default privileges" to include the "o" privilege
@ to give anonymous users read-only permission to the
@ latest version of the embedded documentation in the www/ folder without
@ allowing them to see the rest of the source code.
@ (Property: "public-pages")
@ </p>
@ <hr />
onoff_attribute("Allow users to register themselves",
"self-register", "selfreg", 0, 0);
@ <p>Allow users to register themselves on the /register webpage.
@ A self-registration creates a new entry in the USER table and
@ perhaps also in the SUBSCRIBER table if email notification is
@ enabled.
@ (Property: "self-register")</p>
@ <hr />
onoff_attribute("Email verification required for self-registration",
"selfreg-verify", "sfverify", 0, 0);
@ <p>If enabled, self-registration creates a new entry in the USER table
@ with only capabilities "7". The default user capabilities are not
@ added until the email address associated with the self-registration
@ has been verified. This setting only makes sense if
@ email notifications are enabled.
@ (Property: "selfreg-verify")</p>
@ <hr />
onoff_attribute("Allow anonymous subscriptions",
"anon-subscribe", "anonsub", 1, 0);
@ <p>If disabled, email notification subscriptions are only allowed
@ for users with a login. If Nobody or Anonymous visit the /subscribe
@ page, they are redirected to /register or /login.
@ (Property: "anon-subscribe")</p>
@ <hr />
entry_attribute("Authorized subscription email addresses", 35,
"auth-sub-email", "asemail", "", 0);
@ <p>This is a comma-separated list of GLOB patterns that specify
@ email addresses that are authorized to subscriptions. If blank
@ (the usual case), then any email address can be used to self-register.
@ This setting is used to limit subscriptions to members of a particular
@ organization or group based on their email address.
@ (Property: "auth-sub-email")</p>
@ <hr />
entry_attribute("Default privileges", 10, "default-perms",
"defaultperms", "u", 0);
@ <p>Permissions given to users that... <ul><li>register themselves using
@ the self-registration procedure (if enabled), or <li>access "public"
@ pages identified by the public-pages glob pattern above, or <li>
|
| ︙ | ︙ | |||
574 575 576 577 578 579 580 |
@ is not currently part of any login-group.
@ To join a login group, fill out the form below.</p>
@
@ <form action="%s(g.zTop)/setup_login_group" method="post"><div>
login_insert_csrf_secret();
@ <blockquote><table border="0">
@
| > | > | | > | | | > | > | | 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 |
@ is not currently part of any login-group.
@ To join a login group, fill out the form below.</p>
@
@ <form action="%s(g.zTop)/setup_login_group" method="post"><div>
login_insert_csrf_secret();
@ <blockquote><table border="0">
@
@ <tr><th align="right" id="rfigtj">Repository filename \
@ in group to join:</th>
@ <td width="5"></td><td>
@ <input aria-labelledby="rfigtj" type="text" size="50" \
@ value="%h(zRepo)" name="repo"></td></tr>
@
@ <tr><th align="right" id="lotar">Login on the above repo:</th>
@ <td width="5"></td><td>
@ <input aria-labelledby="lotar" type="text" size="20" \
@ value="%h(zLogin)" name="login"></td></tr>
@
@ <tr><th align="right" id="lgpw">Password:</th>
@ <td width="5"></td><td>
@ <input aria-labelledby="lgpw" type="password" size="20" name="pw">\
@ </td></tr>
@
@ <tr><th align="right" id="nolg">Name of login-group:</th>
@ <td width="5"></td><td>
@ <input aria-labelledby="nolg" type="text" size="30" \
@ value="%h(zNewName)" name="newname">
@ (only used if creating a new login-group).</td></tr>
@
@ <tr><td colspan="3" align="center">
@ <input type="submit" value="Join" name="join"></td></tr>
@ </table></blockquote></div></form>
}else{
Stmt q;
|
| ︙ | ︙ | |||
731 732 733 734 735 736 737 738 739 740 741 742 743 744 |
}else if( tmDiff<0.0 ){
sqlite3_snprintf(sizeof(zTmDiff), zTmDiff, "%.1f", -tmDiff);
@ %s(zTmDiff) hours behind UTC.</p>
}else{
@ %s(zTmDiff) hours ahead of UTC.</p>
}
@ <p>(Property: "timeline-utc")
@ <hr />
multiple_choice_attribute("Per-Item Time Format", "timeline-date-format",
"tdf", "0", count(azTimeFormats)/2, azTimeFormats);
@ <p>If the "HH:MM" or "HH:MM:SS" format is selected, then the date is shown
@ in a separate box (using CSS class "timelineDate") whenever the date
@ changes. With the "YYYY-MM-DD HH:MM" and "YYMMDD ..." formats,
@ the complete date and time is shown on every timeline entry using the
| > > > > > > > | 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 |
}else if( tmDiff<0.0 ){
sqlite3_snprintf(sizeof(zTmDiff), zTmDiff, "%.1f", -tmDiff);
@ %s(zTmDiff) hours behind UTC.</p>
}else{
@ %s(zTmDiff) hours ahead of UTC.</p>
}
@ <p>(Property: "timeline-utc")
@ <hr />
multiple_choice_attribute("Style", "timeline-default-style",
"tdss", "0", N_TIMELINE_VIEW_STYLE, timeline_view_styles);
@ <p>The default timeline viewing style, for when the user has not
@ specified an alternative. (Property: "timeline-default-style")</p>
@ <hr />
multiple_choice_attribute("Per-Item Time Format", "timeline-date-format",
"tdf", "0", count(azTimeFormats)/2, azTimeFormats);
@ <p>If the "HH:MM" or "HH:MM:SS" format is selected, then the date is shown
@ in a separate box (using CSS class "timelineDate") whenever the date
@ changes. With the "YYYY-MM-DD HH:MM" and "YYMMDD ..." formats,
@ the complete date and time is shown on every timeline entry using the
|
| ︙ | ︙ | |||
837 838 839 840 841 842 843 844 |
} else {
@ <br />
}
}
}
@ <br /><input type="submit" name="submit" value="Apply Changes" />
@ </td><td style="width:50px;"></td><td valign="top">
for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
| > | > > > > > > > > < < < < | | | < > | | 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 |
} else {
@ <br />
}
}
}
@ <br /><input type="submit" name="submit" value="Apply Changes" />
@ </td><td style="width:50px;"></td><td valign="top">
@ <table>
for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
if( pSet->width>0 && !pSet->forceTextArea ){
int hasVersionableValue = pSet->versionable &&
(db_get_versioned(pSet->name, NULL)!=0);
@ <tr><td>
@ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
if( pSet->versionable ){
@ (v)
} else {
@
}
@</td><td>
entry_attribute("", /*pSet->width*/ 25, pSet->name,
pSet->var!=0 ? pSet->var : pSet->name,
(char*)pSet->def, hasVersionableValue);
@</td></tr>
}
}
@</table>
@ </td><td style="width:50px;"></td><td valign="top">
for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
if( pSet->width>0 && pSet->forceTextArea ){
int hasVersionableValue = db_get_versioned(pSet->name, NULL)!=0;
@ <a href='%R/help?cmd=%s(pSet->name)'>%s(pSet->name)</a>
if( pSet->versionable ){
@ (v)<br />
} else {
@ <br />
}
|
| ︙ | ︙ | |||
1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 |
@ or tag are treated specially when this feature is enabled.
@ <ul>
@ <li> <b>branch/</b><i>branch-name</i>
@ <li> <b>checkin/</b><i>full-checkin-hash</i>
@ <li> <b>tag/</b><i>tag-name</i>
@ </ul>
@ (Property: "wiki-about")</p>
@ <hr />
onoff_attribute("Enable WYSIWYG Wiki Editing",
"wysiwyg-wiki", "wysiwyg-wiki", 0, 0);
@ <p>Enable what-you-see-is-what-you-get (WYSIWYG) editing of wiki pages.
@ The WYSIWYG editor generates HTML instead of markup, which makes
@ subsequent manual editing more difficult.
@ (Property: "wysiwyg-wiki")</p>
| > > > > > > > > > > > > > > > > > > > > > | 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 |
@ or tag are treated specially when this feature is enabled.
@ <ul>
@ <li> <b>branch/</b><i>branch-name</i>
@ <li> <b>checkin/</b><i>full-checkin-hash</i>
@ <li> <b>tag/</b><i>tag-name</i>
@ </ul>
@ (Property: "wiki-about")</p>
@ <hr />
entry_attribute("Allow Unsafe HTML In Markdown", 6,
"safe-html", "safe-html", "", 0);
@ <p>Allow "unsafe" HTML (ex: <script>, <form>, etc) to be
@ generated by <a href="%R/md_rules">Markdown-formatted</a> documents.
@ This setting is a string where each character indicates a "type" of
@ document in which to allow unsafe HTML:
@ <ul>
@ <li> <b>b</b> → checked-in files, embedded documentation
@ <li> <b>f</b> → forum posts
@ <li> <b>t</b> → tickets
@ <li> <b>w</b> → wiki pages
@ </ul>
@ Include letters for each type of document for which unsafe HTML should
@ be allowed. For example, to allow unsafe HTML only for checked-in files,
@ make this setting be just "<b>b</b>". To allow unsafe HTML anywhere except
@ in forum posts, make this setting be "<b>btw</b>". The default is an
@ empty string which means that Fossil never allows Markdown documents
@ to generate unsafe HTML.
@ (Property: "safe-html")</p>
@ <hr />
@ <hr />
onoff_attribute("Enable WYSIWYG Wiki Editing",
"wysiwyg-wiki", "wysiwyg-wiki", 0, 0);
@ <p>Enable what-you-see-is-what-you-get (WYSIWYG) editing of wiki pages.
@ The WYSIWYG editor generates HTML instead of markup, which makes
@ subsequent manual editing more difficult.
@ (Property: "wysiwyg-wiki")</p>
|
| ︙ | ︙ | |||
1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 |
login_needed(0);
return;
}
db_begin_transaction();
if( P("clear")!=0 && cgi_csrf_safe(1) ){
db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
cgi_replace_parameter("adunit","");
}
style_header("Edit Ad Unit");
@ <form action="%s(g.zTop)/setup_adunit" method="post"><div>
login_insert_csrf_secret();
@ <b>Banner Ad-Unit:</b><br />
textarea_attribute("", 6, 80, "adunit", "adunit", "", 0);
| > > | 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 |
login_needed(0);
return;
}
db_begin_transaction();
if( P("clear")!=0 && cgi_csrf_safe(1) ){
db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
cgi_replace_parameter("adunit","");
cgi_replace_parameter("adright","");
setup_incr_cfgcnt();
}
style_header("Edit Ad Unit");
@ <form action="%s(g.zTop)/setup_adunit" method="post"><div>
login_insert_csrf_secret();
@ <b>Banner Ad-Unit:</b><br />
textarea_attribute("", 6, 80, "adunit", "adunit", "", 0);
|
| ︙ | ︙ | |||
1372 1373 1374 1375 1376 1377 1378 |
@ <input type="submit" name="go" value="Run SQL">
@ <input type="submit" name="schema" value="Show Schema">
@ <input type="submit" name="tablelist" value="List Tables">
@ <input type="submit" name="configtab" value="CONFIG Table Query">
@ </form>
if( P("schema") ){
zQ = sqlite3_mprintf(
| | | | 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 |
@ <input type="submit" name="go" value="Run SQL">
@ <input type="submit" name="schema" value="Show Schema">
@ <input type="submit" name="tablelist" value="List Tables">
@ <input type="submit" name="configtab" value="CONFIG Table Query">
@ </form>
if( P("schema") ){
zQ = sqlite3_mprintf(
"SELECT sql FROM repository.sqlite_sqlite"
" WHERE sql IS NOT NULL ORDER BY name");
go = 1;
}else if( P("tablelist") ){
zQ = sqlite3_mprintf(
"SELECT name FROM repository.sqlite_schema WHERE type='table'"
" ORDER BY name");
go = 1;
}
if( go ){
sqlite3_stmt *pStmt;
int rc;
const char *zTail;
|
| ︙ | ︙ | |||
1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 |
}
if( search_index_exists() ){
@ <p>Currently using an SQLite FTS4 search index. This makes search
@ run faster, especially on large repositories, but takes up space.</p>
onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0);
@ <p><input type="submit" name="fts0" value="Delete The Full-Text Index">
@ <input type="submit" name="fts1" value="Rebuild The Full-Text Index">
}else{
@ <p>The SQLite FTS4 search index is disabled. All searching will be
@ a full-text scan. This usually works fine, but can be slow for
@ larger repositories.</p>
onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0);
@ <p><input type="submit" name="fts1" value="Create A Full-Text Index">
}
| > | 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 |
}
if( search_index_exists() ){
@ <p>Currently using an SQLite FTS4 search index. This makes search
@ run faster, especially on large repositories, but takes up space.</p>
onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0);
@ <p><input type="submit" name="fts0" value="Delete The Full-Text Index">
@ <input type="submit" name="fts1" value="Rebuild The Full-Text Index">
style_submenu_element("FTS Index Debugging","%R/test-ftsdocs");
}else{
@ <p>The SQLite FTS4 search index is disabled. All searching will be
@ a full-text scan. This usually works fine, but can be slow for
@ larger repositories.</p>
onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0);
@ <p><input type="submit" name="fts1" value="Create A Full-Text Index">
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
**
** with=CAP Only show users that have one or more capabilities in CAP.
*/
void setup_ulist(void){
Stmt s;
double rNow;
const char *zWith = P("with");
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
style_submenu_element("Add", "setup_uedit");
style_submenu_element("Log", "access_log");
style_submenu_element("Help", "setup_ulist_notes");
style_header("User List");
| > > > > | | 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 |
**
** with=CAP Only show users that have one or more capabilities in CAP.
*/
void setup_ulist(void){
Stmt s;
double rNow;
const char *zWith = P("with");
int bUnusedOnly = P("unused")!=0;
login_check_credentials();
if( !g.perm.Admin ){
login_needed(0);
return;
}
style_submenu_element("Add", "setup_uedit");
style_submenu_element("Log", "access_log");
style_submenu_element("Help", "setup_ulist_notes");
if( alert_tables_exist() ){
style_submenu_element("Subscribers", "subscribers");
}
style_header("User List");
if( (zWith==0 || zWith[0]==0) && !bUnusedOnly ){
@ <table border=1 cellpadding=2 cellspacing=0 class='userTable'>
@ <thead><tr>
@ <th>Category
@ <th>Capabilities (<a href='%R/setup_ucap_list'>key</a>)
@ <th>Info <th>Last Change</tr></thead>
@ <tbody>
db_prepare(&s,
|
| ︙ | ︙ | |||
89 90 91 92 93 94 95 |
@ </tr>
}
db_finalize(&s);
@ </tbody></table>
@ <div class='section'>Users</div>
}else{
style_submenu_element("All Users", "setup_ulist");
| > > > | | | | | | > > > > > > > > > > > > | | 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 |
@ </tr>
}
db_finalize(&s);
@ </tbody></table>
@ <div class='section'>Users</div>
}else{
style_submenu_element("All Users", "setup_ulist");
if( bUnusedOnly ){
@ <div class='section'>Unused logins</div>
}else if( zWith ){
if( zWith[1]==0 ){
@ <div class='section'>Users with capability "%h(zWith)"</div>
}else{
@ <div class='section'>Users with any capability in "%h(zWith)"</div>
}
}
}
if( !bUnusedOnly ){
style_submenu_element("Unused", "setup_ulist?unused");
}
@ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \
@ data-column-types='ktxTTK' data-init-sort='2'>
@ <thead><tr>
@ <th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login</tr></thead>
@ <tbody>
db_multi_exec(
"CREATE TEMP TABLE lastAccess(uname TEXT PRIMARY KEY, atime REAL)"
"WITHOUT ROWID;"
);
if( db_table_exists("repository","accesslog") ){
db_multi_exec(
"INSERT INTO lastAccess(uname, atime)"
" SELECT uname, max(mtime) FROM ("
" SELECT uname, mtime FROM accesslog WHERE success"
" UNION ALL"
" SELECT login AS uname, rcvfrom.mtime AS mtime"
" FROM rcvfrom JOIN user USING(uid))"
" GROUP BY 1;"
);
}
if( bUnusedOnly ){
zWith = mprintf(
" AND login NOT IN ("
"SELECT user FROM event WHERE user NOT NULL "
"UNION ALL SELECT euser FROM event WHERE euser NOT NULL%s)"
" AND uid NOT IN (SELECT uid FROM rcvfrom)",
alert_tables_exist() ?
" UNION ALL SELECT suname FROM subscriber WHERE suname NOT NULL":"");
}else if( zWith && zWith[0] ){
zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith);
}else{
zWith = "";
}
db_prepare(&s,
"SELECT uid, login, cap, info, date(mtime,'unixepoch'),"
" lower(login) AS sortkey, "
|
| ︙ | ︙ | |||
513 514 515 516 517 518 519 |
@ <input type="hidden" name="login" value="%s(zLogin)">
@ <input type="hidden" name="info" value="">
@ <input type="hidden" name="pw" value="*">
}
@ <input type="hidden" name="referer" value="%h(cgi_referer("setup_ulist"))">
@ <table width="100%%">
@ <tr>
| | > | > > | | > | > > > > > > > > > | | > | > > | 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 |
@ <input type="hidden" name="login" value="%s(zLogin)">
@ <input type="hidden" name="info" value="">
@ <input type="hidden" name="pw" value="*">
}
@ <input type="hidden" name="referer" value="%h(cgi_referer("setup_ulist"))">
@ <table width="100%%">
@ <tr>
@ <td class="usetupEditLabel" id="suuid">User ID:</td>
if( uid ){
@ <td>%d(uid) <input aria-labelledby="suuid" type="hidden" \
@ name="id" value="%d(uid)"/>\
@ </td>
}else{
@ <td>(new user)<input aria-labelledby="suuid" type="hidden" name="id" \
@ value="0" /></td>
}
@ </tr>
@ <tr>
@ <td class="usetupEditLabel" id="sulgn">Login:</td>
if( login_is_special(zLogin) ){
@ <td><b>%h(zLogin)</b></td>
}else{
@ <td><input aria-labelledby="sulgn" type="text" name="login" \
@ value="%h(zLogin)" />
if( alert_tables_exist() ){
int sid;
sid = db_int(0, "SELECT subscriberId FROM subscriber"
" WHERE suname=%Q", zLogin);
if( sid>0 ){
@ <a href="%R/alerts?sid=%d(sid)">\
@ (subscription info for %h(zLogin))</a>\
}
}
@ </td></tr>
@ <tr>
@ <td class="usetupEditLabel" id="sucnfo">Contact Info:</td>
@ <td><textarea aria-labelledby="sucnfo" name="info" cols="40" \
@ rows="2">%h(zInfo)</textarea></td>
}
@ </tr>
@ <tr>
@ <td class="usetupEditLabel">Capabilities:</td>
@ <td width="100%%">
#define B(x) inherit[x]
@ <div class="columns" style="column-width:13em;">
@ <ul style="list-style-type: none;">
if( g.perm.Setup ){
@ <li><label><input type="checkbox" name="as"%s(oa['s']) />
@ Setup%s(B('s'))</label>
}
@ <li><label><input type="checkbox" name="aa"%s(oa['a']) />
@ Admin%s(B('a'))</label>
@ <li><label><input type="checkbox" name="au"%s(oa['u']) />
@ Reader%s(B('u'))</label>
@ <li><label><input type="checkbox" name="av"%s(oa['v']) />
@ Developer%s(B('v'))</label>
#if 0 /* Not Used */
@ <li><label><input type="checkbox" name="ad"%s(oa['d']) />
@ Delete%s(B('d'))</label>
#endif
@ <li><label><input type="checkbox" name="ae"%s(oa['e']) />
@ View-PII%s(B('e'))</label>
@ <li><label><input type="checkbox" name="ap"%s(oa['p']) />
@ Password%s(B('p'))</label>
@ <li><label><input type="checkbox" name="ai"%s(oa['i']) />
@ Check-In%s(B('i'))</label>
@ <li><label><input type="checkbox" name="ao"%s(oa['o']) />
|
| ︙ | ︙ | |||
620 621 622 623 624 625 626 |
@ <td>
@ <span id="usetupEditCapability">(missing JS?)</span>
@ <a href="%R/setup_ucap_list">(key)</a>
@ </td>
@ </tr>
if( !login_is_special(zLogin) ){
@ <tr>
| | > | > | | 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 |
@ <td>
@ <span id="usetupEditCapability">(missing JS?)</span>
@ <a href="%R/setup_ucap_list">(key)</a>
@ </td>
@ </tr>
if( !login_is_special(zLogin) ){
@ <tr>
@ <td align="right" id="supw">Password:</td>
if( zPw[0] ){
/* Obscure the password for all users */
@ <td><input aria-labelledby="supw" type="password" autocomplete="off" \
@ name="pw" value="**********" /></td>
}else{
/* Show an empty password as an empty input field */
@ <td><input aria-labelledby="supw" type="password" name="pw" \
@ autocomplete="off" value="" /></td>
}
@ </tr>
}
zGroup = login_group_name();
if( zGroup ){
@ <tr>
@ <td valign="top" align="right">Scope:</td>
|
| ︙ | ︙ |
| ︙ | ︙ | |||
99 100 101 102 103 104 105 | /* * blk0() and blk() perform the initial expand. * I got the idea of expanding during the round function from SSLeay * * blk0le() for little-endian and blk0be() for big-endian. */ | < < < < < < < < < < < < < < < < < < < | 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
/*
* blk0() and blk() perform the initial expand.
* I got the idea of expanding during the round function from SSLeay
*
* blk0le() for little-endian and blk0be() for big-endian.
*/
#define SHA_ROT(x,l,r) ((x) << (l) | (x) >> (r))
#define rol(x,k) SHA_ROT(x,k,32-(k))
#define ror(x,k) SHA_ROT(x,32-(k),k)
#define blk0le(i) (block[i] = (ror(block[i],8)&0xFF00FF00) \
|(rol(block[i],8)&0x00FF00FF))
#define blk0be(i) block[i]
#define blk(i) (block[i&15] = rol(block[(i+13)&15]^block[(i+8)&15] \
^block[(i+2)&15]^block[i&15],1))
/*
|
| ︙ | ︙ |
| ︙ | ︙ | |||
31 32 33 34 35 36 37 38 39 40 41 42 43 44 | ** utility for accessing SQLite databases. */ #if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS) /* This needs to come before any includes for MSVC compiler */ #define _CRT_SECURE_NO_WARNINGS #endif /* ** Warning pragmas copied from msvc.h in the core. */ #if defined(_MSC_VER) #pragma warning(disable : 4054) #pragma warning(disable : 4055) #pragma warning(disable : 4100) | > > > > > > > > | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | ** utility for accessing SQLite databases. */ #if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS) /* This needs to come before any includes for MSVC compiler */ #define _CRT_SECURE_NO_WARNINGS #endif /* ** Determine if we are dealing with WinRT, which provides only a subset of ** the full Win32 API. */ #if !defined(SQLITE_OS_WINRT) # define SQLITE_OS_WINRT 0 #endif /* ** Warning pragmas copied from msvc.h in the core. */ #if defined(_MSC_VER) #pragma warning(disable : 4054) #pragma warning(disable : 4055) #pragma warning(disable : 4100) |
| ︙ | ︙ | |||
143 144 145 146 147 148 149 | # define shell_stifle_history(X) # define SHELL_USE_LOCAL_GETLINE 1 #endif #if defined(_WIN32) || defined(WIN32) | > > > | | | | | | | | | | | | | | | | > | 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 | # define shell_stifle_history(X) # define SHELL_USE_LOCAL_GETLINE 1 #endif #if defined(_WIN32) || defined(WIN32) # if SQLITE_OS_WINRT # define SQLITE_OMIT_POPEN 1 # else # include <io.h> # include <fcntl.h> # define isatty(h) _isatty(h) # ifndef access # define access(f,m) _access((f),(m)) # endif # ifndef unlink # define unlink _unlink # endif # ifndef strdup # define strdup _strdup # endif # undef popen # define popen _popen # undef pclose # define pclose _pclose # endif #else /* Make sure isatty() has a prototype. */ extern int isatty(int); # if !defined(__RTP__) && !defined(_WRS_KERNEL) /* popen and pclose are not C89 functions and so are ** sometimes omitted from the <stdio.h> header */ |
| ︙ | ︙ | |||
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | /* ctype macros that work with signed characters */ #define IsSpace(X) isspace((unsigned char)X) #define IsDigit(X) isdigit((unsigned char)X) #define ToLower(X) (char)tolower((unsigned char)X) #if defined(_WIN32) || defined(WIN32) #include <windows.h> /* string conversion routines only needed on Win32 */ extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR); extern char *sqlite3_win32_mbcs_to_utf8_v2(const char *, int); extern char *sqlite3_win32_utf8_to_mbcs_v2(const char *, int); extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText); #endif /* On Windows, we normally run with output mode of TEXT so that \n characters ** are automatically translated into \r\n. However, this behavior needs ** to be disabled in some cases (ex: when generating CSV output and when ** rendering quoted strings that contain \n characters). The following ** routines take care of that. */ | > > > | | 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 |
/* ctype macros that work with signed characters */
#define IsSpace(X) isspace((unsigned char)X)
#define IsDigit(X) isdigit((unsigned char)X)
#define ToLower(X) (char)tolower((unsigned char)X)
#if defined(_WIN32) || defined(WIN32)
#if SQLITE_OS_WINRT
#include <intrin.h>
#endif
#include <windows.h>
/* string conversion routines only needed on Win32 */
extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR);
extern char *sqlite3_win32_mbcs_to_utf8_v2(const char *, int);
extern char *sqlite3_win32_utf8_to_mbcs_v2(const char *, int);
extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText);
#endif
/* On Windows, we normally run with output mode of TEXT so that \n characters
** are automatically translated into \r\n. However, this behavior needs
** to be disabled in some cases (ex: when generating CSV output and when
** rendering quoted strings that contain \n characters). The following
** routines take care of that.
*/
#if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT
static void setBinaryMode(FILE *file, int isOutput){
if( isOutput ) fflush(file);
_setmode(_fileno(file), _O_BINARY);
}
static void setTextMode(FILE *file, int isOutput){
if( isOutput ) fflush(file);
_setmode(_fileno(file), _O_TEXT);
|
| ︙ | ︙ | |||
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 |
** Check to see if we have timer support. Return 1 if necessary
** support found (or found previously).
*/
static int hasTimer(void){
if( getProcessTimesAddr ){
return 1;
} else {
/* GetProcessTimes() isn't supported in WIN95 and some other Windows
** versions. See if the version we are running on has it, and if it
** does, save off a pointer to it and the current process handle.
*/
hProcess = GetCurrentProcess();
if( hProcess ){
HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll"));
if( NULL != hinstLib ){
getProcessTimesAddr =
(GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes");
if( NULL != getProcessTimesAddr ){
return 1;
}
FreeLibrary(hinstLib);
}
}
}
return 0;
}
/*
** Begin timing an operation
*/
| > > | 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 |
** Check to see if we have timer support. Return 1 if necessary
** support found (or found previously).
*/
static int hasTimer(void){
if( getProcessTimesAddr ){
return 1;
} else {
#if !SQLITE_OS_WINRT
/* GetProcessTimes() isn't supported in WIN95 and some other Windows
** versions. See if the version we are running on has it, and if it
** does, save off a pointer to it and the current process handle.
*/
hProcess = GetCurrentProcess();
if( hProcess ){
HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll"));
if( NULL != hinstLib ){
getProcessTimesAddr =
(GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes");
if( NULL != getProcessTimesAddr ){
return 1;
}
FreeLibrary(hinstLib);
}
}
#endif
}
return 0;
}
/*
** Begin timing an operation
*/
|
| ︙ | ︙ | |||
411 412 413 414 415 416 417 418 419 420 421 422 423 424 | static sqlite3 *globalDb = 0; /* ** True if an interrupt (Control-C) has been received. */ static volatile int seenInterrupt = 0; /* ** This is the name of our program. It is set in main(), used ** in a number of other places, mostly for error messages. */ static char *Argv0; /* | > > > > > > > > > | 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 | static sqlite3 *globalDb = 0; /* ** True if an interrupt (Control-C) has been received. */ static volatile int seenInterrupt = 0; #ifdef SQLITE_DEBUG /* ** Out-of-memory simulator variables */ static unsigned int oomCounter = 0; /* Simulate OOM when equals 1 */ static unsigned int oomRepeat = 0; /* Number of OOMs in a row */ static void*(*defaultMalloc)(int) = 0; /* The low-level malloc routine */ #endif /* SQLITE_DEBUG */ /* ** This is the name of our program. It is set in main(), used ** in a number of other places, mostly for error messages. */ static char *Argv0; /* |
| ︙ | ︙ | |||
461 462 463 464 465 466 467 468 469 470 471 472 473 474 |
#endif
/* Indicate out-of-memory and exit. */
static void shell_out_of_memory(void){
raw_printf(stderr,"Error: out of memory\n");
exit(1);
}
/*
** Write I/O traces to the following stream.
*/
#ifdef SQLITE_ENABLE_IOTRACE
static FILE *iotrace = 0;
#endif
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
#endif
/* Indicate out-of-memory and exit. */
static void shell_out_of_memory(void){
raw_printf(stderr,"Error: out of memory\n");
exit(1);
}
#ifdef SQLITE_DEBUG
/* This routine is called when a simulated OOM occurs. It is broken
** out as a separate routine to make it easy to set a breakpoint on
** the OOM
*/
void shellOomFault(void){
if( oomRepeat>0 ){
oomRepeat--;
}else{
oomCounter--;
}
}
#endif /* SQLITE_DEBUG */
#ifdef SQLITE_DEBUG
/* This routine is a replacement malloc() that is used to simulate
** Out-Of-Memory (OOM) errors for testing purposes.
*/
static void *oomMalloc(int nByte){
if( oomCounter ){
if( oomCounter==1 ){
shellOomFault();
return 0;
}else{
oomCounter--;
}
}
return defaultMalloc(nByte);
}
#endif /* SQLITE_DEBUG */
#ifdef SQLITE_DEBUG
/* Register the OOM simulator. This must occur before any memory
** allocations */
static void registerOomSimulator(void){
sqlite3_mem_methods mem;
sqlite3_config(SQLITE_CONFIG_GETMALLOC, &mem);
defaultMalloc = mem.xMalloc;
mem.xMalloc = oomMalloc;
sqlite3_config(SQLITE_CONFIG_MALLOC, &mem);
}
#endif
/*
** Write I/O traces to the following stream.
*/
#ifdef SQLITE_ENABLE_IOTRACE
static FILE *iotrace = 0;
#endif
|
| ︙ | ︙ | |||
880 881 882 883 884 885 886 | ** CREATE INDEX ** CREATE UNIQUE INDEX ** CREATE VIEW ** CREATE TRIGGER ** CREATE VIRTUAL TABLE ** ** This UDF is used by the .schema command to insert the schema name of | | | 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 |
** CREATE INDEX
** CREATE UNIQUE INDEX
** CREATE VIEW
** CREATE TRIGGER
** CREATE VIRTUAL TABLE
**
** This UDF is used by the .schema command to insert the schema name of
** attached databases into the middle of the sqlite_schema.sql field.
*/
static void shellAddSchemaName(
sqlite3_context *pCtx,
int nVal,
sqlite3_value **apVal
){
static const char *aPrefix[] = {
|
| ︙ | ︙ | |||
2003 2004 2005 2006 2007 2008 2009 |
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
| | > | | > | | > | | > | | 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 |
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
rc = sqlite3_create_function(db, "sha3", 1,
SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC,
0, sha3Func, 0, 0);
if( rc==SQLITE_OK ){
rc = sqlite3_create_function(db, "sha3", 2,
SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC,
0, sha3Func, 0, 0);
}
if( rc==SQLITE_OK ){
rc = sqlite3_create_function(db, "sha3_query", 1,
SQLITE_UTF8 | SQLITE_DIRECTONLY,
0, sha3QueryFunc, 0, 0);
}
if( rc==SQLITE_OK ){
rc = sqlite3_create_function(db, "sha3_query", 2,
SQLITE_UTF8 | SQLITE_DIRECTONLY,
0, sha3QueryFunc, 0, 0);
}
return rc;
}
/************************* End ../ext/misc/shathree.c ********************/
/************************* Begin ../ext/misc/fileio.c ******************/
/*
|
| ︙ | ︙ | |||
2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 |
if( rc ) return 2;
sqlite3_result_int64(pCtx, nWrite);
}
}
if( mtime>=0 ){
#if defined(_WIN32)
/* Windows */
FILETIME lastAccess;
FILETIME lastWrite;
SYSTEMTIME currentTime;
LONGLONG intervals;
HANDLE hFile;
LPWSTR zUnicodeName;
| > | 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 |
if( rc ) return 2;
sqlite3_result_int64(pCtx, nWrite);
}
}
if( mtime>=0 ){
#if defined(_WIN32)
#if !SQLITE_OS_WINRT
/* Windows */
FILETIME lastAccess;
FILETIME lastWrite;
SYSTEMTIME currentTime;
LONGLONG intervals;
HANDLE hFile;
LPWSTR zUnicodeName;
|
| ︙ | ︙ | |||
2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 |
if( hFile!=INVALID_HANDLE_VALUE ){
BOOL bResult = SetFileTime(hFile, NULL, &lastAccess, &lastWrite);
CloseHandle(hFile);
return !bResult;
}else{
return 1;
}
#elif defined(AT_FDCWD) && 0 /* utimensat() is not universally available */
/* Recent unix */
struct timespec times[2];
times[0].tv_nsec = times[1].tv_nsec = 0;
times[0].tv_sec = time(0);
times[1].tv_sec = mtime;
if( utimensat(AT_FDCWD, zFile, times, AT_SYMLINK_NOFOLLOW) ){
| > | 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 |
if( hFile!=INVALID_HANDLE_VALUE ){
BOOL bResult = SetFileTime(hFile, NULL, &lastAccess, &lastWrite);
CloseHandle(hFile);
return !bResult;
}else{
return 1;
}
#endif
#elif defined(AT_FDCWD) && 0 /* utimensat() is not universally available */
/* Recent unix */
struct timespec times[2];
times[0].tv_nsec = times[1].tv_nsec = 0;
times[0].tv_sec = time(0);
times[1].tv_sec = mtime;
if( utimensat(AT_FDCWD, zFile, times, AT_SYMLINK_NOFOLLOW) ){
|
| ︙ | ︙ | |||
2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 |
(void)argv;
(void)pzErr;
rc = sqlite3_declare_vtab(db, "CREATE TABLE x" FSDIR_SCHEMA);
if( rc==SQLITE_OK ){
pNew = (fsdir_tab*)sqlite3_malloc( sizeof(*pNew) );
if( pNew==0 ) return SQLITE_NOMEM;
memset(pNew, 0, sizeof(*pNew));
}
*ppVtab = (sqlite3_vtab*)pNew;
return rc;
}
/*
** This method is the destructor for fsdir vtab objects.
| > | 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 |
(void)argv;
(void)pzErr;
rc = sqlite3_declare_vtab(db, "CREATE TABLE x" FSDIR_SCHEMA);
if( rc==SQLITE_OK ){
pNew = (fsdir_tab*)sqlite3_malloc( sizeof(*pNew) );
if( pNew==0 ) return SQLITE_NOMEM;
memset(pNew, 0, sizeof(*pNew));
sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY);
}
*ppVtab = (sqlite3_vtab*)pNew;
return rc;
}
/*
** This method is the destructor for fsdir vtab objects.
|
| ︙ | ︙ | |||
3002 3003 3004 3005 3006 3007 3008 |
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
| | > | > | 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 |
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
rc = sqlite3_create_function(db, "readfile", 1,
SQLITE_UTF8|SQLITE_DIRECTONLY, 0,
readfileFunc, 0, 0);
if( rc==SQLITE_OK ){
rc = sqlite3_create_function(db, "writefile", -1,
SQLITE_UTF8|SQLITE_DIRECTONLY, 0,
writefileFunc, 0, 0);
}
if( rc==SQLITE_OK ){
rc = sqlite3_create_function(db, "lsmode", 1, SQLITE_UTF8, 0,
lsModeFunc, 0, 0);
}
if( rc==SQLITE_OK ){
|
| ︙ | ︙ | |||
3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 |
/* Column numbers */
#define COMPLETION_COLUMN_CANDIDATE 0 /* Suggested completion of the input */
#define COMPLETION_COLUMN_PREFIX 1 /* Prefix of the word to be completed */
#define COMPLETION_COLUMN_WHOLELINE 2 /* Entire line seen so far */
#define COMPLETION_COLUMN_PHASE 3 /* ePhase - used for debugging only */
rc = sqlite3_declare_vtab(db,
"CREATE TABLE x("
" candidate TEXT,"
" prefix TEXT HIDDEN,"
" wholeline TEXT HIDDEN,"
" phase INT HIDDEN" /* Used for debugging only */
")");
| > | 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 |
/* Column numbers */
#define COMPLETION_COLUMN_CANDIDATE 0 /* Suggested completion of the input */
#define COMPLETION_COLUMN_PREFIX 1 /* Prefix of the word to be completed */
#define COMPLETION_COLUMN_WHOLELINE 2 /* Entire line seen so far */
#define COMPLETION_COLUMN_PHASE 3 /* ePhase - used for debugging only */
sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
rc = sqlite3_declare_vtab(db,
"CREATE TABLE x("
" candidate TEXT,"
" prefix TEXT HIDDEN,"
" wholeline TEXT HIDDEN,"
" phase INT HIDDEN" /* Used for debugging only */
")");
|
| ︙ | ︙ | |||
3247 3248 3249 3250 3251 3252 3253 |
char *zSql = 0;
const char *zSep = "";
sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, &pS2, 0);
while( sqlite3_step(pS2)==SQLITE_ROW ){
const char *zDb = (const char*)sqlite3_column_text(pS2, 1);
zSql = sqlite3_mprintf(
"%z%s"
| | | 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 |
char *zSql = 0;
const char *zSep = "";
sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, &pS2, 0);
while( sqlite3_step(pS2)==SQLITE_ROW ){
const char *zDb = (const char*)sqlite3_column_text(pS2, 1);
zSql = sqlite3_mprintf(
"%z%s"
"SELECT name FROM \"%w\".sqlite_schema",
zSql, zSep, zDb
);
if( zSql==0 ) return SQLITE_NOMEM;
zSep = " UNION ";
}
sqlite3_finalize(pS2);
sqlite3_prepare_v2(pCur->db, zSql, -1, &pCur->pStmt, 0);
|
| ︙ | ︙ | |||
3271 3272 3273 3274 3275 3276 3277 |
char *zSql = 0;
const char *zSep = "";
sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, &pS2, 0);
while( sqlite3_step(pS2)==SQLITE_ROW ){
const char *zDb = (const char*)sqlite3_column_text(pS2, 1);
zSql = sqlite3_mprintf(
"%z%s"
| | | 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 |
char *zSql = 0;
const char *zSep = "";
sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, &pS2, 0);
while( sqlite3_step(pS2)==SQLITE_ROW ){
const char *zDb = (const char*)sqlite3_column_text(pS2, 1);
zSql = sqlite3_mprintf(
"%z%s"
"SELECT pti.name FROM \"%w\".sqlite_schema AS sm"
" JOIN pragma_table_info(sm.name,%Q) AS pti"
" WHERE sm.type='table'",
zSql, zSep, zDb, zDb
);
if( zSql==0 ) return SQLITE_NOMEM;
zSep = " UNION ";
}
|
| ︙ | ︙ | |||
4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 |
}
}
memtraceOut = 0;
return rc;
}
/************************* End ../ext/misc/memtrace.c ********************/
#ifdef SQLITE_HAVE_ZLIB
/************************* Begin ../ext/misc/zipfile.c ******************/
/*
** 2017-12-26
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 |
}
}
memtraceOut = 0;
return rc;
}
/************************* End ../ext/misc/memtrace.c ********************/
/************************* Begin ../ext/misc/uint.c ******************/
/*
** 2020-04-14
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
******************************************************************************
**
** This SQLite extension implements the UINT collating sequence.
**
** UINT works like BINARY for text, except that embedded strings
** of digits compare in numeric order.
**
** * Leading zeros are handled properly, in the sense that
** they do not mess of the maginitude comparison of embedded
** strings of digits. "x00123y" is equal to "x123y".
**
** * Only unsigned integers are recognized. Plus and minus
** signs are ignored. Decimal points and exponential notation
** are ignored.
**
** * Embedded integers can be of arbitrary length. Comparison
** is *not* limited integers that can be expressed as a
** 64-bit machine integer.
*/
/* #include "sqlite3ext.h" */
SQLITE_EXTENSION_INIT1
#include <assert.h>
#include <string.h>
#include <ctype.h>
/*
** Compare text in lexicographic order, except strings of digits
** compare in numeric order.
*/
static int uintCollFunc(
void *notUsed,
int nKey1, const void *pKey1,
int nKey2, const void *pKey2
){
const unsigned char *zA = (const unsigned char*)pKey1;
const unsigned char *zB = (const unsigned char*)pKey2;
int i=0, j=0, x;
(void)notUsed;
while( i<nKey1 && j<nKey2 ){
x = zA[i] - zB[j];
if( isdigit(zA[i]) ){
int k;
if( !isdigit(zB[j]) ) return x;
while( i<nKey1 && zA[i]=='0' ){ i++; }
while( j<nKey2 && zB[j]=='0' ){ j++; }
k = 0;
while( i+k<nKey1 && isdigit(zA[i+k])
&& j+k<nKey2 && isdigit(zB[j+k]) ){
k++;
}
if( i+k<nKey1 && isdigit(zA[i+k]) ){
return +1;
}else if( j+k<nKey2 && isdigit(zB[j+k]) ){
return -1;
}else{
x = memcmp(zA+i, zB+j, k);
if( x ) return x;
i += k;
j += k;
}
}else if( x ){
return x;
}else{
i++;
j++;
}
}
return (nKey1 - i) - (nKey2 - j);
}
#ifdef _WIN32
#endif
int sqlite3_uint_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
return sqlite3_create_collation(db, "uint", SQLITE_UTF8, 0, uintCollFunc);
}
/************************* End ../ext/misc/uint.c ********************/
#ifdef SQLITE_HAVE_ZLIB
/************************* Begin ../ext/misc/zipfile.c ******************/
/*
** 2017-12-26
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
|
| ︙ | ︙ | |||
4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 |
pNew->aBuffer = (u8*)&pNew[1];
if( zFile ){
pNew->zFile = (char*)&pNew->aBuffer[ZIPFILE_BUFFER_SIZE];
memcpy(pNew->zFile, zFile, nFile);
zipfileDequote(pNew->zFile);
}
}
*ppVtab = (sqlite3_vtab*)pNew;
return rc;
}
/*
** Free the ZipfileEntry structure indicated by the only argument.
*/
| > | 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 |
pNew->aBuffer = (u8*)&pNew[1];
if( zFile ){
pNew->zFile = (char*)&pNew->aBuffer[ZIPFILE_BUFFER_SIZE];
memcpy(pNew->zFile, zFile, nFile);
zipfileDequote(pNew->zFile);
}
}
sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY);
*ppVtab = (sqlite3_vtab*)pNew;
return rc;
}
/*
** Free the ZipfileEntry structure indicated by the only argument.
*/
|
| ︙ | ︙ | |||
5186 5187 5188 5189 5190 5191 5192 |
** case.
*/
static int zipfileDeflate(
const u8 *aIn, int nIn, /* Input */
u8 **ppOut, int *pnOut, /* Output */
char **pzErr /* OUT: Error message */
){
| > | > | > > > > > < < < < < < < | 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 |
** case.
*/
static int zipfileDeflate(
const u8 *aIn, int nIn, /* Input */
u8 **ppOut, int *pnOut, /* Output */
char **pzErr /* OUT: Error message */
){
int rc = SQLITE_OK;
sqlite3_int64 nAlloc;
z_stream str;
u8 *aOut;
memset(&str, 0, sizeof(str));
str.next_in = (Bytef*)aIn;
str.avail_in = nIn;
deflateInit2(&str, 9, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
nAlloc = deflateBound(&str, nIn);
aOut = (u8*)sqlite3_malloc64(nAlloc);
if( aOut==0 ){
rc = SQLITE_NOMEM;
}else{
int res;
str.next_out = aOut;
str.avail_out = nAlloc;
res = deflate(&str, Z_FINISH);
if( res==Z_STREAM_END ){
*ppOut = aOut;
*pnOut = (int)str.total_out;
}else{
sqlite3_free(aOut);
*pzErr = sqlite3_mprintf("zipfile: deflate() error");
rc = SQLITE_ERROR;
|
| ︙ | ︙ | |||
5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 |
if( pCons->iColumn!=ZIPFILE_F_COLUMN_IDX ) continue;
if( pCons->usable==0 ){
unusable = 1;
}else if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ ){
idx = i;
}
}
if( idx>=0 ){
pIdxInfo->aConstraintUsage[idx].argvIndex = 1;
pIdxInfo->aConstraintUsage[idx].omit = 1;
| > < | 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 |
if( pCons->iColumn!=ZIPFILE_F_COLUMN_IDX ) continue;
if( pCons->usable==0 ){
unusable = 1;
}else if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ ){
idx = i;
}
}
pIdxInfo->estimatedCost = 1000.0;
if( idx>=0 ){
pIdxInfo->aConstraintUsage[idx].argvIndex = 1;
pIdxInfo->aConstraintUsage[idx].omit = 1;
pIdxInfo->idxNum = 1;
}else if( unusable ){
return SQLITE_CONSTRAINT;
}
return SQLITE_OK;
}
|
| ︙ | ︙ | |||
5638 5639 5640 5641 5642 5643 5644 |
/*
** Both (const char*) arguments point to nul-terminated strings. Argument
** nB is the value of strlen(zB). This function returns 0 if the strings are
** identical, ignoring any trailing '/' character in either path. */
static int zipfileComparePath(const char *zA, const char *zB, int nB){
int nA = (int)strlen(zA);
| | | > > > > | 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 |
/*
** Both (const char*) arguments point to nul-terminated strings. Argument
** nB is the value of strlen(zB). This function returns 0 if the strings are
** identical, ignoring any trailing '/' character in either path. */
static int zipfileComparePath(const char *zA, const char *zB, int nB){
int nA = (int)strlen(zA);
if( nA>0 && zA[nA-1]=='/' ) nA--;
if( nB>0 && zB[nB-1]=='/' ) nB--;
if( nA==nB && memcmp(zA, zB, nA)==0 ) return 0;
return 1;
}
static int zipfileBegin(sqlite3_vtab *pVtab){
ZipfileTab *pTab = (ZipfileTab*)pVtab;
int rc = SQLITE_OK;
assert( pTab->pWriteFd==0 );
if( pTab->zFile==0 || pTab->zFile[0]==0 ){
pTab->base.zErrMsg = sqlite3_mprintf("zipfile: missing filename");
return SQLITE_ERROR;
}
/* Open a write fd on the file. Also load the entire central directory
** structure into memory. During the transaction any new file data is
** appended to the archive file, but the central directory is accumulated
** in main-memory until the transaction is committed. */
pTab->pWriteFd = fopen(pTab->zFile, "ab+");
if( pTab->pWriteFd==0 ){
|
| ︙ | ︙ | |||
5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 |
if( rc==SQLITE_OK ){
rc = zipfileGetMode(apVal[3], bIsDir, &mode, &pTab->base.zErrMsg);
}
if( rc==SQLITE_OK ){
zPath = (const char*)sqlite3_value_text(apVal[2]);
nPath = (int)strlen(zPath);
mTime = zipfileGetTime(apVal[4]);
}
if( rc==SQLITE_OK && bIsDir ){
/* For a directory, check that the last character in the path is a
** '/'. This appears to be required for compatibility with info-zip
** (the unzip command on unix). It does not create directories
** otherwise. */
| > | < > > | > > > | 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 6030 6031 6032 6033 6034 |
if( rc==SQLITE_OK ){
rc = zipfileGetMode(apVal[3], bIsDir, &mode, &pTab->base.zErrMsg);
}
if( rc==SQLITE_OK ){
zPath = (const char*)sqlite3_value_text(apVal[2]);
if( zPath==0 ) zPath = "";
nPath = (int)strlen(zPath);
mTime = zipfileGetTime(apVal[4]);
}
if( rc==SQLITE_OK && bIsDir ){
/* For a directory, check that the last character in the path is a
** '/'. This appears to be required for compatibility with info-zip
** (the unzip command on unix). It does not create directories
** otherwise. */
if( nPath<=0 || zPath[nPath-1]!='/' ){
zFree = sqlite3_mprintf("%s/", zPath);
zPath = (const char*)zFree;
if( zFree==0 ){
rc = SQLITE_NOMEM;
nPath = 0;
}else{
nPath = (int)strlen(zPath);
}
}
}
/* Check that we're not inserting a duplicate entry -OR- updating an
** entry with a path, thereby making it into a duplicate. */
if( (pOld==0 || bUpdate) && rc==SQLITE_OK ){
ZipfileEntry *p;
|
| ︙ | ︙ | |||
6229 6230 6231 6232 6233 6234 6235 |
/* Decode the "mtime" argument. */
e.mUnixTime = zipfileGetTime(pMtime);
/* If this is a directory entry, ensure that there is exactly one '/'
** at the end of the path. Or, if this is not a directory and the path
** ends in '/' it is an error. */
if( bIsDir==0 ){
| | | < > | 6413 6414 6415 6416 6417 6418 6419 6420 6421 6422 6423 6424 6425 6426 6427 6428 6429 6430 6431 6432 6433 6434 6435 6436 6437 6438 6439 |
/* Decode the "mtime" argument. */
e.mUnixTime = zipfileGetTime(pMtime);
/* If this is a directory entry, ensure that there is exactly one '/'
** at the end of the path. Or, if this is not a directory and the path
** ends in '/' it is an error. */
if( bIsDir==0 ){
if( nName>0 && zName[nName-1]=='/' ){
zErr = sqlite3_mprintf("non-directory name must not end with /");
rc = SQLITE_ERROR;
goto zipfile_step_out;
}
}else{
if( nName==0 || zName[nName-1]!='/' ){
zName = zFree = sqlite3_mprintf("%s/", zName);
if( zName==0 ){
rc = SQLITE_NOMEM;
goto zipfile_step_out;
}
nName = (int)strlen(zName);
}else{
while( nName>1 && zName[nName-2]=='/' ) nName--;
}
}
/* Assemble the ZipfileEntry object for the new zip archive entry */
e.cds.iVersionMadeBy = ZIPFILE_NEWENTRY_MADEBY;
|
| ︙ | ︙ | |||
6403 6404 6405 6406 6407 6408 6409 6410 6411 6412 6413 6414 6415 6416 | ** Utility functions sqlar_compress() and sqlar_uncompress(). Useful ** for working with sqlar archives and used by the shell tool's built-in ** sqlar support. */ /* #include "sqlite3ext.h" */ SQLITE_EXTENSION_INIT1 #include <zlib.h> /* ** Implementation of the "sqlar_compress(X)" SQL function. ** ** If the type of X is SQLITE_BLOB, and compressing that blob using ** zlib utility function compress() yields a smaller blob, return the ** compressed blob. Otherwise, return a copy of X. | > | 6587 6588 6589 6590 6591 6592 6593 6594 6595 6596 6597 6598 6599 6600 6601 | ** Utility functions sqlar_compress() and sqlar_uncompress(). Useful ** for working with sqlar archives and used by the shell tool's built-in ** sqlar support. */ /* #include "sqlite3ext.h" */ SQLITE_EXTENSION_INIT1 #include <zlib.h> #include <assert.h> /* ** Implementation of the "sqlar_compress(X)" SQL function. ** ** If the type of X is SQLITE_BLOB, and compressing that blob using ** zlib utility function compress() yields a smaller blob, return the ** compressed blob. Otherwise, return a copy of X. |
| ︙ | ︙ | |||
6497 6498 6499 6500 6501 6502 6503 |
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
| | > | > | | | 6682 6683 6684 6685 6686 6687 6688 6689 6690 6691 6692 6693 6694 6695 6696 6697 6698 6699 6700 6701 6702 6703 6704 6705 6706 6707 6708 6709 6710 6711 6712 6713 6714 6715 6716 6717 6718 6719 6720 6721 6722 6723 |
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
rc = sqlite3_create_function(db, "sqlar_compress", 1,
SQLITE_UTF8|SQLITE_INNOCUOUS, 0,
sqlarCompressFunc, 0, 0);
if( rc==SQLITE_OK ){
rc = sqlite3_create_function(db, "sqlar_uncompress", 2,
SQLITE_UTF8|SQLITE_INNOCUOUS, 0,
sqlarUncompressFunc, 0, 0);
}
return rc;
}
/************************* End ../ext/misc/sqlar.c ********************/
#endif
/************************* Begin ../ext/expert/sqlite3expert.h ******************/
/*
** 2017 April 07
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
*/
#if !defined(SQLITEEXPERT_H)
#define SQLITEEXPERT_H 1
/* #include "sqlite3.h" */
typedef struct sqlite3expert sqlite3expert;
/*
** Create a new sqlite3expert object.
**
|
| ︙ | ︙ | |||
6676 6677 6678 6679 6680 6681 6682 | /* ** Free an (sqlite3expert*) handle and all associated resources. There ** should be one call to this function for each successful call to ** sqlite3-expert_new(). */ void sqlite3_expert_destroy(sqlite3expert*); | | | 6863 6864 6865 6866 6867 6868 6869 6870 6871 6872 6873 6874 6875 6876 6877 | /* ** Free an (sqlite3expert*) handle and all associated resources. There ** should be one call to this function for each successful call to ** sqlite3-expert_new(). */ void sqlite3_expert_destroy(sqlite3expert*); #endif /* !defined(SQLITEEXPERT_H) */ /************************* End ../ext/expert/sqlite3expert.h ********************/ /************************* Begin ../ext/expert/sqlite3expert.c ******************/ /* ** 2017 April 09 ** ** The author disclaims copyright to this source code. In place of |
| ︙ | ︙ | |||
7810 7811 7812 7813 7814 7815 7816 |
"EXPLAIN QUERY PLAN %s", pStmt->zSql
);
while( rc==SQLITE_OK && sqlite3_step(pExplain)==SQLITE_ROW ){
/* int iId = sqlite3_column_int(pExplain, 0); */
/* int iParent = sqlite3_column_int(pExplain, 1); */
/* int iNotUsed = sqlite3_column_int(pExplain, 2); */
const char *zDetail = (const char*)sqlite3_column_text(pExplain, 3);
| | > > > | > | > | 7997 7998 7999 8000 8001 8002 8003 8004 8005 8006 8007 8008 8009 8010 8011 8012 8013 8014 8015 8016 8017 8018 8019 8020 8021 8022 8023 |
"EXPLAIN QUERY PLAN %s", pStmt->zSql
);
while( rc==SQLITE_OK && sqlite3_step(pExplain)==SQLITE_ROW ){
/* int iId = sqlite3_column_int(pExplain, 0); */
/* int iParent = sqlite3_column_int(pExplain, 1); */
/* int iNotUsed = sqlite3_column_int(pExplain, 2); */
const char *zDetail = (const char*)sqlite3_column_text(pExplain, 3);
int nDetail;
int i;
if( !zDetail ) continue;
nDetail = STRLEN(zDetail);
for(i=0; i<nDetail; i++){
const char *zIdx = 0;
if( i+13<nDetail && memcmp(&zDetail[i], " USING INDEX ", 13)==0 ){
zIdx = &zDetail[i+13];
}else if( i+22<nDetail
&& memcmp(&zDetail[i], " USING COVERING INDEX ", 22)==0
){
zIdx = &zDetail[i+22];
}
if( zIdx ){
const char *zSql;
int nIdx = 0;
while( zIdx[nIdx]!='\0' && (zIdx[nIdx]!=' ' || zIdx[nIdx+1]!='(') ){
nIdx++;
|
| ︙ | ︙ | |||
7900 7901 7902 7903 7904 7905 7906 |
char **pzErr
){
static const char *zInt = UNIQUE_TABLE_NAME;
static const char *zDrop = "DROP TABLE " UNIQUE_TABLE_NAME;
IdxTable *pTab = pWrite->pTab;
const char *zTab = pTab->zName;
const char *zSql =
| | | 8092 8093 8094 8095 8096 8097 8098 8099 8100 8101 8102 8103 8104 8105 8106 |
char **pzErr
){
static const char *zInt = UNIQUE_TABLE_NAME;
static const char *zDrop = "DROP TABLE " UNIQUE_TABLE_NAME;
IdxTable *pTab = pWrite->pTab;
const char *zTab = pTab->zName;
const char *zSql =
"SELECT 'CREATE TEMP' || substr(sql, 7) FROM sqlite_schema "
"WHERE tbl_name = %Q AND type IN ('table', 'trigger') "
"ORDER BY type;";
sqlite3_stmt *pSelect = 0;
int rc = SQLITE_OK;
char *zWrite = 0;
/* Create the table and its triggers in the temp schema */
|
| ︙ | ︙ | |||
8000 8001 8002 8003 8004 8005 8006 | /* For each table in the main db schema: ** ** 1) Add an entry to the p->pTable list, and ** 2) Create the equivalent virtual table in dbv. */ rc = idxPrepareStmt(p->db, &pSchema, pzErrmsg, | | | | | 8192 8193 8194 8195 8196 8197 8198 8199 8200 8201 8202 8203 8204 8205 8206 8207 8208 8209 8210 8211 |
/* For each table in the main db schema:
**
** 1) Add an entry to the p->pTable list, and
** 2) Create the equivalent virtual table in dbv.
*/
rc = idxPrepareStmt(p->db, &pSchema, pzErrmsg,
"SELECT type, name, sql, 1 FROM sqlite_schema "
"WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%%' "
" UNION ALL "
"SELECT type, name, sql, 2 FROM sqlite_schema "
"WHERE type = 'trigger'"
" AND tbl_name IN(SELECT name FROM sqlite_schema WHERE type = 'view') "
"ORDER BY 4, 1"
);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSchema) ){
const char *zType = (const char*)sqlite3_column_text(pSchema, 0);
const char *zName = (const char*)sqlite3_column_text(pSchema, 1);
const char *zSql = (const char*)sqlite3_column_text(pSchema, 2);
|
| ︙ | ︙ | |||
8175 8176 8177 8178 8179 8180 8181 |
}
}
static int idxLargestIndex(sqlite3 *db, int *pnMax, char **pzErr){
int rc = SQLITE_OK;
const char *zMax =
"SELECT max(i.seqno) FROM "
| | | 8367 8368 8369 8370 8371 8372 8373 8374 8375 8376 8377 8378 8379 8380 8381 |
}
}
static int idxLargestIndex(sqlite3 *db, int *pnMax, char **pzErr){
int rc = SQLITE_OK;
const char *zMax =
"SELECT max(i.seqno) FROM "
" sqlite_schema AS s, "
" pragma_index_list(s.name) AS l, "
" pragma_index_info(l.name) AS i "
"WHERE s.type = 'table'";
sqlite3_stmt *pMax = 0;
*pnMax = 0;
rc = idxPrepareStmt(db, &pMax, pzErr, zMax);
|
| ︙ | ︙ | |||
8328 8329 8330 8331 8332 8333 8334 |
i64 iPrev = -100000;
sqlite3_stmt *pAllIndex = 0;
sqlite3_stmt *pIndexXInfo = 0;
sqlite3_stmt *pWrite = 0;
const char *zAllIndex =
"SELECT s.rowid, s.name, l.name FROM "
| | | 8520 8521 8522 8523 8524 8525 8526 8527 8528 8529 8530 8531 8532 8533 8534 |
i64 iPrev = -100000;
sqlite3_stmt *pAllIndex = 0;
sqlite3_stmt *pIndexXInfo = 0;
sqlite3_stmt *pWrite = 0;
const char *zAllIndex =
"SELECT s.rowid, s.name, l.name FROM "
" sqlite_schema AS s, "
" pragma_index_list(s.name) AS l "
"WHERE s.type = 'table'";
const char *zIndexXInfo =
"SELECT name, coll FROM pragma_index_xinfo(?) WHERE key";
const char *zWrite = "INSERT INTO sqlite_stat1 VALUES(?, ?, ?)";
/* If iSample==0, no sqlite_stat1 data is required. */
|
| ︙ | ︙ | |||
8402 8403 8404 8405 8406 8407 8408 |
for(i=0; i<pCtx->nSlot; i++){
sqlite3_free(pCtx->aSlot[i].z);
}
sqlite3_free(pCtx);
if( rc==SQLITE_OK ){
| | | 8594 8595 8596 8597 8598 8599 8600 8601 8602 8603 8604 8605 8606 8607 8608 |
for(i=0; i<pCtx->nSlot; i++){
sqlite3_free(pCtx->aSlot[i].z);
}
sqlite3_free(pCtx);
if( rc==SQLITE_OK ){
rc = sqlite3_exec(p->dbm, "ANALYZE sqlite_schema", 0, 0, 0);
}
sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp."UNIQUE_TABLE_NAME,0,0,0);
return rc;
}
/*
|
| ︙ | ︙ | |||
8441 8442 8443 8444 8445 8446 8447 |
}
/* Copy the entire schema of database [db] into [dbm]. */
if( rc==SQLITE_OK ){
sqlite3_stmt *pSql;
rc = idxPrintfPrepareStmt(pNew->db, &pSql, pzErrmsg,
| | | 8633 8634 8635 8636 8637 8638 8639 8640 8641 8642 8643 8644 8645 8646 8647 |
}
/* Copy the entire schema of database [db] into [dbm]. */
if( rc==SQLITE_OK ){
sqlite3_stmt *pSql;
rc = idxPrintfPrepareStmt(pNew->db, &pSql, pzErrmsg,
"SELECT sql FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%%'"
" AND sql NOT LIKE 'CREATE VIRTUAL %%'"
);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
const char *zSql = (const char*)sqlite3_column_text(pSql, 0);
rc = sqlite3_exec(pNew->dbm, zSql, 0, 0, pzErrmsg);
}
idxFinalize(&rc, pSql);
|
| ︙ | ︙ | |||
8632 8633 8634 8635 8636 8637 8638 |
idxWriteFree(p->pWrite);
idxHashClear(&p->hIdx);
sqlite3_free(p->zCandidates);
sqlite3_free(p);
}
}
| | | 8824 8825 8826 8827 8828 8829 8830 8831 8832 8833 8834 8835 8836 8837 8838 |
idxWriteFree(p->pWrite);
idxHashClear(&p->hIdx);
sqlite3_free(p->zCandidates);
sqlite3_free(p);
}
}
#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */
/************************* End ../ext/expert/sqlite3expert.c ********************/
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
/************************* Begin ../ext/misc/dbdata.c ******************/
/*
** 2019-04-17
|
| ︙ | ︙ | |||
9506 9507 9508 9509 9510 9511 9512 | char *zName; /* Symbolic name for this session */ int nFilter; /* Number of xFilter rejection GLOB patterns */ char **azFilter; /* Array of xFilter rejection GLOB patterns */ sqlite3_session *p; /* The open session */ }; #endif | < < < < < < < < < < < < | 9698 9699 9700 9701 9702 9703 9704 9705 9706 9707 9708 9709 9710 9711 |
char *zName; /* Symbolic name for this session */
int nFilter; /* Number of xFilter rejection GLOB patterns */
char **azFilter; /* Array of xFilter rejection GLOB patterns */
sqlite3_session *p; /* The open session */
};
#endif
typedef struct ExpertInfo ExpertInfo;
struct ExpertInfo {
sqlite3expert *pExpert;
int bVerbose;
};
/* A single line in the EQP output */
|
| ︙ | ︙ | |||
9562 9563 9564 9565 9566 9567 9568 9569 9570 9571 9572 9573 9574 9575 9576 9577 9578 9579 9580 9581 9582 9583 9584 9585 9586 9587 9588 9589 9590 9591 | u8 doXdgOpen; /* Invoke start/open/xdg-open in output_reset() */ u8 nEqpLevel; /* Depth of the EQP output graph */ u8 eTraceType; /* SHELL_TRACE_* value for type of trace */ unsigned mEqpLines; /* Mask of veritical lines in the EQP output graph */ int outCount; /* Revert to stdout when reaching zero */ int cnt; /* Number of records displayed so far */ int lineno; /* Line number of last line read from in */ FILE *in; /* Read commands from this stream */ FILE *out; /* Write results here */ FILE *traceOut; /* Output for sqlite3_trace() */ int nErr; /* Number of errors seen */ int mode; /* An output mode setting */ int modePrior; /* Saved mode */ int cMode; /* temporary output mode for the current query */ int normalMode; /* Output mode before ".explain on" */ int writableSchema; /* True if PRAGMA writable_schema=ON */ int showHeader; /* True to show column names in List or Column mode */ int nCheck; /* Number of ".check" commands run */ unsigned nProgress; /* Number of progress callbacks encountered */ unsigned mxProgress; /* Maximum progress callbacks before failing */ unsigned flgProgress; /* Flags for the progress callback */ unsigned shellFlgs; /* Various flags */ sqlite3_int64 szMax; /* --maxsize argument to .open */ char *zDestTable; /* Name of destination table when MODE_Insert */ char *zTempFile; /* Temporary file that might need deleting */ char zTestcase[30]; /* Name of current test case */ char colSeparator[20]; /* Column separator character for several modes */ char rowSeparator[20]; /* Row separator character for MODE_Ascii */ char colSepPrior[20]; /* Saved column separator */ char rowSepPrior[20]; /* Saved row separator */ | > > | | > | 9742 9743 9744 9745 9746 9747 9748 9749 9750 9751 9752 9753 9754 9755 9756 9757 9758 9759 9760 9761 9762 9763 9764 9765 9766 9767 9768 9769 9770 9771 9772 9773 9774 9775 9776 9777 9778 9779 9780 9781 9782 9783 |
u8 doXdgOpen; /* Invoke start/open/xdg-open in output_reset() */
u8 nEqpLevel; /* Depth of the EQP output graph */
u8 eTraceType; /* SHELL_TRACE_* value for type of trace */
unsigned mEqpLines; /* Mask of veritical lines in the EQP output graph */
int outCount; /* Revert to stdout when reaching zero */
int cnt; /* Number of records displayed so far */
int lineno; /* Line number of last line read from in */
int openFlags; /* Additional flags to open. (SQLITE_OPEN_NOFOLLOW) */
FILE *in; /* Read commands from this stream */
FILE *out; /* Write results here */
FILE *traceOut; /* Output for sqlite3_trace() */
int nErr; /* Number of errors seen */
int mode; /* An output mode setting */
int modePrior; /* Saved mode */
int cMode; /* temporary output mode for the current query */
int normalMode; /* Output mode before ".explain on" */
int writableSchema; /* True if PRAGMA writable_schema=ON */
int showHeader; /* True to show column names in List or Column mode */
int nCheck; /* Number of ".check" commands run */
unsigned nProgress; /* Number of progress callbacks encountered */
unsigned mxProgress; /* Maximum progress callbacks before failing */
unsigned flgProgress; /* Flags for the progress callback */
unsigned shellFlgs; /* Various flags */
unsigned priorShFlgs; /* Saved copy of flags */
sqlite3_int64 szMax; /* --maxsize argument to .open */
char *zDestTable; /* Name of destination table when MODE_Insert */
char *zTempFile; /* Temporary file that might need deleting */
char zTestcase[30]; /* Name of current test case */
char colSeparator[20]; /* Column separator character for several modes */
char rowSeparator[20]; /* Row separator character for MODE_Ascii */
char colSepPrior[20]; /* Saved column separator */
char rowSepPrior[20]; /* Saved row separator */
int *colWidth; /* Requested width of each column in columnar modes */
int *actualWidth; /* Actual width of each column */
int nWidth; /* Number of slots in colWidth[] and actualWidth[] */
char nullValue[20]; /* The text to print when a NULL comes back from
** the database */
char outfile[FILENAME_MAX]; /* Filename for *out */
const char *zDbFilename; /* name of the database file */
char *zFreeOnClose; /* Filename to free when closing */
const char *zVfs; /* Name of VFS to use */
sqlite3_stmt *pStmt; /* Current statement if any. */
|
| ︙ | ︙ | |||
9647 9648 9649 9650 9651 9652 9653 9654 9655 9656 9657 9658 9659 9660 | #define SHFLG_Pagecache 0x00000001 /* The --pagecache option is used */ #define SHFLG_Lookaside 0x00000002 /* Lookaside memory is used */ #define SHFLG_Backslash 0x00000004 /* The --backslash option is used */ #define SHFLG_PreserveRowid 0x00000008 /* .dump preserves rowid values */ #define SHFLG_Newlines 0x00000010 /* .dump --newline flag */ #define SHFLG_CountChanges 0x00000020 /* .changes setting */ #define SHFLG_Echo 0x00000040 /* .echo or --echo setting */ /* ** Macros for testing and setting shellFlgs */ #define ShellHasFlag(P,X) (((P)->shellFlgs & (X))!=0) #define ShellSetFlag(P,X) ((P)->shellFlgs|=(X)) #define ShellClearFlag(P,X) ((P)->shellFlgs&=(~(X))) | > | 9830 9831 9832 9833 9834 9835 9836 9837 9838 9839 9840 9841 9842 9843 9844 | #define SHFLG_Pagecache 0x00000001 /* The --pagecache option is used */ #define SHFLG_Lookaside 0x00000002 /* Lookaside memory is used */ #define SHFLG_Backslash 0x00000004 /* The --backslash option is used */ #define SHFLG_PreserveRowid 0x00000008 /* .dump preserves rowid values */ #define SHFLG_Newlines 0x00000010 /* .dump --newline flag */ #define SHFLG_CountChanges 0x00000020 /* .changes setting */ #define SHFLG_Echo 0x00000040 /* .echo or --echo setting */ #define SHFLG_HeaderSet 0x00000080 /* .header has been used */ /* ** Macros for testing and setting shellFlgs */ #define ShellHasFlag(P,X) (((P)->shellFlgs & (X))!=0) #define ShellSetFlag(P,X) ((P)->shellFlgs|=(X)) #define ShellClearFlag(P,X) ((P)->shellFlgs&=(~(X))) |
| ︙ | ︙ | |||
9671 9672 9673 9674 9675 9676 9677 9678 9679 9680 9681 9682 9683 9684 9685 9686 9687 9688 9689 9690 9691 |
#define MODE_Quote 6 /* Quote values as for SQL */
#define MODE_Tcl 7 /* Generate ANSI-C or TCL quoted elements */
#define MODE_Csv 8 /* Quote strings, numbers are plain */
#define MODE_Explain 9 /* Like MODE_Column, but do not truncate data */
#define MODE_Ascii 10 /* Use ASCII unit and record separators (0x1F/0x1E) */
#define MODE_Pretty 11 /* Pretty-print schemas */
#define MODE_EQP 12 /* Converts EXPLAIN QUERY PLAN output into a graph */
static const char *modeDescr[] = {
"line",
"column",
"list",
"semi",
"html",
"insert",
"quote",
"tcl",
"csv",
"explain",
"ascii",
"prettyprint",
| > > > > | > > > > | 9855 9856 9857 9858 9859 9860 9861 9862 9863 9864 9865 9866 9867 9868 9869 9870 9871 9872 9873 9874 9875 9876 9877 9878 9879 9880 9881 9882 9883 9884 9885 9886 9887 9888 9889 9890 9891 |
#define MODE_Quote 6 /* Quote values as for SQL */
#define MODE_Tcl 7 /* Generate ANSI-C or TCL quoted elements */
#define MODE_Csv 8 /* Quote strings, numbers are plain */
#define MODE_Explain 9 /* Like MODE_Column, but do not truncate data */
#define MODE_Ascii 10 /* Use ASCII unit and record separators (0x1F/0x1E) */
#define MODE_Pretty 11 /* Pretty-print schemas */
#define MODE_EQP 12 /* Converts EXPLAIN QUERY PLAN output into a graph */
#define MODE_Json 13 /* Output JSON */
#define MODE_Markdown 14 /* Markdown formatting */
#define MODE_Table 15 /* MySQL-style table formatting */
#define MODE_Box 16 /* Unicode box-drawing characters */
static const char *modeDescr[] = {
"line",
"column",
"list",
"semi",
"html",
"insert",
"quote",
"tcl",
"csv",
"explain",
"ascii",
"prettyprint",
"eqp",
"json",
"markdown",
"table",
"box"
};
/*
** These are the column/row/line separators used by the various
** import/export modes.
*/
#define SEP_Column "|"
|
| ︙ | ︙ | |||
9797 9798 9799 9800 9801 9802 9803 |
f = fopen(zTempFile, bBin ? "wb" : "w");
if( f==0 ){
sqlite3_result_error(context, "edit() cannot open temp file", -1);
goto edit_func_end;
}
sz = sqlite3_value_bytes(argv[0]);
if( bBin ){
| | | | 9989 9990 9991 9992 9993 9994 9995 9996 9997 9998 9999 10000 10001 10002 10003 10004 10005 10006 10007 10008 |
f = fopen(zTempFile, bBin ? "wb" : "w");
if( f==0 ){
sqlite3_result_error(context, "edit() cannot open temp file", -1);
goto edit_func_end;
}
sz = sqlite3_value_bytes(argv[0]);
if( bBin ){
x = fwrite(sqlite3_value_blob(argv[0]), 1, (size_t)sz, f);
}else{
const char *z = (const char*)sqlite3_value_text(argv[0]);
/* Remember whether or not the value originally contained \r\n */
if( z && strstr(z,"\r\n")!=0 ) hasCRNL = 1;
x = fwrite(sqlite3_value_text(argv[0]), 1, (size_t)sz, f);
}
fclose(f);
f = 0;
if( x!=sz ){
sqlite3_result_error(context, "edit() could not write the whole file", -1);
goto edit_func_end;
}
|
| ︙ | ︙ | |||
9830 9831 9832 9833 9834 9835 9836 |
sqlite3_result_error(context,
"edit() cannot reopen temp file after edit", -1);
goto edit_func_end;
}
fseek(f, 0, SEEK_END);
sz = ftell(f);
rewind(f);
| | | | 10022 10023 10024 10025 10026 10027 10028 10029 10030 10031 10032 10033 10034 10035 10036 10037 10038 10039 10040 10041 |
sqlite3_result_error(context,
"edit() cannot reopen temp file after edit", -1);
goto edit_func_end;
}
fseek(f, 0, SEEK_END);
sz = ftell(f);
rewind(f);
p = sqlite3_malloc64( sz+1 );
if( p==0 ){
sqlite3_result_error_nomem(context);
goto edit_func_end;
}
x = fread(p, 1, (size_t)sz, f);
fclose(f);
f = 0;
if( x!=sz ){
sqlite3_result_error(context, "could not read back the whole file", -1);
goto edit_func_end;
}
if( bBin ){
|
| ︙ | ︙ | |||
9877 9878 9879 9880 9881 9882 9883 9884 9885 9886 9887 9888 9889 9890 9891 9892 9893 9894 9895 |
#endif /* SQLITE_NOHAVE_SYSTEM */
/*
** Save or restore the current output mode
*/
static void outputModePush(ShellState *p){
p->modePrior = p->mode;
memcpy(p->colSepPrior, p->colSeparator, sizeof(p->colSeparator));
memcpy(p->rowSepPrior, p->rowSeparator, sizeof(p->rowSeparator));
}
static void outputModePop(ShellState *p){
p->mode = p->modePrior;
memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator));
memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator));
}
/*
** Output the given string as a hex-encoded blob (eg. X'1234' )
*/
| > > | 10069 10070 10071 10072 10073 10074 10075 10076 10077 10078 10079 10080 10081 10082 10083 10084 10085 10086 10087 10088 10089 |
#endif /* SQLITE_NOHAVE_SYSTEM */
/*
** Save or restore the current output mode
*/
static void outputModePush(ShellState *p){
p->modePrior = p->mode;
p->priorShFlgs = p->shellFlgs;
memcpy(p->colSepPrior, p->colSeparator, sizeof(p->colSeparator));
memcpy(p->rowSepPrior, p->rowSeparator, sizeof(p->rowSeparator));
}
static void outputModePop(ShellState *p){
p->mode = p->modePrior;
p->shellFlgs = p->priorShFlgs;
memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator));
memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator));
}
/*
** Output the given string as a hex-encoded blob (eg. X'1234' )
*/
|
| ︙ | ︙ | |||
10044 10045 10046 10047 10048 10049 10050 10051 10052 10053 10054 10055 10056 10057 |
fputc('\\', out);
fputc('n', out);
}else if( c=='\r' ){
fputc('\\', out);
fputc('r', out);
}else if( !isprint(c&0xff) ){
raw_printf(out, "\\%03o", c&0xff);
}else{
fputc(c, out);
}
}
fputc('"', out);
}
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 10238 10239 10240 10241 10242 10243 10244 10245 10246 10247 10248 10249 10250 10251 10252 10253 10254 10255 10256 10257 10258 10259 10260 10261 10262 10263 10264 10265 10266 10267 10268 10269 10270 10271 10272 10273 10274 10275 10276 10277 10278 10279 10280 10281 10282 10283 10284 10285 |
fputc('\\', out);
fputc('n', out);
}else if( c=='\r' ){
fputc('\\', out);
fputc('r', out);
}else if( !isprint(c&0xff) ){
raw_printf(out, "\\%03o", c&0xff);
}else{
fputc(c, out);
}
}
fputc('"', out);
}
/*
** Output the given string as a quoted according to JSON quoting rules.
*/
static void output_json_string(FILE *out, const char *z, int n){
unsigned int c;
if( n<0 ) n = (int)strlen(z);
fputc('"', out);
while( n-- ){
c = *(z++);
if( c=='\\' || c=='"' ){
fputc('\\', out);
fputc(c, out);
}else if( c<=0x1f ){
fputc('\\', out);
if( c=='\b' ){
fputc('b', out);
}else if( c=='\f' ){
fputc('f', out);
}else if( c=='\n' ){
fputc('n', out);
}else if( c=='\r' ){
fputc('r', out);
}else if( c=='\t' ){
fputc('t', out);
}else{
raw_printf(out, "u%04x",c);
}
}else{
fputc(c, out);
}
}
fputc('"', out);
}
|
| ︙ | ︙ | |||
10307 10308 10309 10310 10311 10312 10313 |
static void eqp_render_level(ShellState *p, int iEqpId){
EQPGraphRow *pRow, *pNext;
int n = strlen30(p->sGraph.zPrefix);
char *z;
for(pRow = eqp_next_row(p, iEqpId, 0); pRow; pRow = pNext){
pNext = eqp_next_row(p, iEqpId, pRow);
z = pRow->zText;
| | > | 10535 10536 10537 10538 10539 10540 10541 10542 10543 10544 10545 10546 10547 10548 10549 10550 |
static void eqp_render_level(ShellState *p, int iEqpId){
EQPGraphRow *pRow, *pNext;
int n = strlen30(p->sGraph.zPrefix);
char *z;
for(pRow = eqp_next_row(p, iEqpId, 0); pRow; pRow = pNext){
pNext = eqp_next_row(p, iEqpId, pRow);
z = pRow->zText;
utf8_printf(p->out, "%s%s%s\n", p->sGraph.zPrefix,
pNext ? "|--" : "`--", z);
if( n<(int)sizeof(p->sGraph.zPrefix)-7 ){
memcpy(&p->sGraph.zPrefix[n], pNext ? "| " : " ", 4);
eqp_render_level(p, pRow->iEqpId);
p->sGraph.zPrefix[n] = 0;
}
}
}
|
| ︙ | ︙ | |||
10358 10359 10360 10361 10362 10363 10364 10365 10366 10367 10368 10369 10370 10371 10372 10373 10374 |
}
if( (p->flgProgress & SHELL_PROGRESS_QUIET)==0 ){
raw_printf(p->out, "Progress %u\n", p->nProgress);
}
return 0;
}
#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */
/*
** This is the callback routine that the shell
** invokes for each row of a query result.
*/
static int shell_callback(
void *pArg,
int nArg, /* Number of result columns */
char **azArg, /* Text of each result column */
char **azCol, /* Column names */
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | < | | < < < < < < < | < < < < | < < < < < < < < < < < < < | | | < < | < < | < < < < | < < | < < < | < < < | | | 10587 10588 10589 10590 10591 10592 10593 10594 10595 10596 10597 10598 10599 10600 10601 10602 10603 10604 10605 10606 10607 10608 10609 10610 10611 10612 10613 10614 10615 10616 10617 10618 10619 10620 10621 10622 10623 10624 10625 10626 10627 10628 10629 10630 10631 10632 10633 10634 10635 10636 10637 10638 10639 10640 10641 10642 10643 10644 10645 10646 10647 10648 10649 10650 10651 10652 10653 10654 10655 10656 10657 10658 10659 10660 10661 10662 10663 10664 10665 10666 10667 10668 10669 10670 10671 10672 10673 10674 10675 10676 10677 10678 10679 10680 10681 10682 10683 10684 10685 10686 10687 10688 10689 10690 10691 10692 10693 10694 10695 10696 |
}
if( (p->flgProgress & SHELL_PROGRESS_QUIET)==0 ){
raw_printf(p->out, "Progress %u\n", p->nProgress);
}
return 0;
}
#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */
/*
** Print N dashes
*/
static void print_dashes(FILE *out, int N){
const char zDash[] = "--------------------------------------------------";
const int nDash = sizeof(zDash) - 1;
while( N>nDash ){
fputs(zDash, out);
N -= nDash;
}
raw_printf(out, "%.*s", N, zDash);
}
/*
** Print a markdown or table-style row separator using ascii-art
*/
static void print_row_separator(
ShellState *p,
int nArg,
const char *zSep
){
int i;
if( nArg>0 ){
fputs(zSep, p->out);
print_dashes(p->out, p->actualWidth[0]+2);
for(i=1; i<nArg; i++){
fputs(zSep, p->out);
print_dashes(p->out, p->actualWidth[i]+2);
}
fputs(zSep, p->out);
}
fputs("\n", p->out);
}
/*
** This is the callback routine that the shell
** invokes for each row of a query result.
*/
static int shell_callback(
void *pArg,
int nArg, /* Number of result columns */
char **azArg, /* Text of each result column */
char **azCol, /* Column names */
int *aiType /* Column types. Might be NULL */
){
int i;
ShellState *p = (ShellState*)pArg;
if( azArg==0 ) return 0;
switch( p->cMode ){
case MODE_Line: {
int w = 5;
if( azArg==0 ) break;
for(i=0; i<nArg; i++){
int len = strlen30(azCol[i] ? azCol[i] : "");
if( len>w ) w = len;
}
if( p->cnt++>0 ) utf8_printf(p->out, "%s", p->rowSeparator);
for(i=0; i<nArg; i++){
utf8_printf(p->out,"%*s = %s%s", w, azCol[i],
azArg[i] ? azArg[i] : p->nullValue, p->rowSeparator);
}
break;
}
case MODE_Explain: {
static const int aExplainWidth[] = {4, 13, 4, 4, 4, 13, 2, 13};
if( nArg>ArraySize(aExplainWidth) ){
nArg = ArraySize(aExplainWidth);
}
if( p->cnt++==0 ){
for(i=0; i<nArg; i++){
int w = aExplainWidth[i];
utf8_width_print(p->out, w, azCol[i]);
fputs(i==nArg-1 ? "\n" : " ", p->out);
}
for(i=0; i<nArg; i++){
int w = aExplainWidth[i];
print_dashes(p->out, w);
fputs(i==nArg-1 ? "\n" : " ", p->out);
}
}
if( azArg==0 ) break;
for(i=0; i<nArg; i++){
int w = aExplainWidth[i];
if( azArg[i] && strlenChar(azArg[i])>w ){
w = strlenChar(azArg[i]);
}
if( i==1 && p->aiIndent && p->pStmt ){
if( p->iIndent<p->nIndent ){
utf8_printf(p->out, "%*.s", p->aiIndent[p->iIndent], "");
}
p->iIndent++;
}
utf8_width_print(p->out, w, azArg[i] ? azArg[i] : p->nullValue);
fputs(i==nArg-1 ? "\n" : " ", p->out);
}
break;
}
case MODE_Semi: { /* .schema and .fullschema output */
printSchemaLine(p->out, azArg[0], ";\n");
break;
}
|
| ︙ | ︙ | |||
10498 10499 10500 10501 10502 10503 10504 |
j--;
}
z[j++] = c;
}
while( j>0 && IsSpace(z[j-1]) ){ j--; }
z[j] = 0;
if( strlen30(z)>=79 ){
| | | 10720 10721 10722 10723 10724 10725 10726 10727 10728 10729 10730 10731 10732 10733 10734 |
j--;
}
z[j++] = c;
}
while( j>0 && IsSpace(z[j-1]) ){ j--; }
z[j] = 0;
if( strlen30(z)>=79 ){
for(i=j=0; (c = z[i])!=0; i++){ /* Copy from z[i] back to z[j] */
if( c==cEnd ){
cEnd = 0;
}else if( c=='"' || c=='\'' || c=='`' ){
cEnd = c;
}else if( c=='[' ){
cEnd = ']';
}else if( c=='-' && z[i+1]=='-' ){
|
| ︙ | ︙ | |||
10663 10664 10665 10666 10667 10668 10669 10670 |
output_quoted_string(p->out, azArg[i]);
}else{
output_quoted_escaped_string(p->out, azArg[i]);
}
}
raw_printf(p->out,");\n");
break;
}
| | | > > > > > | > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | 10885 10886 10887 10888 10889 10890 10891 10892 10893 10894 10895 10896 10897 10898 10899 10900 10901 10902 10903 10904 10905 10906 10907 10908 10909 10910 10911 10912 10913 10914 10915 10916 10917 10918 10919 10920 10921 10922 10923 10924 10925 10926 10927 10928 10929 10930 10931 10932 10933 10934 10935 10936 10937 10938 10939 10940 10941 10942 10943 10944 10945 10946 10947 10948 10949 10950 10951 10952 10953 |
output_quoted_string(p->out, azArg[i]);
}else{
output_quoted_escaped_string(p->out, azArg[i]);
}
}
raw_printf(p->out,");\n");
break;
}
case MODE_Json: {
if( azArg==0 ) break;
if( p->cnt==0 ){
fputs("[{", p->out);
}else{
fputs(",\n{", p->out);
}
p->cnt++;
for(i=0; i<nArg; i++){
output_json_string(p->out, azCol[i], -1);
putc(':', p->out);
if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){
fputs("null",p->out);
}else if( aiType && aiType[i]==SQLITE_FLOAT ){
char z[50];
double r = sqlite3_column_double(p->pStmt, i);
sqlite3_uint64 ur;
memcpy(&ur,&r,sizeof(r));
if( ur==0x7ff0000000000000LL ){
raw_printf(p->out, "1e999");
}else if( ur==0xfff0000000000000LL ){
raw_printf(p->out, "-1e999");
}else{
sqlite3_snprintf(50,z,"%!.20g", r);
raw_printf(p->out, "%s", z);
}
}else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){
const void *pBlob = sqlite3_column_blob(p->pStmt, i);
int nBlob = sqlite3_column_bytes(p->pStmt, i);
output_json_string(p->out, pBlob, nBlob);
}else if( aiType && aiType[i]==SQLITE_TEXT ){
output_json_string(p->out, azArg[i], -1);
}else{
utf8_printf(p->out,"%s", azArg[i]);
}
if( i<nArg-1 ){
putc(',', p->out);
}
}
putc('}', p->out);
break;
}
case MODE_Quote: {
if( azArg==0 ) break;
if( p->cnt==0 && p->showHeader ){
for(i=0; i<nArg; i++){
if( i>0 ) fputs(p->colSeparator, p->out);
output_quoted_string(p->out, azCol[i]);
}
fputs(p->rowSeparator, p->out);
}
p->cnt++;
for(i=0; i<nArg; i++){
if( i>0 ) fputs(p->colSeparator, p->out);
if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){
utf8_printf(p->out,"NULL");
}else if( aiType && aiType[i]==SQLITE_TEXT ){
output_quoted_string(p->out, azArg[i]);
}else if( aiType && aiType[i]==SQLITE_INTEGER ){
utf8_printf(p->out,"%s", azArg[i]);
}else if( aiType && aiType[i]==SQLITE_FLOAT ){
|
| ︙ | ︙ | |||
10697 10698 10699 10700 10701 10702 10703 |
output_hex_blob(p->out, pBlob, nBlob);
}else if( isNumber(azArg[i], 0) ){
utf8_printf(p->out,"%s", azArg[i]);
}else{
output_quoted_string(p->out, azArg[i]);
}
}
| | | 10961 10962 10963 10964 10965 10966 10967 10968 10969 10970 10971 10972 10973 10974 10975 |
output_hex_blob(p->out, pBlob, nBlob);
}else if( isNumber(azArg[i], 0) ){
utf8_printf(p->out,"%s", azArg[i]);
}else{
output_quoted_string(p->out, azArg[i]);
}
}
fputs(p->rowSeparator, p->out);
break;
}
case MODE_Ascii: {
if( p->cnt++==0 && p->showHeader ){
for(i=0; i<nArg; i++){
if( i>0 ) utf8_printf(p->out, "%s", p->colSeparator);
utf8_printf(p->out,"%s",azCol[i] ? azCol[i] : "");
|
| ︙ | ︙ | |||
10770 10771 10772 10773 10774 10775 10776 |
"CREATE TEMP TABLE [_shell$self](op,cmd,ans);\n"
"INSERT INTO [_shell$self](rowid,op,cmd)\n"
" VALUES(coalesce((SELECT (max(tno)+100)/10 FROM selftest),10),\n"
" 'memo','Tests generated by --init');\n"
"INSERT INTO [_shell$self]\n"
" SELECT 'run',\n"
" 'SELECT hex(sha3_query(''SELECT type,name,tbl_name,sql "
| | | | | 11034 11035 11036 11037 11038 11039 11040 11041 11042 11043 11044 11045 11046 11047 11048 11049 11050 11051 11052 11053 11054 11055 11056 11057 |
"CREATE TEMP TABLE [_shell$self](op,cmd,ans);\n"
"INSERT INTO [_shell$self](rowid,op,cmd)\n"
" VALUES(coalesce((SELECT (max(tno)+100)/10 FROM selftest),10),\n"
" 'memo','Tests generated by --init');\n"
"INSERT INTO [_shell$self]\n"
" SELECT 'run',\n"
" 'SELECT hex(sha3_query(''SELECT type,name,tbl_name,sql "
"FROM sqlite_schema ORDER BY 2'',224))',\n"
" hex(sha3_query('SELECT type,name,tbl_name,sql "
"FROM sqlite_schema ORDER BY 2',224));\n"
"INSERT INTO [_shell$self]\n"
" SELECT 'run',"
" 'SELECT hex(sha3_query(''SELECT * FROM \"' ||"
" printf('%w',name) || '\" NOT INDEXED'',224))',\n"
" hex(sha3_query(printf('SELECT * FROM \"%w\" NOT INDEXED',name),224))\n"
" FROM (\n"
" SELECT name FROM sqlite_schema\n"
" WHERE type='table'\n"
" AND name<>'selftest'\n"
" AND coalesce(rootpage,0)>0\n"
" )\n"
" ORDER BY name;\n"
"INSERT INTO [_shell$self]\n"
" VALUES('run','PRAGMA integrity_check','ok');\n"
|
| ︙ | ︙ | |||
10842 10843 10844 10845 10846 10847 10848 | ** If the number of columns is 1 and that column contains text "--" ** then write the semicolon on a separate line. That way, if a ** "--" comment occurs at the end of the statement, the comment ** won't consume the semicolon terminator. */ static int run_table_dump_query( ShellState *p, /* Query context */ | | < < < < < | 11106 11107 11108 11109 11110 11111 11112 11113 11114 11115 11116 11117 11118 11119 11120 11121 11122 11123 11124 11125 11126 11127 11128 11129 11130 11131 11132 11133 11134 11135 11136 |
** If the number of columns is 1 and that column contains text "--"
** then write the semicolon on a separate line. That way, if a
** "--" comment occurs at the end of the statement, the comment
** won't consume the semicolon terminator.
*/
static int run_table_dump_query(
ShellState *p, /* Query context */
const char *zSelect /* SELECT statement to extract content */
){
sqlite3_stmt *pSelect;
int rc;
int nResult;
int i;
const char *z;
rc = sqlite3_prepare_v2(p->db, zSelect, -1, &pSelect, 0);
if( rc!=SQLITE_OK || !pSelect ){
utf8_printf(p->out, "/**** ERROR: (%d) %s *****/\n", rc,
sqlite3_errmsg(p->db));
if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++;
return rc;
}
rc = sqlite3_step(pSelect);
nResult = sqlite3_column_count(pSelect);
while( rc==SQLITE_ROW ){
z = (const char*)sqlite3_column_text(pSelect, 0);
utf8_printf(p->out, "%s", z);
for(i=1; i<nResult; i++){
utf8_printf(p->out, ",%s", sqlite3_column_text(pSelect, i));
}
if( z==0 ) z = "";
while( z[0] && (z[0]!='-' || z[1]!='-') ) z++;
|
| ︙ | ︙ | |||
11077 11078 11079 11080 11081 11082 11083 |
raw_printf(pArg->out, "Fullscan Steps: %d\n", iCur);
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset);
raw_printf(pArg->out, "Sort Operations: %d\n", iCur);
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset);
raw_printf(pArg->out, "Autoindex Inserts: %d\n", iCur);
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
raw_printf(pArg->out, "Virtual Machine Steps: %d\n", iCur);
| | | 11336 11337 11338 11339 11340 11341 11342 11343 11344 11345 11346 11347 11348 11349 11350 |
raw_printf(pArg->out, "Fullscan Steps: %d\n", iCur);
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset);
raw_printf(pArg->out, "Sort Operations: %d\n", iCur);
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset);
raw_printf(pArg->out, "Autoindex Inserts: %d\n", iCur);
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
raw_printf(pArg->out, "Virtual Machine Steps: %d\n", iCur);
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_REPREPARE,bReset);
raw_printf(pArg->out, "Reprepare operations: %d\n", iCur);
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_RUN, bReset);
raw_printf(pArg->out, "Number of times run: %d\n", iCur);
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_MEMUSED, bReset);
raw_printf(pArg->out, "Memory used by prepared stmt: %d\n", iCur);
}
|
| ︙ | ︙ | |||
11297 11298 11299 11300 11301 11302 11303 11304 11305 11306 11307 11308 11309 11310 11311 11312 11313 11314 11315 11316 11317 11318 11319 11320 11321 11322 |
sqlite3WhereTrace = savedWhereTrace;
#endif
}
/* Create the TEMP table used to store parameter bindings */
static void bind_table_init(ShellState *p){
int wrSchema = 0;
sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &wrSchema);
sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0);
sqlite3_exec(p->db,
"CREATE TABLE IF NOT EXISTS temp.sqlite_parameters(\n"
" key TEXT PRIMARY KEY,\n"
" value ANY\n"
") WITHOUT ROWID;",
0, 0, 0);
sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, wrSchema, 0);
}
/*
** Bind parameters on a prepared statement.
**
** Parameter bindings are taken from a TEMP table of the form:
**
** CREATE TEMP TABLE sqlite_parameters(key TEXT PRIMARY KEY, value)
** WITHOUT ROWID;
**
| > > > > | | | | 11556 11557 11558 11559 11560 11561 11562 11563 11564 11565 11566 11567 11568 11569 11570 11571 11572 11573 11574 11575 11576 11577 11578 11579 11580 11581 11582 11583 11584 11585 11586 11587 11588 11589 11590 11591 11592 11593 11594 11595 |
sqlite3WhereTrace = savedWhereTrace;
#endif
}
/* Create the TEMP table used to store parameter bindings */
static void bind_table_init(ShellState *p){
int wrSchema = 0;
int defensiveMode = 0;
sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, -1, &defensiveMode);
sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &wrSchema);
sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0);
sqlite3_exec(p->db,
"CREATE TABLE IF NOT EXISTS temp.sqlite_parameters(\n"
" key TEXT PRIMARY KEY,\n"
" value ANY\n"
") WITHOUT ROWID;",
0, 0, 0);
sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, wrSchema, 0);
sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, defensiveMode, 0);
}
/*
** Bind parameters on a prepared statement.
**
** Parameter bindings are taken from a TEMP table of the form:
**
** CREATE TEMP TABLE sqlite_parameters(key TEXT PRIMARY KEY, value)
** WITHOUT ROWID;
**
** No bindings occur if this table does not exist. The name of the table
** begins with "sqlite_" so that it will not collide with ordinary application
** tables. The table must be in the TEMP schema.
*/
static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){
int nVar;
int i;
int rc;
sqlite3_stmt *pQ = 0;
|
| ︙ | ︙ | |||
11353 11354 11355 11356 11357 11358 11359 11360 11361 11362 11363 11364 11365 11366 11367 11368 11369 11370 11371 11372 11373 11374 11375 |
}else{
sqlite3_bind_null(pStmt, i);
}
sqlite3_reset(pQ);
}
sqlite3_finalize(pQ);
}
/*
** Run a prepared statement
*/
static void exec_prepared_stmt(
ShellState *pArg, /* Pointer to ShellState */
sqlite3_stmt *pStmt /* Statment to run */
){
int rc;
/* perform the first step. this will tell us if we
** have a result set or not and how wide it is.
*/
rc = sqlite3_step(pStmt);
/* if we have a result set... */
if( SQLITE_ROW == rc ){
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 11616 11617 11618 11619 11620 11621 11622 11623 11624 11625 11626 11627 11628 11629 11630 11631 11632 11633 11634 11635 11636 11637 11638 11639 11640 11641 11642 11643 11644 11645 11646 11647 11648 11649 11650 11651 11652 11653 11654 11655 11656 11657 11658 11659 11660 11661 11662 11663 11664 11665 11666 11667 11668 11669 11670 11671 11672 11673 11674 11675 11676 11677 11678 11679 11680 11681 11682 11683 11684 11685 11686 11687 11688 11689 11690 11691 11692 11693 11694 11695 11696 11697 11698 11699 11700 11701 11702 11703 11704 11705 11706 11707 11708 11709 11710 11711 11712 11713 11714 11715 11716 11717 11718 11719 11720 11721 11722 11723 11724 11725 11726 11727 11728 11729 11730 11731 11732 11733 11734 11735 11736 11737 11738 11739 11740 11741 11742 11743 11744 11745 11746 11747 11748 11749 11750 11751 11752 11753 11754 11755 11756 11757 11758 11759 11760 11761 11762 11763 11764 11765 11766 11767 11768 11769 11770 11771 11772 11773 11774 11775 11776 11777 11778 11779 11780 11781 11782 11783 11784 11785 11786 11787 11788 11789 11790 11791 11792 11793 11794 11795 11796 11797 11798 11799 11800 11801 11802 11803 11804 11805 11806 11807 11808 11809 11810 11811 11812 11813 11814 11815 11816 11817 11818 11819 11820 11821 11822 11823 11824 11825 11826 11827 11828 11829 11830 11831 11832 11833 11834 11835 11836 11837 11838 11839 11840 11841 11842 11843 11844 11845 11846 11847 11848 11849 11850 11851 11852 11853 11854 11855 11856 11857 11858 |
}else{
sqlite3_bind_null(pStmt, i);
}
sqlite3_reset(pQ);
}
sqlite3_finalize(pQ);
}
/*
** UTF8 box-drawing characters. Imagine box lines like this:
**
** 1
** |
** 4 --+-- 2
** |
** 3
**
** Each box characters has between 2 and 4 of the lines leading from
** the center. The characters are here identified by the numbers of
** their corresponding lines.
*/
#define BOX_24 "\342\224\200" /* U+2500 --- */
#define BOX_13 "\342\224\202" /* U+2502 | */
#define BOX_23 "\342\224\214" /* U+250c ,- */
#define BOX_34 "\342\224\220" /* U+2510 -, */
#define BOX_12 "\342\224\224" /* U+2514 '- */
#define BOX_14 "\342\224\230" /* U+2518 -' */
#define BOX_123 "\342\224\234" /* U+251c |- */
#define BOX_134 "\342\224\244" /* U+2524 -| */
#define BOX_234 "\342\224\254" /* U+252c -,- */
#define BOX_124 "\342\224\264" /* U+2534 -'- */
#define BOX_1234 "\342\224\274" /* U+253c -|- */
/* Draw horizontal line N characters long using unicode box
** characters
*/
static void print_box_line(FILE *out, int N){
const char zDash[] =
BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24
BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24;
const int nDash = sizeof(zDash) - 1;
N *= 3;
while( N>nDash ){
utf8_printf(out, zDash);
N -= nDash;
}
utf8_printf(out, "%.*s", N, zDash);
}
/*
** Draw a horizontal separator for a MODE_Box table.
*/
static void print_box_row_separator(
ShellState *p,
int nArg,
const char *zSep1,
const char *zSep2,
const char *zSep3
){
int i;
if( nArg>0 ){
utf8_printf(p->out, "%s", zSep1);
print_box_line(p->out, p->actualWidth[0]+2);
for(i=1; i<nArg; i++){
utf8_printf(p->out, "%s", zSep2);
print_box_line(p->out, p->actualWidth[i]+2);
}
utf8_printf(p->out, "%s", zSep3);
}
fputs("\n", p->out);
}
/*
** Run a prepared statement and output the result in one of the
** table-oriented formats: MODE_Column, MODE_Markdown, MODE_Table,
** or MODE_Box.
**
** This is different from ordinary exec_prepared_stmt() in that
** it has to run the entire query and gather the results into memory
** first, in order to determine column widths, before providing
** any output.
*/
static void exec_prepared_stmt_columnar(
ShellState *p, /* Pointer to ShellState */
sqlite3_stmt *pStmt /* Statment to run */
){
int nRow = 0;
int nColumn = 0;
char **azData = 0;
char *zMsg = 0;
const char *z;
int rc;
int i, j, nTotal, w, n;
const char *colSep = 0;
const char *rowSep = 0;
rc = sqlite3_get_table(p->db, sqlite3_sql(pStmt),
&azData, &nRow, &nColumn, &zMsg);
if( rc ){
utf8_printf(p->out, "ERROR: %s\n", zMsg);
sqlite3_free(zMsg);
sqlite3_free_table(azData);
return;
}
if( nRow==0 || nColumn==0 ) goto columnar_end;
if( nColumn>p->nWidth ){
p->colWidth = realloc(p->colWidth, nColumn*2*sizeof(int));
if( p->colWidth==0 ) shell_out_of_memory();
for(i=p->nWidth; i<nColumn; i++) p->colWidth[i] = 0;
p->nWidth = nColumn;
p->actualWidth = &p->colWidth[nColumn];
}
memset(p->actualWidth, 0, nColumn*sizeof(int));
for(i=0; i<nColumn; i++){
w = p->colWidth[i];
if( w<0 ) w = -w;
p->actualWidth[i] = w;
}
nTotal = nColumn*(nRow+1);
for(i=0; i<nTotal; i++){
z = azData[i];
if( z==0 ) z = p->nullValue;
n = strlenChar(z);
j = i%nColumn;
if( n>p->actualWidth[j] ) p->actualWidth[j] = n;
}
if( seenInterrupt ) goto columnar_end;
switch( p->cMode ){
case MODE_Column: {
colSep = " ";
rowSep = "\n";
if( p->showHeader ){
for(i=0; i<nColumn; i++){
w = p->actualWidth[i];
if( p->colWidth[i]<0 ) w = -w;
utf8_width_print(p->out, w, azData[i]);
fputs(i==nColumn-1?"\n":" ", p->out);
}
for(i=0; i<nColumn; i++){
print_dashes(p->out, p->actualWidth[i]);
fputs(i==nColumn-1?"\n":" ", p->out);
}
}
break;
}
case MODE_Table: {
colSep = " | ";
rowSep = " |\n";
print_row_separator(p, nColumn, "+");
fputs("| ", p->out);
for(i=0; i<nColumn; i++){
w = p->actualWidth[i];
n = strlenChar(azData[i]);
utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, "");
fputs(i==nColumn-1?" |\n":" | ", p->out);
}
print_row_separator(p, nColumn, "+");
break;
}
case MODE_Markdown: {
colSep = " | ";
rowSep = " |\n";
fputs("| ", p->out);
for(i=0; i<nColumn; i++){
w = p->actualWidth[i];
n = strlenChar(azData[i]);
utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, "");
fputs(i==nColumn-1?" |\n":" | ", p->out);
}
print_row_separator(p, nColumn, "|");
break;
}
case MODE_Box: {
colSep = " " BOX_13 " ";
rowSep = " " BOX_13 "\n";
print_box_row_separator(p, nColumn, BOX_23, BOX_234, BOX_34);
utf8_printf(p->out, BOX_13 " ");
for(i=0; i<nColumn; i++){
w = p->actualWidth[i];
n = strlenChar(azData[i]);
utf8_printf(p->out, "%*s%s%*s%s",
(w-n)/2, "", azData[i], (w-n+1)/2, "",
i==nColumn-1?" "BOX_13"\n":" "BOX_13" ");
}
print_box_row_separator(p, nColumn, BOX_123, BOX_1234, BOX_134);
break;
}
}
for(i=nColumn, j=0; i<nTotal; i++, j++){
if( j==0 && p->cMode!=MODE_Column ){
utf8_printf(p->out, "%s", p->cMode==MODE_Box?BOX_13" ":"| ");
}
z = azData[i];
if( z==0 ) z = p->nullValue;
w = p->actualWidth[j];
if( p->colWidth[j]<0 ) w = -w;
utf8_width_print(p->out, w, z);
if( j==nColumn-1 ){
utf8_printf(p->out, "%s", rowSep);
j = -1;
if( seenInterrupt ) goto columnar_end;
}else{
utf8_printf(p->out, "%s", colSep);
}
}
if( p->cMode==MODE_Table ){
print_row_separator(p, nColumn, "+");
}else if( p->cMode==MODE_Box ){
print_box_row_separator(p, nColumn, BOX_12, BOX_124, BOX_14);
}
columnar_end:
if( seenInterrupt ){
utf8_printf(p->out, "Interrupt\n");
}
sqlite3_free_table(azData);
}
/*
** Run a prepared statement
*/
static void exec_prepared_stmt(
ShellState *pArg, /* Pointer to ShellState */
sqlite3_stmt *pStmt /* Statment to run */
){
int rc;
if( pArg->cMode==MODE_Column
|| pArg->cMode==MODE_Table
|| pArg->cMode==MODE_Box
|| pArg->cMode==MODE_Markdown
){
exec_prepared_stmt_columnar(pArg, pStmt);
return;
}
/* perform the first step. this will tell us if we
** have a result set or not and how wide it is.
*/
rc = sqlite3_step(pStmt);
/* if we have a result set... */
if( SQLITE_ROW == rc ){
|
| ︙ | ︙ | |||
11410 11411 11412 11413 11414 11415 11416 11417 11418 11419 11420 11421 11422 11423 |
rc = SQLITE_ABORT;
}else{
rc = sqlite3_step(pStmt);
}
}
} while( SQLITE_ROW == rc );
sqlite3_free(pData);
}
}
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
/*
** This function is called to process SQL if the previous shell command
| > > > | 11893 11894 11895 11896 11897 11898 11899 11900 11901 11902 11903 11904 11905 11906 11907 11908 11909 |
rc = SQLITE_ABORT;
}else{
rc = sqlite3_step(pStmt);
}
}
} while( SQLITE_ROW == rc );
sqlite3_free(pData);
if( pArg->cMode==MODE_Json ){
fputs("]\n", pArg->out);
}
}
}
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
/*
** This function is called to process SQL if the previous shell command
|
| ︙ | ︙ | |||
11622 11623 11624 11625 11626 11627 11628 11629 11630 11631 11632 11633 11634 11635 |
zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zStmtSql);
rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
if( rc==SQLITE_OK ){
while( sqlite3_step(pExplain)==SQLITE_ROW ){
const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3);
int iEqpId = sqlite3_column_int(pExplain, 0);
int iParentId = sqlite3_column_int(pExplain, 1);
if( zEQPLine[0]=='-' ) eqp_render(pArg);
eqp_append(pArg, iEqpId, iParentId, zEQPLine);
}
eqp_render(pArg);
}
sqlite3_finalize(pExplain);
sqlite3_free(zEQP);
| > | 12108 12109 12110 12111 12112 12113 12114 12115 12116 12117 12118 12119 12120 12121 12122 |
zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zStmtSql);
rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
if( rc==SQLITE_OK ){
while( sqlite3_step(pExplain)==SQLITE_ROW ){
const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3);
int iEqpId = sqlite3_column_int(pExplain, 0);
int iParentId = sqlite3_column_int(pExplain, 1);
if( zEQPLine==0 ) zEQPLine = "";
if( zEQPLine[0]=='-' ) eqp_render(pArg);
eqp_append(pArg, iEqpId, iParentId, zEQPLine);
}
eqp_render(pArg);
}
sqlite3_finalize(pExplain);
sqlite3_free(zEQP);
|
| ︙ | ︙ | |||
11859 11860 11861 11862 11863 11864 11865 |
zTable = azArg[0];
zType = azArg[1];
zSql = azArg[2];
if( strcmp(zTable, "sqlite_sequence")==0 ){
raw_printf(p->out, "DELETE FROM sqlite_sequence;\n");
}else if( sqlite3_strglob("sqlite_stat?", zTable)==0 ){
| | | | 12346 12347 12348 12349 12350 12351 12352 12353 12354 12355 12356 12357 12358 12359 12360 12361 12362 12363 12364 12365 12366 12367 12368 12369 12370 |
zTable = azArg[0];
zType = azArg[1];
zSql = azArg[2];
if( strcmp(zTable, "sqlite_sequence")==0 ){
raw_printf(p->out, "DELETE FROM sqlite_sequence;\n");
}else if( sqlite3_strglob("sqlite_stat?", zTable)==0 ){
raw_printf(p->out, "ANALYZE sqlite_schema;\n");
}else if( strncmp(zTable, "sqlite_", 7)==0 ){
return 0;
}else if( strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){
char *zIns;
if( !p->writableSchema ){
raw_printf(p->out, "PRAGMA writable_schema=ON;\n");
p->writableSchema = 1;
}
zIns = sqlite3_mprintf(
"INSERT INTO sqlite_schema(type,name,tbl_name,rootpage,sql)"
"VALUES('table','%q','%q',0,'%q');",
zTable, zTable, zSql);
utf8_printf(p->out, "%s\n", zIns);
sqlite3_free(zIns);
return 0;
}else{
printSchemaLine(p->out, zSql, ";\n");
|
| ︙ | ︙ | |||
12000 12001 12002 12003 12004 12005 12006 |
** start of the description of what that command does.
*/
static const char *(azHelp[]) = {
#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE)
".archive ... Manage SQL archives",
" Each command must have exactly one of the following options:",
" -c, --create Create a new archive",
| | | | | | | | | | | > | | > | < < | > | > > > > > > > > > > > > | > | | | | > | | > | > | | | | | | > > > | > | > > > > | > > > > > | 12487 12488 12489 12490 12491 12492 12493 12494 12495 12496 12497 12498 12499 12500 12501 12502 12503 12504 12505 12506 12507 12508 12509 12510 12511 12512 12513 12514 12515 12516 12517 12518 12519 12520 12521 12522 12523 12524 12525 12526 12527 12528 12529 12530 12531 12532 12533 12534 12535 12536 12537 12538 12539 12540 12541 12542 12543 12544 12545 12546 12547 12548 12549 12550 12551 12552 12553 12554 12555 12556 12557 12558 12559 12560 12561 12562 12563 12564 12565 12566 12567 12568 12569 12570 12571 12572 12573 12574 12575 12576 12577 12578 12579 12580 12581 12582 12583 12584 12585 12586 12587 12588 12589 12590 12591 12592 12593 12594 12595 12596 12597 12598 12599 12600 12601 12602 12603 12604 12605 12606 12607 12608 12609 12610 12611 12612 12613 12614 12615 12616 12617 12618 12619 12620 12621 12622 12623 12624 12625 12626 12627 12628 12629 12630 12631 12632 12633 12634 12635 12636 12637 12638 12639 12640 12641 12642 12643 12644 12645 12646 12647 12648 12649 12650 12651 12652 12653 12654 12655 |
** start of the description of what that command does.
*/
static const char *(azHelp[]) = {
#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE)
".archive ... Manage SQL archives",
" Each command must have exactly one of the following options:",
" -c, --create Create a new archive",
" -u, --update Add or update files with changed mtime",
" -i, --insert Like -u but always add even if unchanged",
" -t, --list List contents of archive",
" -x, --extract Extract files from archive",
" Optional arguments:",
" -v, --verbose Print each filename as it is processed",
" -f FILE, --file FILE Use archive FILE (default is current db)",
" -a FILE, --append FILE Open FILE using the apndvfs VFS",
" -C DIR, --directory DIR Read/extract files from directory DIR",
" -n, --dryrun Show the SQL that would have occurred",
" Examples:",
" .ar -cf ARCHIVE foo bar # Create ARCHIVE from files foo and bar",
" .ar -tf ARCHIVE # List members of ARCHIVE",
" .ar -xvf ARCHIVE # Verbosely extract files from ARCHIVE",
" See also:",
" http://sqlite.org/cli.html#sqlar_archive_support",
#endif
#ifndef SQLITE_OMIT_AUTHORIZATION
".auth ON|OFF Show authorizer callbacks",
#endif
".backup ?DB? FILE Backup DB (default \"main\") to FILE",
" --append Use the appendvfs",
" --async Write to FILE without journal and fsync()",
".bail on|off Stop after hitting an error. Default OFF",
".binary on|off Turn binary output on or off. Default OFF",
".cd DIRECTORY Change the working directory to DIRECTORY",
".changes on|off Show number of rows changed by SQL",
".check GLOB Fail if output since .testcase does not match",
".clone NEWDB Clone data into NEWDB from the existing database",
".databases List names and files of attached databases",
".dbconfig ?op? ?val? List or change sqlite3_db_config() options",
".dbinfo ?DB? Show status information about the database",
".dump ?TABLE? Render database content as SQL",
" Options:",
" --preserve-rowids Include ROWID values in the output",
" --newlines Allow unescaped newline characters in output",
" TABLE is a LIKE pattern for the tables to dump",
" Additional LIKE patterns can be given in subsequent arguments",
".echo on|off Turn command echo on or off",
".eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN",
" Other Modes:",
#ifdef SQLITE_DEBUG
" test Show raw EXPLAIN QUERY PLAN output",
" trace Like \"full\" but enable \"PRAGMA vdbe_trace\"",
#endif
" trigger Like \"full\" but also show trigger bytecode",
".excel Display the output of next command in spreadsheet",
" --bom Put a UTF8 byte-order mark on intermediate file",
".exit ?CODE? Exit this program with return-code CODE",
".expert EXPERIMENTAL. Suggest indexes for queries",
".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto",
".filectrl CMD ... Run various sqlite3_file_control() operations",
" --schema SCHEMA Use SCHEMA instead of \"main\"",
" --help Show CMD details",
".fullschema ?--indent? Show schema and the content of sqlite_stat tables",
".headers on|off Turn display of headers on or off",
".help ?-all? ?PATTERN? Show help text for PATTERN",
".import FILE TABLE Import data from FILE into TABLE",
" Options:",
" --ascii Use \\037 and \\036 as column and row separators",
" --csv Use , and \\n as column and row separators",
" --skip N Skip the first N rows of input",
" -v \"Verbose\" - increase auxiliary output",
" Notes:",
" * If TABLE does not exist, it is created. The first row of input",
" determines the column names.",
" * If neither --csv or --ascii are used, the input mode is derived",
" from the \".mode\" output mode",
" * If FILE begins with \"|\" then it is a command that generates the",
" input text.",
#ifndef SQLITE_OMIT_TEST_CONTROL
".imposter INDEX TABLE Create imposter table TABLE on index INDEX",
#endif
".indexes ?TABLE? Show names of indexes",
" If TABLE is specified, only show indexes for",
" tables matching TABLE using the LIKE operator.",
#ifdef SQLITE_ENABLE_IOTRACE
".iotrace FILE Enable I/O diagnostic logging to FILE",
#endif
".limit ?LIMIT? ?VAL? Display or change the value of an SQLITE_LIMIT",
".lint OPTIONS Report potential schema issues.",
" Options:",
" fkey-indexes Find missing foreign key indexes",
#ifndef SQLITE_OMIT_LOAD_EXTENSION
".load FILE ?ENTRY? Load an extension library",
#endif
".log FILE|off Turn logging on or off. FILE can be stderr/stdout",
".mode MODE ?TABLE? Set output mode",
" MODE is one of:",
" ascii Columns/rows delimited by 0x1F and 0x1E",
" box Tables using unicode box-drawing characters",
" csv Comma-separated values",
" column Output in columns. (See .width)",
" html HTML <table> code",
" insert SQL insert statements for TABLE",
" json Results in a JSON array",
" line One value per line",
" list Values delimited by \"|\"",
" markdown Markdown table format",
" quote Escape answers as for SQL",
" table ASCII-art table",
" tabs Tab-separated values",
" tcl TCL list elements",
".nullvalue STRING Use STRING in place of NULL values",
".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE",
" If FILE begins with '|' then open as a pipe",
" --bom Put a UTF8 byte-order mark at the beginning",
" -e Send output to the system text editor",
" -x Send output as CSV to a spreadsheet (same as \".excel\")",
#ifdef SQLITE_DEBUG
".oom ?--repeat M? ?N? Simulate an OOM error on the N-th allocation",
#endif
".open ?OPTIONS? ?FILE? Close existing database and reopen FILE",
" Options:",
" --append Use appendvfs to append database to the end of FILE",
#ifdef SQLITE_ENABLE_DESERIALIZE
" --deserialize Load into memory useing sqlite3_deserialize()",
" --hexdb Load the output of \"dbtotxt\" as an in-memory db",
" --maxsize N Maximum size for --hexdb or --deserialized database",
#endif
" --new Initialize FILE to an empty database",
" --nofollow Do not follow symbolic links",
" --readonly Open FILE readonly",
" --zip FILE is a ZIP archive",
".output ?FILE? Send output to FILE or stdout if FILE is omitted",
" If FILE begins with '|' then open it as a pipe.",
" Options:",
" --bom Prefix output with a UTF8 byte-order mark",
" -e Send output to the system text editor",
" -x Send output as CSV to a spreadsheet",
".parameter CMD ... Manage SQL parameter bindings",
" clear Erase all bindings",
" init Initialize the TEMP table that holds bindings",
" list List the current parameter bindings",
" set PARAMETER VALUE Given SQL parameter PARAMETER a value of VALUE",
" PARAMETER should start with one of: $ : @ ?",
" unset PARAMETER Remove PARAMETER from the binding table",
".print STRING... Print literal STRING",
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
".progress N Invoke progress handler after every N opcodes",
" --limit N Interrupt after N progress callbacks",
" --once Do no more than one progress interrupt",
" --quiet|-q No output except at interrupts",
" --reset Reset the count for each input and interrupt",
#endif
".prompt MAIN CONTINUE Replace the standard prompts",
".quit Exit this program",
".read FILE Read input from FILE",
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
".recover Recover as much data as possible from corrupt db.",
" --freelist-corrupt Assume the freelist is corrupt",
" --recovery-db NAME Store recovery metadata in database file NAME",
" --lost-and-found TABLE Alternative name for the lost-and-found table",
" --no-rowids Do not attempt to recover rowid values",
" that are not also INTEGER PRIMARY KEYs",
#endif
".restore ?DB? FILE Restore content of DB (default \"main\") from FILE",
".save FILE Write in-memory database into FILE",
".scanstats on|off Turn sqlite3_stmt_scanstatus() metrics on or off",
".schema ?PATTERN? Show the CREATE statements matching PATTERN",
" Options:",
" --indent Try to pretty-print the schema",
|
| ︙ | ︙ | |||
12154 12155 12156 12157 12158 12159 12160 | " list List currently open session names", " open DB NAME Open a new session on DB", " patchset FILE Write a patchset into FILE", " If ?NAME? is omitted, the first defined session is used.", #endif ".sha3sum ... Compute a SHA3 hash of database content", " Options:", | | | | 12671 12672 12673 12674 12675 12676 12677 12678 12679 12680 12681 12682 12683 12684 12685 12686 12687 | " list List currently open session names", " open DB NAME Open a new session on DB", " patchset FILE Write a patchset into FILE", " If ?NAME? is omitted, the first defined session is used.", #endif ".sha3sum ... Compute a SHA3 hash of database content", " Options:", " --schema Also hash the sqlite_schema table", " --sha3-224 Use the sha3-224 algorithm", " --sha3-256 Use the sha3-256 algorithm (default)", " --sha3-384 Use the sha3-384 algorithm", " --sha3-512 Use the sha3-512 algorithm", " Any other argument is a LIKE pattern for tables to hash", #ifndef SQLITE_NOHAVE_SYSTEM ".shell CMD ARGS... Run CMD ARGS... in a system shell", #endif ".show Show the current values for various settings", |
| ︙ | ︙ | |||
12190 12191 12192 12193 12194 12195 12196 12197 12198 12199 | #endif " --plain Show SQL as it is input", " --stmt Trace statement execution (SQLITE_TRACE_STMT)", " --profile Profile statements (SQLITE_TRACE_PROFILE)", " --row Trace each row (SQLITE_TRACE_ROW)", " --close Trace connection close (SQLITE_TRACE_CLOSE)", #endif /* SQLITE_OMIT_TRACE */ ".vfsinfo ?AUX? Information about the top-level VFS", ".vfslist List all available VFSes", ".vfsname ?AUX? Print the name of the VFS stack", | > > > > | | 12707 12708 12709 12710 12711 12712 12713 12714 12715 12716 12717 12718 12719 12720 12721 12722 12723 12724 12725 12726 12727 12728 | #endif " --plain Show SQL as it is input", " --stmt Trace statement execution (SQLITE_TRACE_STMT)", " --profile Profile statements (SQLITE_TRACE_PROFILE)", " --row Trace each row (SQLITE_TRACE_ROW)", " --close Trace connection close (SQLITE_TRACE_CLOSE)", #endif /* SQLITE_OMIT_TRACE */ #ifdef SQLITE_DEBUG ".unmodule NAME ... Unregister virtual table modules", " --allexcept Unregister everything except those named", #endif ".vfsinfo ?AUX? Information about the top-level VFS", ".vfslist List all available VFSes", ".vfsname ?AUX? Print the name of the VFS stack", ".width NUM1 NUM2 ... Set minimum column widths for columnar output", " Negative values right-justify", }; /* ** Output help text. ** ** zPattern describes the set of commands for which help text is provided. |
| ︙ | ︙ | |||
12215 12216 12217 12218 12219 12220 12221 12222 12223 12224 12225 12226 12227 12228 |
int j = 0;
int n = 0;
char *zPat;
if( zPattern==0
|| zPattern[0]=='0'
|| strcmp(zPattern,"-a")==0
|| strcmp(zPattern,"-all")==0
){
/* Show all commands, but only one line per command */
if( zPattern==0 ) zPattern = "";
for(i=0; i<ArraySize(azHelp); i++){
if( azHelp[i][0]=='.' || zPattern[0] ){
utf8_printf(out, "%s\n", azHelp[i]);
n++;
| > | 12736 12737 12738 12739 12740 12741 12742 12743 12744 12745 12746 12747 12748 12749 12750 |
int j = 0;
int n = 0;
char *zPat;
if( zPattern==0
|| zPattern[0]=='0'
|| strcmp(zPattern,"-a")==0
|| strcmp(zPattern,"-all")==0
|| strcmp(zPattern,"--all")==0
){
/* Show all commands, but only one line per command */
if( zPattern==0 ) zPattern = "";
for(i=0; i<ArraySize(azHelp); i++){
if( azHelp[i][0]=='.' || zPattern[0] ){
utf8_printf(out, "%s\n", azHelp[i]);
n++;
|
| ︙ | ︙ | |||
12432 12433 12434 12435 12436 12437 12438 12439 12440 12441 12442 12443 12444 12445 |
}
*pnData = 0;
nLine++;
if( fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error;
rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz);
if( rc!=2 ) goto readHexDb_error;
if( n<0 ) goto readHexDb_error;
a = sqlite3_malloc( n ? n : 1 );
if( a==0 ){
utf8_printf(stderr, "Out of memory!\n");
goto readHexDb_error;
}
memset(a, 0, n);
if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
| > > | 12954 12955 12956 12957 12958 12959 12960 12961 12962 12963 12964 12965 12966 12967 12968 12969 |
}
*pnData = 0;
nLine++;
if( fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error;
rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz);
if( rc!=2 ) goto readHexDb_error;
if( n<0 ) goto readHexDb_error;
if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error;
n = (n+pgsz-1)&~(pgsz-1); /* Round n up to the next multiple of pgsz */
a = sqlite3_malloc( n ? n : 1 );
if( a==0 ){
utf8_printf(stderr, "Out of memory!\n");
goto readHexDb_error;
}
memset(a, 0, n);
if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
|
| ︙ | ︙ | |||
12515 12516 12517 12518 12519 12520 12521 12522 12523 12524 12525 12526 12527 12528 |
sqlite3_int64 iVal = ((sqlite3_int64)a[0]<<24)
+ ((sqlite3_int64)a[1]<<16)
+ ((sqlite3_int64)a[2]<< 8)
+ ((sqlite3_int64)a[3]<< 0);
sqlite3_result_int64(context, iVal);
}
}
/*
** Scalar function "shell_escape_crnl" used by the .recover command.
** The argument passed to this function is the output of built-in
** function quote(). If the first character of the input is "'",
** indicating that the value passed to quote() was a text value,
** then this function searches the input for "\n" and "\r" characters
| > > > > > > > > > > > > > > > > > | 13039 13040 13041 13042 13043 13044 13045 13046 13047 13048 13049 13050 13051 13052 13053 13054 13055 13056 13057 13058 13059 13060 13061 13062 13063 13064 13065 13066 13067 13068 13069 |
sqlite3_int64 iVal = ((sqlite3_int64)a[0]<<24)
+ ((sqlite3_int64)a[1]<<16)
+ ((sqlite3_int64)a[2]<< 8)
+ ((sqlite3_int64)a[3]<< 0);
sqlite3_result_int64(context, iVal);
}
}
/*
** Scalar function "shell_idquote(X)" returns string X quoted as an identifier,
** using "..." with internal double-quote characters doubled.
*/
static void shellIdQuote(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
const char *zName = (const char*)sqlite3_value_text(argv[0]);
UNUSED_PARAMETER(argc);
if( zName ){
char *z = sqlite3_mprintf("\"%w\"", zName);
sqlite3_result_text(context, z, -1, sqlite3_free);
}
}
/*
** Scalar function "shell_escape_crnl" used by the .recover command.
** The argument passed to this function is the output of built-in
** function quote(). If the first character of the input is "'",
** indicating that the value passed to quote() was a text value,
** then this function searches the input for "\n" and "\r" characters
|
| ︙ | ︙ | |||
12637 12638 12639 12640 12641 12642 12643 |
p->openMode = (u8)deduceDatabaseType(p->zDbFilename,
(openFlags & OPEN_DB_ZIPFILE)!=0);
}
}
switch( p->openMode ){
case SHELL_OPEN_APPENDVFS: {
sqlite3_open_v2(p->zDbFilename, &p->db,
| | | > | > > > > | 13178 13179 13180 13181 13182 13183 13184 13185 13186 13187 13188 13189 13190 13191 13192 13193 13194 13195 13196 13197 13198 13199 13200 13201 13202 13203 13204 13205 13206 13207 13208 13209 13210 13211 13212 13213 13214 13215 13216 13217 13218 13219 13220 13221 13222 13223 13224 13225 13226 13227 13228 13229 13230 13231 13232 13233 13234 13235 13236 13237 13238 13239 13240 13241 13242 13243 13244 13245 13246 13247 13248 13249 13250 13251 |
p->openMode = (u8)deduceDatabaseType(p->zDbFilename,
(openFlags & OPEN_DB_ZIPFILE)!=0);
}
}
switch( p->openMode ){
case SHELL_OPEN_APPENDVFS: {
sqlite3_open_v2(p->zDbFilename, &p->db,
SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs");
break;
}
case SHELL_OPEN_HEXDB:
case SHELL_OPEN_DESERIALIZE: {
sqlite3_open(0, &p->db);
break;
}
case SHELL_OPEN_ZIPFILE: {
sqlite3_open(":memory:", &p->db);
break;
}
case SHELL_OPEN_READONLY: {
sqlite3_open_v2(p->zDbFilename, &p->db,
SQLITE_OPEN_READONLY|p->openFlags, 0);
break;
}
case SHELL_OPEN_UNSPEC:
case SHELL_OPEN_NORMAL: {
sqlite3_open_v2(p->zDbFilename, &p->db,
SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, 0);
break;
}
}
globalDb = p->db;
if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n",
p->zDbFilename, sqlite3_errmsg(p->db));
if( openFlags & OPEN_DB_KEEPALIVE ){
sqlite3_open(":memory:", &p->db);
return;
}
exit(1);
}
#ifndef SQLITE_OMIT_LOAD_EXTENSION
sqlite3_enable_load_extension(p->db, 1);
#endif
sqlite3_fileio_init(p->db, 0, 0);
sqlite3_shathree_init(p->db, 0, 0);
sqlite3_completion_init(p->db, 0, 0);
sqlite3_uint_init(p->db, 0, 0);
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
sqlite3_dbdata_init(p->db, 0, 0);
#endif
#ifdef SQLITE_HAVE_ZLIB
sqlite3_zipfile_init(p->db, 0, 0);
sqlite3_sqlar_init(p->db, 0, 0);
#endif
sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0,
shellAddSchemaName, 0, 0);
sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0,
shellModuleSchema, 0, 0);
sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p,
shellPutsFunc, 0, 0);
sqlite3_create_function(p->db, "shell_escape_crnl", 1, SQLITE_UTF8, 0,
shellEscapeCrnl, 0, 0);
sqlite3_create_function(p->db, "shell_int32", 2, SQLITE_UTF8, 0,
shellInt32, 0, 0);
sqlite3_create_function(p->db, "shell_idquote", 1, SQLITE_UTF8, 0,
shellIdQuote, 0, 0);
#ifndef SQLITE_NOHAVE_SYSTEM
sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0,
editFunc, 0, 0);
sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0,
editFunc, 0, 0);
#endif
if( p->openMode==SHELL_OPEN_ZIPFILE ){
|
| ︙ | ︙ | |||
13005 13006 13007 13008 13009 13010 13011 13012 13013 13014 13015 13016 13017 13018 13019 13020 13021 13022 13023 13024 13025 13026 13027 |
/*
** An object used to read a CSV and other files for import.
*/
typedef struct ImportCtx ImportCtx;
struct ImportCtx {
const char *zFile; /* Name of the input file */
FILE *in; /* Read the CSV text from this input stream */
char *z; /* Accumulated text for a field */
int n; /* Number of bytes in z */
int nAlloc; /* Space allocated for z[] */
int nLine; /* Current line number */
int bNotFirst; /* True if one or more bytes already read */
int cTerm; /* Character that terminated the most recent field */
int cColSep; /* The column separator character. (Usually ",") */
int cRowSep; /* The row separator character. (Usually "\n") */
};
/* Append a single byte to z[] */
static void import_append_char(ImportCtx *p, int c){
if( p->n+1>=p->nAlloc ){
p->nAlloc += p->nAlloc + 100;
p->z = sqlite3_realloc64(p->z, p->nAlloc);
if( p->z==0 ) shell_out_of_memory();
| > > > > > > > > > > > > > | 13551 13552 13553 13554 13555 13556 13557 13558 13559 13560 13561 13562 13563 13564 13565 13566 13567 13568 13569 13570 13571 13572 13573 13574 13575 13576 13577 13578 13579 13580 13581 13582 13583 13584 13585 13586 |
/*
** An object used to read a CSV and other files for import.
*/
typedef struct ImportCtx ImportCtx;
struct ImportCtx {
const char *zFile; /* Name of the input file */
FILE *in; /* Read the CSV text from this input stream */
int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */
char *z; /* Accumulated text for a field */
int n; /* Number of bytes in z */
int nAlloc; /* Space allocated for z[] */
int nLine; /* Current line number */
int nRow; /* Number of rows imported */
int nErr; /* Number of errors encountered */
int bNotFirst; /* True if one or more bytes already read */
int cTerm; /* Character that terminated the most recent field */
int cColSep; /* The column separator character. (Usually ",") */
int cRowSep; /* The row separator character. (Usually "\n") */
};
/* Clean up resourced used by an ImportCtx */
static void import_cleanup(ImportCtx *p){
if( p->in!=0 && p->xCloser!=0 ){
p->xCloser(p->in);
p->in = 0;
}
sqlite3_free(p->z);
p->z = 0;
}
/* Append a single byte to z[] */
static void import_append_char(ImportCtx *p, int c){
if( p->n+1>=p->nAlloc ){
p->nAlloc += p->nAlloc + 100;
p->z = sqlite3_realloc64(p->z, p->nAlloc);
if( p->z==0 ) shell_out_of_memory();
|
| ︙ | ︙ | |||
13263 13264 13265 13266 13267 13268 13269 | } /* ** Try to transfer all rows of the schema that match zWhere. For ** each row, invoke xForEach() on the object defined by that row. ** If an error is encountered while moving forward through the | | | | 13822 13823 13824 13825 13826 13827 13828 13829 13830 13831 13832 13833 13834 13835 13836 13837 13838 13839 13840 13841 13842 13843 13844 13845 13846 13847 13848 13849 13850 13851 |
}
/*
** Try to transfer all rows of the schema that match zWhere. For
** each row, invoke xForEach() on the object defined by that row.
** If an error is encountered while moving forward through the
** sqlite_schema table, try again moving backwards.
*/
static void tryToCloneSchema(
ShellState *p,
sqlite3 *newDb,
const char *zWhere,
void (*xForEach)(ShellState*,sqlite3*,const char*)
){
sqlite3_stmt *pQuery = 0;
char *zQuery = 0;
int rc;
const unsigned char *zName;
const unsigned char *zSql;
char *zErrMsg = 0;
zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_schema"
" WHERE %s", zWhere);
rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
if( rc ){
utf8_printf(stderr, "Error: (%d) %s on [%s]\n",
sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db),
zQuery);
goto end_schema_xfer;
|
| ︙ | ︙ | |||
13305 13306 13307 13308 13309 13310 13311 |
xForEach(p, newDb, (const char*)zName);
}
printf("done\n");
}
if( rc!=SQLITE_DONE ){
sqlite3_finalize(pQuery);
sqlite3_free(zQuery);
| | | 13864 13865 13866 13867 13868 13869 13870 13871 13872 13873 13874 13875 13876 13877 13878 |
xForEach(p, newDb, (const char*)zName);
}
printf("done\n");
}
if( rc!=SQLITE_DONE ){
sqlite3_finalize(pQuery);
sqlite3_free(zQuery);
zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_schema"
" WHERE %s ORDER BY rowid DESC", zWhere);
rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
if( rc ){
utf8_printf(stderr, "Error: (%d) %s on [%s]\n",
sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db),
zQuery);
goto end_schema_xfer;
|
| ︙ | ︙ | |||
13390 13391 13392 13393 13394 13395 13396 13397 13398 13399 13400 |
#else
"xdg-open";
#endif
char *zCmd;
zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, p->zTempFile);
if( system(zCmd) ){
utf8_printf(stderr, "Failed: [%s]\n", zCmd);
}
sqlite3_free(zCmd);
outputModePop(p);
p->doXdgOpen = 0;
| > > > > > < | 13949 13950 13951 13952 13953 13954 13955 13956 13957 13958 13959 13960 13961 13962 13963 13964 13965 13966 13967 13968 13969 13970 13971 |
#else
"xdg-open";
#endif
char *zCmd;
zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, p->zTempFile);
if( system(zCmd) ){
utf8_printf(stderr, "Failed: [%s]\n", zCmd);
}else{
/* Give the start/open/xdg-open command some time to get
** going before we continue, and potential delete the
** p->zTempFile data file out from under it */
sqlite3_sleep(2000);
}
sqlite3_free(zCmd);
outputModePop(p);
p->doXdgOpen = 0;
}
#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */
}
p->outfile[0] = 0;
p->out = stdout;
}
|
| ︙ | ︙ | |||
13427 13428 13429 13430 13431 13432 13433 |
return (a[0]<<8) + a[1];
}
static unsigned int get4byteInt(unsigned char *a){
return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3];
}
/*
| | | 13990 13991 13992 13993 13994 13995 13996 13997 13998 13999 14000 14001 14002 14003 14004 |
return (a[0]<<8) + a[1];
}
static unsigned int get4byteInt(unsigned char *a){
return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3];
}
/*
** Implementation of the ".dbinfo" command.
**
** Return 1 on error, 2 to exit, and 0 otherwise.
*/
static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){
static const struct { const char *zName; int ofst; } aField[] = {
{ "file change counter:", 24 },
{ "database page count:", 28 },
|
| ︙ | ︙ | |||
13470 13471 13472 13473 13474 13475 13476 |
unsigned char aHdr[100];
open_db(p, 0);
if( p->db==0 ) return 1;
rc = sqlite3_prepare_v2(p->db,
"SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1",
-1, &pStmt, 0);
if( rc ){
| < < < < | < | 14033 14034 14035 14036 14037 14038 14039 14040 14041 14042 14043 14044 14045 14046 14047 |
unsigned char aHdr[100];
open_db(p, 0);
if( p->db==0 ) return 1;
rc = sqlite3_prepare_v2(p->db,
"SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1",
-1, &pStmt, 0);
if( rc ){
utf8_printf(stderr, "error: %s\n", sqlite3_errmsg(p->db));
sqlite3_finalize(pStmt);
return 1;
}
sqlite3_bind_text(pStmt, 1, zDb, -1, SQLITE_STATIC);
if( sqlite3_step(pStmt)==SQLITE_ROW
&& sqlite3_column_bytes(pStmt,0)>100
){
|
| ︙ | ︙ | |||
13510 13511 13512 13513 13514 13515 13516 |
if( val==2 ) raw_printf(p->out, " (utf16le)");
if( val==3 ) raw_printf(p->out, " (utf16be)");
}
}
raw_printf(p->out, "\n");
}
if( zDb==0 ){
| | | | | 14068 14069 14070 14071 14072 14073 14074 14075 14076 14077 14078 14079 14080 14081 14082 14083 14084 14085 14086 |
if( val==2 ) raw_printf(p->out, " (utf16le)");
if( val==3 ) raw_printf(p->out, " (utf16be)");
}
}
raw_printf(p->out, "\n");
}
if( zDb==0 ){
zSchemaTab = sqlite3_mprintf("main.sqlite_schema");
}else if( strcmp(zDb,"temp")==0 ){
zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_schema");
}else{
zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_schema", zDb);
}
for(i=0; i<ArraySize(aQuery); i++){
char *zSql = sqlite3_mprintf(aQuery[i].zSql, zSchemaTab);
int val = db_int(p, zSql);
sqlite3_free(zSql);
utf8_printf(p->out, "%-20s %d\n", aQuery[i].zName, val);
}
|
| ︙ | ︙ | |||
13684 13685 13686 13687 13688 13689 13690 13691 13692 |
clearTempFile(p);
sqlite3_free(p->zTempFile);
p->zTempFile = 0;
if( p->db ){
sqlite3_file_control(p->db, 0, SQLITE_FCNTL_TEMPFILENAME, &p->zTempFile);
}
if( p->zTempFile==0 ){
sqlite3_uint64 r;
sqlite3_randomness(sizeof(r), &r);
| > > > > > > > > > > > > | | 14242 14243 14244 14245 14246 14247 14248 14249 14250 14251 14252 14253 14254 14255 14256 14257 14258 14259 14260 14261 14262 14263 14264 14265 14266 14267 14268 14269 14270 |
clearTempFile(p);
sqlite3_free(p->zTempFile);
p->zTempFile = 0;
if( p->db ){
sqlite3_file_control(p->db, 0, SQLITE_FCNTL_TEMPFILENAME, &p->zTempFile);
}
if( p->zTempFile==0 ){
/* If p->db is an in-memory database then the TEMPFILENAME file-control
** will not work and we will need to fallback to guessing */
char *zTemp;
sqlite3_uint64 r;
sqlite3_randomness(sizeof(r), &r);
zTemp = getenv("TEMP");
if( zTemp==0 ) zTemp = getenv("TMP");
if( zTemp==0 ){
#ifdef _WIN32
zTemp = "\\tmp";
#else
zTemp = "/tmp";
#endif
}
p->zTempFile = sqlite3_mprintf("%s/temp%llx.%s", zTemp, r, zSuffix);
}else{
p->zTempFile = sqlite3_mprintf("%z.%s", p->zTempFile, zSuffix);
}
if( p->zTempFile==0 ){
raw_printf(stderr, "out of memory\n");
exit(1);
}
|
| ︙ | ︙ | |||
13824 13825 13826 13827 13828 13829 13830 |
" || ' ON ' || quote(s.name) || '('"
" || group_concat(quote(f.[from]) ||"
" fkey_collate_clause("
" f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')"
" || ');'"
", "
" f.[table] "
| | | 14394 14395 14396 14397 14398 14399 14400 14401 14402 14403 14404 14405 14406 14407 14408 |
" || ' ON ' || quote(s.name) || '('"
" || group_concat(quote(f.[from]) ||"
" fkey_collate_clause("
" f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')"
" || ');'"
", "
" f.[table] "
"FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f "
"LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) "
"GROUP BY s.name, f.id "
"ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)"
;
const char *zGlobIPK = "SEARCH TABLE * USING INTEGER PRIMARY KEY (rowid=?)";
for(i=2; i<nArg; i++){
|
| ︙ | ︙ | |||
14039 14040 14041 14042 14043 14044 14045 |
}
*pRc = rc;
}
}
#endif /* !defined SQLITE_OMIT_VIRTUALTABLE */
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
| | | 14609 14610 14611 14612 14613 14614 14615 14616 14617 14618 14619 14620 14621 14622 14623 |
}
*pRc = rc;
}
}
#endif /* !defined SQLITE_OMIT_VIRTUALTABLE */
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
/******************************************************************************
** The ".archive" or ".ar" command.
*/
/*
** Structure representing a single ".ar" command.
*/
typedef struct ArCommand ArCommand;
struct ArCommand {
|
| ︙ | ︙ | |||
14237 14238 14239 14240 14241 14242 14243 |
}
if( pOpt->bArg ){
if( i<(n-1) ){
zArg = &z[i+1];
i = n;
}else{
if( iArg>=(nArg-1) ){
| | > | 14807 14808 14809 14810 14811 14812 14813 14814 14815 14816 14817 14818 14819 14820 14821 14822 |
}
if( pOpt->bArg ){
if( i<(n-1) ){
zArg = &z[i+1];
i = n;
}else{
if( iArg>=(nArg-1) ){
return arErrorMsg(pAr, "option requires an argument: %c",
z[i]);
}
zArg = azArg[++iArg];
}
}
if( arProcessSwitch(pAr, pOpt->eSwitch, zArg) ) return SQLITE_ERROR;
}
}else if( z[2]=='\0' ){
|
| ︙ | ︙ | |||
14625 14626 14627 14628 14629 14630 14631 | return rc; } /* ** Implementation of ".ar" dot command. */ static int arDotCommand( | | | | | | 15196 15197 15198 15199 15200 15201 15202 15203 15204 15205 15206 15207 15208 15209 15210 15211 15212 15213 |
return rc;
}
/*
** Implementation of ".ar" dot command.
*/
static int arDotCommand(
ShellState *pState, /* Current shell tool state */
int fromCmdLine, /* True if -A command-line option, not .ar cmd */
char **azArg, /* Array of arguments passed to dot command */
int nArg /* Number of entries in azArg[] */
){
ArCommand cmd;
int rc;
memset(&cmd, 0, sizeof(cmd));
cmd.fromCmdLine = fromCmdLine;
rc = arParseCommand(azArg, nArg, &cmd);
if( rc==SQLITE_OK ){
|
| ︙ | ︙ | |||
14728 14729 14730 14731 14732 14733 14734 |
close_db(cmd.db);
}
sqlite3_free(cmd.zSrcTable);
return rc;
}
/* End of the ".archive" or ".ar" command logic
| | | 15299 15300 15301 15302 15303 15304 15305 15306 15307 15308 15309 15310 15311 15312 15313 |
close_db(cmd.db);
}
sqlite3_free(cmd.zSrcTable);
return rc;
}
/* End of the ".archive" or ".ar" command logic
*******************************************************************************/
#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
/*
** If (*pRc) is not SQLITE_OK when this function is called, it is a no-op.
** Otherwise, the SQL statement or statements in zSql are executed using
** database connection db and the error code written to *pRc before
|
| ︙ | ︙ | |||
14869 14870 14871 14872 14873 14874 14875 14876 14877 14878 14879 14880 14881 14882 |
pTab = (RecoverTable*)shellMalloc(&rc, sizeof(RecoverTable));
if( rc==SQLITE_OK ){
int nSqlCol = 0;
int bSqlIntkey = 0;
sqlite3_stmt *pStmt = 0;
rc = sqlite3_open("", &dbtmp);
if( rc==SQLITE_OK ){
rc = sqlite3_exec(dbtmp, "PRAGMA writable_schema = on", 0, 0, 0);
}
if( rc==SQLITE_OK ){
rc = sqlite3_exec(dbtmp, zSql, 0, 0, 0);
if( rc==SQLITE_ERROR ){
rc = SQLITE_OK;
| > > > > | 15440 15441 15442 15443 15444 15445 15446 15447 15448 15449 15450 15451 15452 15453 15454 15455 15456 15457 |
pTab = (RecoverTable*)shellMalloc(&rc, sizeof(RecoverTable));
if( rc==SQLITE_OK ){
int nSqlCol = 0;
int bSqlIntkey = 0;
sqlite3_stmt *pStmt = 0;
rc = sqlite3_open("", &dbtmp);
if( rc==SQLITE_OK ){
sqlite3_create_function(dbtmp, "shell_idquote", 1, SQLITE_UTF8, 0,
shellIdQuote, 0, 0);
}
if( rc==SQLITE_OK ){
rc = sqlite3_exec(dbtmp, "PRAGMA writable_schema = on", 0, 0, 0);
}
if( rc==SQLITE_OK ){
rc = sqlite3_exec(dbtmp, zSql, 0, 0, 0);
if( rc==SQLITE_ERROR ){
rc = SQLITE_OK;
|
| ︙ | ︙ | |||
14894 14895 14896 14897 14898 14899 14900 |
if( rc!=SQLITE_OK || nSqlCol<nCol ){
goto finished;
}
shellPreparePrintf(dbtmp, &rc, &pStmt,
"SELECT ("
" SELECT substr(data,1,1)==X'0D' FROM sqlite_dbpage WHERE pgno=rootpage"
| | | 15469 15470 15471 15472 15473 15474 15475 15476 15477 15478 15479 15480 15481 15482 15483 |
if( rc!=SQLITE_OK || nSqlCol<nCol ){
goto finished;
}
shellPreparePrintf(dbtmp, &rc, &pStmt,
"SELECT ("
" SELECT substr(data,1,1)==X'0D' FROM sqlite_dbpage WHERE pgno=rootpage"
") FROM sqlite_schema WHERE name = %Q", zName
);
if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
bSqlIntkey = sqlite3_column_int(pStmt, 0);
}
shellFinalize(&rc, pStmt);
if( bIntkey==bSqlIntkey ){
|
| ︙ | ︙ | |||
14925 14926 14927 14928 14929 14930 14931 |
);
if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPkFinder) ){
pTab->iPk = sqlite3_column_int(pPkFinder, 0);
zPk = (const char*)sqlite3_column_text(pPkFinder, 1);
}
}
| | | | | 15500 15501 15502 15503 15504 15505 15506 15507 15508 15509 15510 15511 15512 15513 15514 15515 15516 15517 15518 15519 15520 15521 15522 15523 15524 15525 |
);
if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPkFinder) ){
pTab->iPk = sqlite3_column_int(pPkFinder, 0);
zPk = (const char*)sqlite3_column_text(pPkFinder, 1);
}
}
pTab->zQuoted = shellMPrintf(&rc, "\"%w\"", zName);
pTab->azlCol = (char**)shellMalloc(&rc, sizeof(char*) * (nSqlCol+1));
pTab->nCol = nSqlCol;
if( bIntkey ){
pTab->azlCol[0] = shellMPrintf(&rc, "\"%w\"", zPk);
}else{
pTab->azlCol[0] = shellMPrintf(&rc, "");
}
i = 1;
shellPreparePrintf(dbtmp, &rc, &pStmt,
"SELECT %Q || group_concat(shell_idquote(name), ', ') "
" FILTER (WHERE cid!=%d) OVER (ORDER BY %s cid) "
"FROM pragma_table_info(%Q)",
bIntkey ? ", " : "", pTab->iPk,
bIntkey ? "" : "(CASE WHEN pk=0 THEN 1000000 ELSE pk END), ",
zName
);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
| ︙ | ︙ | |||
14966 14967 14968 14969 14970 14971 14972 |
pTab = 0;
}
return pTab;
}
/*
** This function is called to search the schema recovered from the
| | | 15541 15542 15543 15544 15545 15546 15547 15548 15549 15550 15551 15552 15553 15554 15555 |
pTab = 0;
}
return pTab;
}
/*
** This function is called to search the schema recovered from the
** sqlite_schema table of the (possibly) corrupt database as part
** of a ".recover" command. Specifically, for a table with root page
** iRoot and at least nCol columns. Additionally, if bIntkey is 0, the
** table must be a WITHOUT ROWID table, or if non-zero, not one of
** those.
**
** If a table is found, a (RecoverTable*) object is returned. Or, if
** no such table is found, but bIntkey is false and iRoot is the
|
| ︙ | ︙ | |||
15050 15051 15052 15053 15054 15055 15056 |
zTab = shellMPrintf(pRc, "%s_%d", zLostAndFound, iTab++);
sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT);
}
shellFinalize(pRc, pTest);
pTab = (RecoverTable*)shellMalloc(pRc, sizeof(RecoverTable));
if( pTab ){
| | | 15625 15626 15627 15628 15629 15630 15631 15632 15633 15634 15635 15636 15637 15638 15639 |
zTab = shellMPrintf(pRc, "%s_%d", zLostAndFound, iTab++);
sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT);
}
shellFinalize(pRc, pTest);
pTab = (RecoverTable*)shellMalloc(pRc, sizeof(RecoverTable));
if( pTab ){
pTab->zQuoted = shellMPrintf(pRc, "\"%w\"", zTab);
pTab->nCol = nCol;
pTab->iPk = -2;
if( nCol>0 ){
pTab->azlCol = (char**)shellMalloc(pRc, sizeof(char*) * (nCol+1));
if( pTab->azlCol ){
pTab->azlCol[nCol] = shellMPrintf(pRc, "");
for(i=nCol-1; i>=0; i--){
|
| ︙ | ︙ | |||
15099 15100 15101 15102 15103 15104 15105 15106 15107 15108 15109 15110 15111 15112 15113 15114 15115 15116 15117 15118 15119 15120 15121 15122 |
const char *zRecoveryDb = ""; /* Name of "recovery" database */
const char *zLostAndFound = "lost_and_found";
int i;
int nOrphan = -1;
RecoverTable *pOrphan = 0;
int bFreelist = 1; /* 0 if --freelist-corrupt is specified */
for(i=1; i<nArg; i++){
char *z = azArg[i];
int n;
if( z[0]=='-' && z[1]=='-' ) z++;
n = strlen30(z);
if( n<=17 && memcmp("-freelist-corrupt", z, n)==0 ){
bFreelist = 0;
}else
if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
i++;
zRecoveryDb = azArg[i];
}else
if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
i++;
zLostAndFound = azArg[i];
}
else{
| > > > > | < < < | > | 15674 15675 15676 15677 15678 15679 15680 15681 15682 15683 15684 15685 15686 15687 15688 15689 15690 15691 15692 15693 15694 15695 15696 15697 15698 15699 15700 15701 15702 15703 15704 15705 15706 15707 15708 15709 15710 15711 15712 15713 15714 15715 15716 15717 15718 |
const char *zRecoveryDb = ""; /* Name of "recovery" database */
const char *zLostAndFound = "lost_and_found";
int i;
int nOrphan = -1;
RecoverTable *pOrphan = 0;
int bFreelist = 1; /* 0 if --freelist-corrupt is specified */
int bRowids = 1; /* 0 if --no-rowids */
for(i=1; i<nArg; i++){
char *z = azArg[i];
int n;
if( z[0]=='-' && z[1]=='-' ) z++;
n = strlen30(z);
if( n<=17 && memcmp("-freelist-corrupt", z, n)==0 ){
bFreelist = 0;
}else
if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
i++;
zRecoveryDb = azArg[i];
}else
if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
i++;
zLostAndFound = azArg[i];
}else
if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
bRowids = 0;
}
else{
utf8_printf(stderr, "unexpected option: %s\n", azArg[i]);
showHelp(pState->out, azArg[0]);
return 1;
}
}
shellExecPrintf(pState->db, &rc,
/* Attach an in-memory database named 'recovery'. Create an indexed
** cache of the sqlite_dbptr virtual table. */
"PRAGMA writable_schema = on;"
"ATTACH %Q AS recovery;"
"DROP TABLE IF EXISTS recovery.dbptr;"
"DROP TABLE IF EXISTS recovery.freelist;"
"DROP TABLE IF EXISTS recovery.map;"
"DROP TABLE IF EXISTS recovery.schema;"
"CREATE TABLE recovery.freelist(pgno INTEGER PRIMARY KEY);", zRecoveryDb
);
|
| ︙ | ︙ | |||
15157 15158 15159 15160 15161 15162 15163 15164 15165 15166 15167 15168 15169 15170 |
" UNION ALL"
" SELECT data, n-1, shell_int32(data, 2+n) "
" FROM freelist WHERE n>=0"
")"
"REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;"
);
}
shellExec(pState->db, &rc,
"CREATE TABLE recovery.dbptr("
" pgno, child, PRIMARY KEY(child, pgno)"
") WITHOUT ROWID;"
"INSERT OR IGNORE INTO recovery.dbptr(pgno, child) "
" SELECT * FROM sqlite_dbptr"
| > > > > > > > > > > > > > > > | 15734 15735 15736 15737 15738 15739 15740 15741 15742 15743 15744 15745 15746 15747 15748 15749 15750 15751 15752 15753 15754 15755 15756 15757 15758 15759 15760 15761 15762 |
" UNION ALL"
" SELECT data, n-1, shell_int32(data, 2+n) "
" FROM freelist WHERE n>=0"
")"
"REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;"
);
}
/* If this is an auto-vacuum database, add all pointer-map pages to
** the freelist table. Do this regardless of whether or not
** --freelist-corrupt was specified. */
shellExec(pState->db, &rc,
"WITH ptrmap(pgno) AS ("
" SELECT 2 WHERE shell_int32("
" (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13"
" )"
" UNION ALL "
" SELECT pgno+1+(SELECT page_size FROM pragma_page_size)/5 AS pp "
" FROM ptrmap WHERE pp<=(SELECT page_count FROM pragma_page_count)"
")"
"REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap"
);
shellExec(pState->db, &rc,
"CREATE TABLE recovery.dbptr("
" pgno, child, PRIMARY KEY(child, pgno)"
") WITHOUT ROWID;"
"INSERT OR IGNORE INTO recovery.dbptr(pgno, child) "
" SELECT * FROM sqlite_dbptr"
|
| ︙ | ︙ | |||
15206 15207 15208 15209 15210 15211 15212 |
" SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)"
" UNION "
" SELECT i, p.parent, "
" (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p"
" )"
" SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)"
") "
| | | > > > > > | 15798 15799 15800 15801 15802 15803 15804 15805 15806 15807 15808 15809 15810 15811 15812 15813 15814 15815 15816 15817 15818 15819 15820 15821 15822 15823 15824 15825 15826 15827 15828 15829 15830 15831 15832 15833 15834 15835 15836 15837 15838 15839 15840 15841 |
" SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)"
" UNION "
" SELECT i, p.parent, "
" (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p"
" )"
" SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)"
") "
"FROM pages WHERE maxlen IS NOT NULL AND i NOT IN freelist;"
"UPDATE recovery.map AS o SET intkey = ("
" SELECT substr(data, 1, 1)==X'0D' FROM sqlite_dbpage WHERE pgno=o.pgno"
");"
/* Extract data from page 1 and any linked pages into table
** recovery.schema. With the same schema as an sqlite_schema table. */
"CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);"
"INSERT INTO recovery.schema SELECT "
" max(CASE WHEN field=0 THEN value ELSE NULL END),"
" max(CASE WHEN field=1 THEN value ELSE NULL END),"
" max(CASE WHEN field=2 THEN value ELSE NULL END),"
" max(CASE WHEN field=3 THEN value ELSE NULL END),"
" max(CASE WHEN field=4 THEN value ELSE NULL END)"
"FROM sqlite_dbdata WHERE pgno IN ("
" SELECT pgno FROM recovery.map WHERE root=1"
")"
"GROUP BY pgno, cell;"
"CREATE INDEX recovery.schema_rootpage ON schema(rootpage);"
);
/* Open a transaction, then print out all non-virtual, non-"sqlite_%"
** CREATE TABLE statements that extracted from the existing schema. */
if( rc==SQLITE_OK ){
sqlite3_stmt *pStmt = 0;
/* ".recover" might output content in an order which causes immediate
** foreign key constraints to be violated. So disable foreign-key
** constraint enforcement to prevent problems when running the output
** script. */
raw_printf(pState->out, "PRAGMA foreign_keys=OFF;\n");
raw_printf(pState->out, "BEGIN;\n");
raw_printf(pState->out, "PRAGMA writable_schema = on;\n");
shellPrepare(pState->db, &rc,
"SELECT sql FROM recovery.schema "
"WHERE type='table' AND sql LIKE 'create table%'", &pStmt
);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
| ︙ | ︙ | |||
15261 15262 15263 15264 15265 15266 15267 15268 |
}
shellFinalize(&rc, pLoop);
pLoop = 0;
shellPrepare(pState->db, &rc,
"SELECT pgno FROM recovery.map WHERE root=?", &pPages
);
shellPrepare(pState->db, &rc,
| > | > > > > | > > > > > | | > > > > > > > > > > | | | | | 15858 15859 15860 15861 15862 15863 15864 15865 15866 15867 15868 15869 15870 15871 15872 15873 15874 15875 15876 15877 15878 15879 15880 15881 15882 15883 15884 15885 15886 15887 15888 15889 15890 15891 15892 15893 15894 15895 15896 15897 15898 15899 15900 15901 15902 15903 15904 15905 15906 15907 15908 15909 15910 15911 15912 15913 15914 15915 15916 15917 15918 15919 15920 15921 15922 15923 15924 15925 15926 15927 15928 15929 15930 15931 15932 15933 15934 15935 15936 15937 15938 15939 15940 15941 15942 15943 15944 |
}
shellFinalize(&rc, pLoop);
pLoop = 0;
shellPrepare(pState->db, &rc,
"SELECT pgno FROM recovery.map WHERE root=?", &pPages
);
shellPrepare(pState->db, &rc,
"SELECT max(field), group_concat(shell_escape_crnl(quote"
"(case when (? AND field<0) then NULL else value end)"
"), ', ')"
", min(field) "
"FROM sqlite_dbdata WHERE pgno = ? AND field != ?"
"GROUP BY cell", &pCells
);
/* Loop through each root page. */
shellPrepare(pState->db, &rc,
"SELECT root, intkey, max(maxlen) FROM recovery.map"
" WHERE root>1 GROUP BY root, intkey ORDER BY root=("
" SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'"
")", &pLoop
);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
int iRoot = sqlite3_column_int(pLoop, 0);
int bIntkey = sqlite3_column_int(pLoop, 1);
int nCol = sqlite3_column_int(pLoop, 2);
int bNoop = 0;
RecoverTable *pTab;
assert( bIntkey==0 || bIntkey==1 );
pTab = recoverFindTable(pState, &rc, iRoot, bIntkey, nCol, &bNoop);
if( bNoop || rc ) continue;
if( pTab==0 ){
if( pOrphan==0 ){
pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
}
pTab = pOrphan;
if( pTab==0 ) break;
}
if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){
raw_printf(pState->out, "DELETE FROM sqlite_sequence;\n");
}
sqlite3_bind_int(pPages, 1, iRoot);
if( bRowids==0 && pTab->iPk<0 ){
sqlite3_bind_int(pCells, 1, 1);
}else{
sqlite3_bind_int(pCells, 1, 0);
}
sqlite3_bind_int(pCells, 3, pTab->iPk);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){
int iPgno = sqlite3_column_int(pPages, 0);
sqlite3_bind_int(pCells, 2, iPgno);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){
int nField = sqlite3_column_int(pCells, 0);
int iMin = sqlite3_column_int(pCells, 2);
const char *zVal = (const char*)sqlite3_column_text(pCells, 1);
RecoverTable *pTab2 = pTab;
if( pTab!=pOrphan && (iMin<0)!=bIntkey ){
if( pOrphan==0 ){
pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
}
pTab2 = pOrphan;
if( pTab2==0 ) break;
}
nField = nField+1;
if( pTab2==pOrphan ){
raw_printf(pState->out,
"INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n",
pTab2->zQuoted, iRoot, iPgno, nField,
iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField]
);
}else{
raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n",
pTab2->zQuoted, pTab2->azlCol[nField], zVal
);
}
}
shellReset(&rc, pCells);
}
shellReset(&rc, pPages);
if( pTab!=pOrphan ) recoverFreeTable(pTab);
|
| ︙ | ︙ | |||
15339 15340 15341 15342 15343 15344 15345 |
"WHERE sql NOT LIKE 'create table%'", &pStmt
);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
const char *zSql = (const char*)sqlite3_column_text(pStmt, 0);
if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){
const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
char *zPrint = shellMPrintf(&rc,
| | | 15956 15957 15958 15959 15960 15961 15962 15963 15964 15965 15966 15967 15968 15969 15970 |
"WHERE sql NOT LIKE 'create table%'", &pStmt
);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
const char *zSql = (const char*)sqlite3_column_text(pStmt, 0);
if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){
const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
char *zPrint = shellMPrintf(&rc,
"INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)",
zName, zName, zSql
);
raw_printf(pState->out, "%s;\n", zPrint);
sqlite3_free(zPrint);
}else{
raw_printf(pState->out, "%s;\n", zSql);
}
|
| ︙ | ︙ | |||
15372 15373 15374 15375 15376 15377 15378 |
** Return 1 on error, 2 to exit, and 0 otherwise.
*/
static int do_meta_command(char *zLine, ShellState *p){
int h = 1;
int nArg = 0;
int n, c;
int rc = 0;
| | | > | 15989 15990 15991 15992 15993 15994 15995 15996 15997 15998 15999 16000 16001 16002 16003 16004 16005 16006 16007 16008 16009 16010 16011 16012 16013 16014 16015 16016 16017 16018 16019 16020 16021 16022 16023 16024 16025 16026 16027 16028 16029 16030 16031 16032 16033 16034 |
** Return 1 on error, 2 to exit, and 0 otherwise.
*/
static int do_meta_command(char *zLine, ShellState *p){
int h = 1;
int nArg = 0;
int n, c;
int rc = 0;
char *azArg[52];
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( p->expert.pExpert ){
expertFinish(p, 1, 0);
}
#endif
/* Parse the input line into tokens.
*/
while( zLine[h] && nArg<ArraySize(azArg)-1 ){
while( IsSpace(zLine[h]) ){ h++; }
if( zLine[h]==0 ) break;
if( zLine[h]=='\'' || zLine[h]=='"' ){
int delim = zLine[h++];
azArg[nArg++] = &zLine[h];
while( zLine[h] && zLine[h]!=delim ){
if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
h++;
}
if( zLine[h]==delim ){
zLine[h++] = 0;
}
if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
}else{
azArg[nArg++] = &zLine[h];
while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
if( zLine[h] ) zLine[h++] = 0;
resolve_backslashes(azArg[nArg-1]);
}
}
azArg[nArg] = 0;
/* Process the input line.
*/
if( nArg==0 ) return 0; /* no tokens, no error */
n = strlen30(azArg[0]);
c = azArg[0][0];
clearTempFile(p);
|
| ︙ | ︙ | |||
15616 15617 15618 15619 15620 15621 15622 15623 15624 15625 15626 15627 |
}else
if( c=='d' && n>=3 && strncmp(azArg[0], "dbconfig", n)==0 ){
static const struct DbConfigChoices {
const char *zName;
int op;
} aDbConfig[] = {
{ "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY },
{ "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER },
{ "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
{ "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
{ "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE },
| > > > > > > > | | < < < < | | > > > | | < < | | | < < < < < < < < < < < < < < | | | | > > > | | | | | | | > | | < > | 16234 16235 16236 16237 16238 16239 16240 16241 16242 16243 16244 16245 16246 16247 16248 16249 16250 16251 16252 16253 16254 16255 16256 16257 16258 16259 16260 16261 16262 16263 16264 16265 16266 16267 16268 16269 16270 16271 16272 16273 16274 16275 16276 16277 16278 16279 16280 16281 16282 16283 16284 16285 16286 16287 16288 16289 16290 16291 16292 16293 16294 16295 16296 16297 16298 16299 16300 16301 16302 16303 16304 16305 16306 16307 16308 16309 16310 16311 16312 16313 16314 16315 16316 16317 16318 16319 16320 16321 16322 16323 16324 16325 16326 16327 16328 16329 16330 16331 16332 16333 16334 16335 16336 16337 16338 16339 16340 16341 16342 16343 16344 16345 16346 16347 16348 16349 16350 16351 16352 16353 16354 16355 16356 16357 16358 16359 16360 16361 16362 16363 16364 |
}else
if( c=='d' && n>=3 && strncmp(azArg[0], "dbconfig", n)==0 ){
static const struct DbConfigChoices {
const char *zName;
int op;
} aDbConfig[] = {
{ "defensive", SQLITE_DBCONFIG_DEFENSIVE },
{ "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL },
{ "dqs_dml", SQLITE_DBCONFIG_DQS_DML },
{ "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY },
{ "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG },
{ "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER },
{ "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW },
{ "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
{ "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE },
{ "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT },
{ "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
{ "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE },
{ "reset_database", SQLITE_DBCONFIG_RESET_DATABASE },
{ "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP },
{ "trusted_schema", SQLITE_DBCONFIG_TRUSTED_SCHEMA },
{ "writable_schema", SQLITE_DBCONFIG_WRITABLE_SCHEMA },
};
int ii, v;
open_db(p, 0);
for(ii=0; ii<ArraySize(aDbConfig); ii++){
if( nArg>1 && strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
if( nArg>=3 ){
sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0);
}
sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v);
utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off");
if( nArg>1 ) break;
}
if( nArg>1 && ii==ArraySize(aDbConfig) ){
utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]);
utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n");
}
}else
if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){
rc = shell_dbinfo_command(p, nArg, azArg);
}else
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){
open_db(p, 0);
rc = recoverDatabaseCmd(p, nArg, azArg);
}else
#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
char *zLike = 0;
char *zSql;
int i;
int savedShowHeader = p->showHeader;
int savedShellFlags = p->shellFlgs;
ShellClearFlag(p, SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo);
for(i=1; i<nArg; i++){
if( azArg[i][0]=='-' ){
const char *z = azArg[i]+1;
if( z[0]=='-' ) z++;
if( strcmp(z,"preserve-rowids")==0 ){
#ifdef SQLITE_OMIT_VIRTUALTABLE
raw_printf(stderr, "The --preserve-rowids option is not compatible"
" with SQLITE_OMIT_VIRTUALTABLE\n");
rc = 1;
sqlite3_free(zLike);
goto meta_command_exit;
#else
ShellSetFlag(p, SHFLG_PreserveRowid);
#endif
}else
if( strcmp(z,"newlines")==0 ){
ShellSetFlag(p, SHFLG_Newlines);
}else
{
raw_printf(stderr, "Unknown option \"%s\" on \".dump\"\n", azArg[i]);
rc = 1;
sqlite3_free(zLike);
goto meta_command_exit;
}
}else if( zLike ){
zLike = sqlite3_mprintf("%z OR name LIKE %Q ESCAPE '\\'",
zLike, azArg[i]);
}else{
zLike = sqlite3_mprintf("name LIKE %Q ESCAPE '\\'", azArg[i]);
}
}
open_db(p, 0);
/* When playing back a "dump", the content might appear in an order
** which causes immediate foreign key constraints to be violated.
** So disable foreign-key constraint enforcement to prevent problems. */
raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
raw_printf(p->out, "BEGIN TRANSACTION;\n");
p->writableSchema = 0;
p->showHeader = 0;
/* Set writable_schema=ON since doing so forces SQLite to initialize
** as much of the schema as it can even if the sqlite_schema table is
** corrupt. */
sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
p->nErr = 0;
if( zLike==0 ) zLike = sqlite3_mprintf("true");
zSql = sqlite3_mprintf(
"SELECT name, type, sql FROM sqlite_schema "
"WHERE (%s) AND type=='table'"
" AND sql NOT NULL"
" ORDER BY tbl_name='sqlite_sequence', rowid",
zLike
);
run_schema_dump_query(p,zSql);
sqlite3_free(zSql);
zSql = sqlite3_mprintf(
"SELECT sql FROM sqlite_schema "
"WHERE (%s) AND sql NOT NULL"
" AND type IN ('index','trigger','view')",
zLike
);
run_table_dump_query(p, zSql);
sqlite3_free(zSql);
sqlite3_free(zLike);
if( p->writableSchema ){
raw_printf(p->out, "PRAGMA writable_schema=OFF;\n");
p->writableSchema = 0;
}
sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0);
raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
|
| ︙ | ︙ | |||
15778 15779 15780 15781 15782 15783 15784 |
}else if( strcmp(azArg[1],"test")==0 ){
p->autoEQP = AUTOEQP_on;
p->autoEQPtest = 1;
}else if( strcmp(azArg[1],"trace")==0 ){
p->autoEQP = AUTOEQP_full;
p->autoEQPtrace = 1;
open_db(p, 0);
| | | 16390 16391 16392 16393 16394 16395 16396 16397 16398 16399 16400 16401 16402 16403 16404 |
}else if( strcmp(azArg[1],"test")==0 ){
p->autoEQP = AUTOEQP_on;
p->autoEQPtest = 1;
}else if( strcmp(azArg[1],"trace")==0 ){
p->autoEQP = AUTOEQP_full;
p->autoEQPtrace = 1;
open_db(p, 0);
sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0);
#endif
}else{
p->autoEQP = (u8)booleanValue(azArg[1]);
}
}else{
raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n");
|
| ︙ | ︙ | |||
15841 15842 15843 15844 15845 15846 15847 15848 15849 15850 15851 15852 15853 15854 15855 15856 15857 15858 15859 15860 15861 15862 15863 15864 |
/* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/
{ "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" },
{ "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" },
/* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/
{ "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" },
{ "has_moved", SQLITE_FCNTL_HAS_MOVED, "" },
{ "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" },
};
int filectrl = -1;
int iCtrl = -1;
sqlite3_int64 iRes = 0; /* Integer result to display if rc2==1 */
int isOk = 0; /* 0: usage 1: %lld 2: no-result */
int n2, i;
const char *zCmd = 0;
open_db(p, 0);
zCmd = nArg>=2 ? azArg[1] : "help";
/* The argument can optionally begin with "-" or "--" */
if( zCmd[0]=='-' && zCmd[1] ){
zCmd++;
if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
}
| > > > > > > > > > > > > | 16453 16454 16455 16456 16457 16458 16459 16460 16461 16462 16463 16464 16465 16466 16467 16468 16469 16470 16471 16472 16473 16474 16475 16476 16477 16478 16479 16480 16481 16482 16483 16484 16485 16486 16487 16488 |
/* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/
{ "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" },
{ "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" },
/* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/
{ "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" },
{ "has_moved", SQLITE_FCNTL_HAS_MOVED, "" },
{ "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" },
{ "reserve_bytes", SQLITE_FCNTL_RESERVE_BYTES, "[N]" },
};
int filectrl = -1;
int iCtrl = -1;
sqlite3_int64 iRes = 0; /* Integer result to display if rc2==1 */
int isOk = 0; /* 0: usage 1: %lld 2: no-result */
int n2, i;
const char *zCmd = 0;
const char *zSchema = 0;
open_db(p, 0);
zCmd = nArg>=2 ? azArg[1] : "help";
if( zCmd[0]=='-'
&& (strcmp(zCmd,"--schema")==0 || strcmp(zCmd,"-schema")==0)
&& nArg>=4
){
zSchema = azArg[2];
for(i=3; i<nArg; i++) azArg[i-2] = azArg[i];
nArg -= 2;
zCmd = azArg[1];
}
/* The argument can optionally begin with "-" or "--" */
if( zCmd[0]=='-' && zCmd[1] ){
zCmd++;
if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
}
|
| ︙ | ︙ | |||
15893 15894 15895 15896 15897 15898 15899 |
utf8_printf(stderr,"Error: unknown file-control: %s\n"
"Use \".filectrl --help\" for help\n", zCmd);
}else{
switch(filectrl){
case SQLITE_FCNTL_SIZE_LIMIT: {
if( nArg!=2 && nArg!=3 ) break;
iRes = nArg==3 ? integerValue(azArg[2]) : -1;
| | | | | | > > > > > > > > > > > > | 16517 16518 16519 16520 16521 16522 16523 16524 16525 16526 16527 16528 16529 16530 16531 16532 16533 16534 16535 16536 16537 16538 16539 16540 16541 16542 16543 16544 16545 16546 16547 16548 16549 16550 16551 16552 16553 16554 16555 16556 16557 16558 16559 16560 16561 16562 16563 16564 16565 16566 16567 16568 16569 16570 16571 16572 16573 16574 16575 16576 16577 16578 16579 16580 16581 16582 16583 |
utf8_printf(stderr,"Error: unknown file-control: %s\n"
"Use \".filectrl --help\" for help\n", zCmd);
}else{
switch(filectrl){
case SQLITE_FCNTL_SIZE_LIMIT: {
if( nArg!=2 && nArg!=3 ) break;
iRes = nArg==3 ? integerValue(azArg[2]) : -1;
sqlite3_file_control(p->db, zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
isOk = 1;
break;
}
case SQLITE_FCNTL_LOCK_TIMEOUT:
case SQLITE_FCNTL_CHUNK_SIZE: {
int x;
if( nArg!=3 ) break;
x = (int)integerValue(azArg[2]);
sqlite3_file_control(p->db, zSchema, filectrl, &x);
isOk = 2;
break;
}
case SQLITE_FCNTL_PERSIST_WAL:
case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
int x;
if( nArg!=2 && nArg!=3 ) break;
x = nArg==3 ? booleanValue(azArg[2]) : -1;
sqlite3_file_control(p->db, zSchema, filectrl, &x);
iRes = x;
isOk = 1;
break;
}
case SQLITE_FCNTL_HAS_MOVED: {
int x;
if( nArg!=2 ) break;
sqlite3_file_control(p->db, zSchema, filectrl, &x);
iRes = x;
isOk = 1;
break;
}
case SQLITE_FCNTL_TEMPFILENAME: {
char *z = 0;
if( nArg!=2 ) break;
sqlite3_file_control(p->db, zSchema, filectrl, &z);
if( z ){
utf8_printf(p->out, "%s\n", z);
sqlite3_free(z);
}
isOk = 2;
break;
}
case SQLITE_FCNTL_RESERVE_BYTES: {
int x;
if( nArg>=3 ){
x = atoi(azArg[2]);
sqlite3_file_control(p->db, zSchema, filectrl, &x);
}
x = -1;
sqlite3_file_control(p->db, zSchema, filectrl, &x);
utf8_printf(p->out,"%d\n", x);
isOk = 2;
break;
}
}
}
if( isOk==0 && iCtrl>=0 ){
utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
rc = 1;
}else if( isOk==1 ){
|
| ︙ | ︙ | |||
15967 15968 15969 15970 15971 15972 15973 |
rc = 1;
goto meta_command_exit;
}
open_db(p, 0);
rc = sqlite3_exec(p->db,
"SELECT sql FROM"
" (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
| | | | | | < < | > | | | | | < < | < < < > > | > > > | > > | > > > > | | | > | | > > > > > > > > > > > > > > | | > > > > > > > > | > > > > > > > > > > > > > > > > > > > > | | > | | > | | | | | | | | | | | | | > > > > | > | | < < < < < | > > > > | > > > | > > > > > > > | < | | > > > > < | | > | | > | > > > | | > | 16603 16604 16605 16606 16607 16608 16609 16610 16611 16612 16613 16614 16615 16616 16617 16618 16619 16620 16621 16622 16623 16624 16625 16626 16627 16628 16629 16630 16631 16632 16633 16634 16635 16636 16637 16638 16639 16640 16641 16642 16643 16644 16645 16646 16647 16648 16649 16650 16651 16652 16653 16654 16655 16656 16657 16658 16659 16660 16661 16662 16663 16664 16665 16666 16667 16668 16669 16670 16671 16672 16673 16674 16675 16676 16677 16678 16679 16680 16681 16682 16683 16684 16685 16686 16687 16688 16689 16690 16691 16692 16693 16694 16695 16696 16697 16698 16699 16700 16701 16702 16703 16704 16705 16706 16707 16708 16709 16710 16711 16712 16713 16714 16715 16716 16717 16718 16719 16720 16721 16722 16723 16724 16725 16726 16727 16728 16729 16730 16731 16732 16733 16734 16735 16736 16737 16738 16739 16740 16741 16742 16743 16744 16745 16746 16747 16748 16749 16750 16751 16752 16753 16754 16755 16756 16757 16758 16759 16760 16761 16762 16763 16764 16765 16766 16767 16768 16769 16770 16771 16772 16773 16774 16775 16776 16777 16778 16779 16780 16781 16782 16783 16784 16785 16786 16787 16788 16789 16790 16791 16792 16793 16794 16795 16796 16797 16798 16799 16800 16801 16802 16803 16804 16805 16806 16807 16808 16809 16810 16811 16812 16813 16814 16815 16816 16817 16818 16819 16820 16821 16822 16823 16824 16825 16826 16827 16828 16829 16830 16831 16832 16833 16834 16835 16836 16837 16838 16839 16840 16841 16842 16843 16844 16845 16846 16847 16848 16849 16850 16851 16852 16853 16854 16855 16856 16857 16858 16859 16860 16861 16862 16863 16864 16865 16866 16867 16868 16869 16870 16871 16872 16873 16874 16875 16876 16877 16878 16879 16880 16881 16882 16883 16884 |
rc = 1;
goto meta_command_exit;
}
open_db(p, 0);
rc = sqlite3_exec(p->db,
"SELECT sql FROM"
" (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
" FROM sqlite_schema UNION ALL"
" SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) "
"WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' "
"ORDER BY rowid",
callback, &data, &zErrMsg
);
if( rc==SQLITE_OK ){
sqlite3_stmt *pStmt;
rc = sqlite3_prepare_v2(p->db,
"SELECT rowid FROM sqlite_schema"
" WHERE name GLOB 'sqlite_stat[134]'",
-1, &pStmt, 0);
doStats = sqlite3_step(pStmt)==SQLITE_ROW;
sqlite3_finalize(pStmt);
}
if( doStats==0 ){
raw_printf(p->out, "/* No STAT tables available */\n");
}else{
raw_printf(p->out, "ANALYZE sqlite_schema;\n");
sqlite3_exec(p->db, "SELECT 'ANALYZE sqlite_schema'",
callback, &data, &zErrMsg);
data.cMode = data.mode = MODE_Insert;
data.zDestTable = "sqlite_stat1";
shell_exec(&data, "SELECT * FROM sqlite_stat1", &zErrMsg);
data.zDestTable = "sqlite_stat4";
shell_exec(&data, "SELECT * FROM sqlite_stat4", &zErrMsg);
raw_printf(p->out, "ANALYZE sqlite_schema;\n");
}
}else
if( c=='h' && strncmp(azArg[0], "headers", n)==0 ){
if( nArg==2 ){
p->showHeader = booleanValue(azArg[1]);
p->shellFlgs |= SHFLG_HeaderSet;
}else{
raw_printf(stderr, "Usage: .headers on|off\n");
rc = 1;
}
}else
if( c=='h' && strncmp(azArg[0], "help", n)==0 ){
if( nArg>=2 ){
n = showHelp(p->out, azArg[1]);
if( n==0 ){
utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
}
}else{
showHelp(p->out, 0);
}
}else
if( c=='i' && strncmp(azArg[0], "import", n)==0 ){
char *zTable = 0; /* Insert data into this table */
char *zFile = 0; /* Name of file to extra content from */
sqlite3_stmt *pStmt = NULL; /* A statement */
int nCol; /* Number of columns in the table */
int nByte; /* Number of bytes in an SQL string */
int i, j; /* Loop counters */
int needCommit; /* True to COMMIT or ROLLBACK at end */
int nSep; /* Number of bytes in p->colSeparator[] */
char *zSql; /* An SQL statement */
ImportCtx sCtx; /* Reader context */
char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
int eVerbose = 0; /* Larger for more console output */
int nSkip = 0; /* Initial lines to skip */
int useOutputMode = 1; /* Use output mode to determine separators */
memset(&sCtx, 0, sizeof(sCtx));
if( p->mode==MODE_Ascii ){
xRead = ascii_read_one_field;
}else{
xRead = csv_read_one_field;
}
for(i=1; i<nArg; i++){
char *z = azArg[i];
if( z[0]=='-' && z[1]=='-' ) z++;
if( z[0]!='-' ){
if( zFile==0 ){
zFile = z;
}else if( zTable==0 ){
zTable = z;
}else{
utf8_printf(p->out, "ERROR: extra argument: \"%s\". Usage:\n", z);
showHelp(p->out, "import");
rc = 1;
goto meta_command_exit;
}
}else if( strcmp(z,"-v")==0 ){
eVerbose++;
}else if( strcmp(z,"-skip")==0 && i<nArg-1 ){
nSkip = integerValue(azArg[++i]);
}else if( strcmp(z,"-ascii")==0 ){
sCtx.cColSep = SEP_Unit[0];
sCtx.cRowSep = SEP_Record[0];
xRead = ascii_read_one_field;
useOutputMode = 0;
}else if( strcmp(z,"-csv")==0 ){
sCtx.cColSep = ',';
sCtx.cRowSep = '\n';
xRead = csv_read_one_field;
useOutputMode = 0;
}else{
utf8_printf(p->out, "ERROR: unknown option: \"%s\". Usage:\n", z);
showHelp(p->out, "import");
rc = 1;
goto meta_command_exit;
}
}
if( zTable==0 ){
utf8_printf(p->out, "ERROR: missing %s argument. Usage:\n",
zFile==0 ? "FILE" : "TABLE");
showHelp(p->out, "import");
rc = 1;
goto meta_command_exit;
}
seenInterrupt = 0;
open_db(p, 0);
if( useOutputMode ){
/* If neither the --csv or --ascii options are specified, then set
** the column and row separator characters from the output mode. */
nSep = strlen30(p->colSeparator);
if( nSep==0 ){
raw_printf(stderr,
"Error: non-null column separator required for import\n");
rc = 1;
goto meta_command_exit;
}
if( nSep>1 ){
raw_printf(stderr,
"Error: multi-character column separators not allowed"
" for import\n");
rc = 1;
goto meta_command_exit;
}
nSep = strlen30(p->rowSeparator);
if( nSep==0 ){
raw_printf(stderr,
"Error: non-null row separator required for import\n");
rc = 1;
goto meta_command_exit;
}
if( nSep==2 && p->mode==MODE_Csv && strcmp(p->rowSeparator,SEP_CrLf)==0 ){
/* When importing CSV (only), if the row separator is set to the
** default output row separator, change it to the default input
** row separator. This avoids having to maintain different input
** and output row separators. */
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
nSep = strlen30(p->rowSeparator);
}
if( nSep>1 ){
raw_printf(stderr, "Error: multi-character row separators not allowed"
" for import\n");
rc = 1;
goto meta_command_exit;
}
sCtx.cColSep = p->colSeparator[0];
sCtx.cRowSep = p->rowSeparator[0];
}
sCtx.zFile = zFile;
sCtx.nLine = 1;
if( sCtx.zFile[0]=='|' ){
#ifdef SQLITE_OMIT_POPEN
raw_printf(stderr, "Error: pipes are not supported in this OS\n");
rc = 1;
goto meta_command_exit;
#else
sCtx.in = popen(sCtx.zFile+1, "r");
sCtx.zFile = "<pipe>";
sCtx.xCloser = pclose;
#endif
}else{
sCtx.in = fopen(sCtx.zFile, "rb");
sCtx.xCloser = fclose;
}
if( sCtx.in==0 ){
utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile);
rc = 1;
goto meta_command_exit;
}
if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
char zSep[2];
zSep[1] = 0;
zSep[0] = sCtx.cColSep;
utf8_printf(p->out, "Column separator ");
output_c_string(p->out, zSep);
utf8_printf(p->out, ", row separator ");
zSep[0] = sCtx.cRowSep;
output_c_string(p->out, zSep);
utf8_printf(p->out, "\n");
}
while( (nSkip--)>0 ){
while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
sCtx.nLine++;
}
zSql = sqlite3_mprintf("SELECT * FROM %s", zTable);
if( zSql==0 ){
import_cleanup(&sCtx);
shell_out_of_memory();
}
nByte = strlen30(zSql);
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */
if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
char *zCreate = sqlite3_mprintf("CREATE TABLE %s", zTable);
char cSep = '(';
while( xRead(&sCtx) ){
zCreate = sqlite3_mprintf("%z%c\n \"%w\" TEXT", zCreate, cSep, sCtx.z);
cSep = ',';
if( sCtx.cTerm!=sCtx.cColSep ) break;
}
if( cSep=='(' ){
sqlite3_free(zCreate);
import_cleanup(&sCtx);
utf8_printf(stderr,"%s: empty file\n", sCtx.zFile);
rc = 1;
goto meta_command_exit;
}
zCreate = sqlite3_mprintf("%z\n)", zCreate);
if( eVerbose>=1 ){
utf8_printf(p->out, "%s\n", zCreate);
}
rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
sqlite3_free(zCreate);
if( rc ){
utf8_printf(stderr, "CREATE TABLE %s(...) failed: %s\n", zTable,
sqlite3_errmsg(p->db));
import_cleanup(&sCtx);
rc = 1;
goto meta_command_exit;
}
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
}
sqlite3_free(zSql);
if( rc ){
if (pStmt) sqlite3_finalize(pStmt);
utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db));
import_cleanup(&sCtx);
rc = 1;
goto meta_command_exit;
}
nCol = sqlite3_column_count(pStmt);
sqlite3_finalize(pStmt);
pStmt = 0;
if( nCol==0 ) return 0; /* no columns, no error */
zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
if( zSql==0 ){
import_cleanup(&sCtx);
shell_out_of_memory();
}
sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\" VALUES(?", zTable);
j = strlen30(zSql);
for(i=1; i<nCol; i++){
zSql[j++] = ',';
zSql[j++] = '?';
}
zSql[j++] = ')';
zSql[j] = 0;
if( eVerbose>=2 ){
utf8_printf(p->out, "Insert using: %s\n", zSql);
}
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
if( rc ){
utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
if (pStmt) sqlite3_finalize(pStmt);
import_cleanup(&sCtx);
rc = 1;
goto meta_command_exit;
}
needCommit = sqlite3_get_autocommit(p->db);
if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
do{
int startLine = sCtx.nLine;
for(i=0; i<nCol; i++){
char *z = xRead(&sCtx);
|
| ︙ | ︙ | |||
16205 16206 16207 16208 16209 16210 16211 16212 16213 16214 16215 |
}
if( i>=nCol ){
sqlite3_step(pStmt);
rc = sqlite3_reset(pStmt);
if( rc!=SQLITE_OK ){
utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
startLine, sqlite3_errmsg(p->db));
}
}
}while( sCtx.cTerm!=EOF );
| > > > | < > > > > > > > > > > > > > > | > | > > > > > > > < < < < < > > > > > > > > > > | | | > | 16913 16914 16915 16916 16917 16918 16919 16920 16921 16922 16923 16924 16925 16926 16927 16928 16929 16930 16931 16932 16933 16934 16935 16936 16937 16938 16939 16940 16941 16942 16943 16944 16945 16946 16947 16948 16949 16950 16951 16952 16953 16954 16955 16956 16957 16958 16959 16960 16961 16962 16963 16964 16965 16966 16967 16968 16969 16970 16971 16972 16973 16974 16975 16976 16977 16978 16979 16980 16981 16982 16983 16984 16985 16986 16987 16988 16989 16990 16991 16992 16993 16994 16995 16996 16997 16998 16999 17000 17001 17002 17003 17004 17005 17006 17007 17008 17009 17010 17011 17012 17013 17014 17015 17016 17017 17018 17019 17020 17021 17022 17023 17024 17025 17026 17027 17028 17029 17030 17031 17032 17033 17034 |
}
if( i>=nCol ){
sqlite3_step(pStmt);
rc = sqlite3_reset(pStmt);
if( rc!=SQLITE_OK ){
utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
startLine, sqlite3_errmsg(p->db));
sCtx.nErr++;
}else{
sCtx.nRow++;
}
}
}while( sCtx.cTerm!=EOF );
import_cleanup(&sCtx);
sqlite3_finalize(pStmt);
if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
if( eVerbose>0 ){
utf8_printf(p->out,
"Added %d rows with %d errors using %d lines of input\n",
sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
}
}else
#ifndef SQLITE_UNTESTABLE
if( c=='i' && strncmp(azArg[0], "imposter", n)==0 ){
char *zSql;
char *zCollist = 0;
sqlite3_stmt *pStmt;
int tnum = 0;
int isWO = 0; /* True if making an imposter of a WITHOUT ROWID table */
int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */
int i;
if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){
utf8_printf(stderr, "Usage: .imposter INDEX IMPOSTER\n"
" .imposter off\n");
/* Also allowed, but not documented:
**
** .imposter TABLE IMPOSTER
**
** where TABLE is a WITHOUT ROWID table. In that case, the
** imposter is another WITHOUT ROWID table with the columns in
** storage order. */
rc = 1;
goto meta_command_exit;
}
open_db(p, 0);
if( nArg==2 ){
sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1);
goto meta_command_exit;
}
zSql = sqlite3_mprintf(
"SELECT rootpage, 0 FROM sqlite_schema"
" WHERE name='%q' AND type='index'"
"UNION ALL "
"SELECT rootpage, 1 FROM sqlite_schema"
" WHERE name='%q' AND type='table'"
" AND sql LIKE '%%without%%rowid%%'",
azArg[1], azArg[1]
);
sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
if( sqlite3_step(pStmt)==SQLITE_ROW ){
tnum = sqlite3_column_int(pStmt, 0);
isWO = sqlite3_column_int(pStmt, 1);
}
sqlite3_finalize(pStmt);
zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]);
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
i = 0;
while( sqlite3_step(pStmt)==SQLITE_ROW ){
char zLabel[20];
const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
i++;
if( zCol==0 ){
if( sqlite3_column_int(pStmt,1)==-1 ){
zCol = "_ROWID_";
}else{
sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
zCol = zLabel;
}
}
if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
lenPK = (int)strlen(zCollist);
}
if( zCollist==0 ){
zCollist = sqlite3_mprintf("\"%w\"", zCol);
}else{
zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol);
}
}
sqlite3_finalize(pStmt);
if( i==0 || tnum==0 ){
utf8_printf(stderr, "no such index: \"%s\"\n", azArg[1]);
rc = 1;
sqlite3_free(zCollist);
goto meta_command_exit;
}
if( lenPK==0 ) lenPK = 100000;
zSql = sqlite3_mprintf(
"CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID",
azArg[2], zCollist, lenPK, zCollist);
sqlite3_free(zCollist);
rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum);
if( rc==SQLITE_OK ){
rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0);
if( rc ){
utf8_printf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db));
}else{
utf8_printf(stdout, "%s;\n", zSql);
raw_printf(stdout,
"WARNING: writing to an imposter table will corrupt the \"%s\" %s!\n",
azArg[1], isWO ? "table" : "index"
);
}
}else{
raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
rc = 1;
}
sqlite3_free(zSql);
|
| ︙ | ︙ | |||
16421 16422 16423 16424 16425 16426 16427 16428 16429 16430 16431 16432 16433 16434 |
int n2 = strlen30(zMode);
int c2 = zMode[0];
if( c2=='l' && n2>2 && strncmp(azArg[1],"lines",n2)==0 ){
p->mode = MODE_Line;
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
}else if( c2=='c' && strncmp(azArg[1],"columns",n2)==0 ){
p->mode = MODE_Column;
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
}else if( c2=='l' && n2>2 && strncmp(azArg[1],"list",n2)==0 ){
p->mode = MODE_List;
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column);
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
}else if( c2=='h' && strncmp(azArg[1],"html",n2)==0 ){
p->mode = MODE_Html;
| > > > | 17159 17160 17161 17162 17163 17164 17165 17166 17167 17168 17169 17170 17171 17172 17173 17174 17175 |
int n2 = strlen30(zMode);
int c2 = zMode[0];
if( c2=='l' && n2>2 && strncmp(azArg[1],"lines",n2)==0 ){
p->mode = MODE_Line;
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
}else if( c2=='c' && strncmp(azArg[1],"columns",n2)==0 ){
p->mode = MODE_Column;
if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){
p->showHeader = 1;
}
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
}else if( c2=='l' && n2>2 && strncmp(azArg[1],"list",n2)==0 ){
p->mode = MODE_List;
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column);
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
}else if( c2=='h' && strncmp(azArg[1],"html",n2)==0 ){
p->mode = MODE_Html;
|
| ︙ | ︙ | |||
16444 16445 16446 16447 16448 16449 16450 16451 16452 16453 16454 16455 16456 16457 16458 |
p->mode = MODE_List;
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab);
}else if( c2=='i' && strncmp(azArg[1],"insert",n2)==0 ){
p->mode = MODE_Insert;
set_table_name(p, nArg>=3 ? azArg[2] : "table");
}else if( c2=='q' && strncmp(azArg[1],"quote",n2)==0 ){
p->mode = MODE_Quote;
}else if( c2=='a' && strncmp(azArg[1],"ascii",n2)==0 ){
p->mode = MODE_Ascii;
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit);
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record);
}else if( nArg==1 ){
raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]);
}else{
raw_printf(stderr, "Error: mode should be one of: "
| > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 17185 17186 17187 17188 17189 17190 17191 17192 17193 17194 17195 17196 17197 17198 17199 17200 17201 17202 17203 17204 17205 17206 17207 17208 17209 17210 17211 17212 17213 17214 17215 17216 17217 17218 17219 17220 17221 17222 17223 17224 17225 17226 17227 17228 17229 17230 17231 17232 17233 17234 17235 17236 17237 17238 17239 17240 17241 17242 17243 17244 17245 17246 17247 17248 17249 17250 17251 17252 17253 17254 17255 17256 17257 17258 17259 17260 17261 17262 17263 17264 17265 17266 17267 17268 17269 17270 17271 17272 17273 17274 17275 17276 17277 17278 17279 17280 17281 17282 17283 17284 17285 17286 17287 17288 17289 17290 |
p->mode = MODE_List;
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab);
}else if( c2=='i' && strncmp(azArg[1],"insert",n2)==0 ){
p->mode = MODE_Insert;
set_table_name(p, nArg>=3 ? azArg[2] : "table");
}else if( c2=='q' && strncmp(azArg[1],"quote",n2)==0 ){
p->mode = MODE_Quote;
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
}else if( c2=='a' && strncmp(azArg[1],"ascii",n2)==0 ){
p->mode = MODE_Ascii;
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit);
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record);
}else if( c2=='m' && strncmp(azArg[1],"markdown",n2)==0 ){
p->mode = MODE_Markdown;
}else if( c2=='t' && strncmp(azArg[1],"table",n2)==0 ){
p->mode = MODE_Table;
}else if( c2=='b' && strncmp(azArg[1],"box",n2)==0 ){
p->mode = MODE_Box;
}else if( c2=='j' && strncmp(azArg[1],"json",n2)==0 ){
p->mode = MODE_Json;
}else if( nArg==1 ){
raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]);
}else{
raw_printf(stderr, "Error: mode should be one of: "
"ascii box column csv html insert json line list markdown "
"quote table tabs tcl\n");
rc = 1;
}
p->cMode = p->mode;
}else
if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 ){
if( nArg==2 ){
sqlite3_snprintf(sizeof(p->nullValue), p->nullValue,
"%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]);
}else{
raw_printf(stderr, "Usage: .nullvalue STRING\n");
rc = 1;
}
}else
#ifdef SQLITE_DEBUG
if( c=='o' && strcmp(azArg[0],"oom")==0 ){
int i;
for(i=1; i<nArg; i++){
const char *z = azArg[i];
if( z[0]=='-' && z[1]=='-' ) z++;
if( strcmp(z,"-repeat")==0 ){
if( i==nArg-1 ){
raw_printf(p->out, "missing argument on \"%s\"\n", azArg[i]);
rc = 1;
}else{
oomRepeat = (int)integerValue(azArg[++i]);
}
}else if( IsDigit(z[0]) ){
oomCounter = (int)integerValue(azArg[i]);
}else{
raw_printf(p->out, "unknown argument: \"%s\"\n", azArg[i]);
raw_printf(p->out, "Usage: .oom [--repeat N] [M]\n");
rc = 1;
}
}
if( rc==0 ){
raw_printf(p->out, "oomCounter = %d\n", oomCounter);
raw_printf(p->out, "oomRepeat = %d\n", oomRepeat);
}
}else
#endif /* SQLITE_DEBUG */
if( c=='o' && strncmp(azArg[0], "open", n)==0 && n>=2 ){
char *zNewFilename; /* Name of the database file to open */
int iName = 1; /* Index in azArg[] of the filename */
int newFlag = 0; /* True to delete file before opening */
/* Close the existing database */
session_close_all(p);
close_db(p->db);
p->db = 0;
p->zDbFilename = 0;
sqlite3_free(p->zFreeOnClose);
p->zFreeOnClose = 0;
p->openMode = SHELL_OPEN_UNSPEC;
p->openFlags = 0;
p->szMax = 0;
/* Check for command-line arguments */
for(iName=1; iName<nArg && azArg[iName][0]=='-'; iName++){
const char *z = azArg[iName];
if( optionMatch(z,"new") ){
newFlag = 1;
#ifdef SQLITE_HAVE_ZLIB
}else if( optionMatch(z, "zip") ){
p->openMode = SHELL_OPEN_ZIPFILE;
#endif
}else if( optionMatch(z, "append") ){
p->openMode = SHELL_OPEN_APPENDVFS;
}else if( optionMatch(z, "readonly") ){
p->openMode = SHELL_OPEN_READONLY;
}else if( optionMatch(z, "nofollow") ){
p->openFlags |= SQLITE_OPEN_NOFOLLOW;
#ifdef SQLITE_ENABLE_DESERIALIZE
}else if( optionMatch(z, "deserialize") ){
p->openMode = SHELL_OPEN_DESERIALIZE;
}else if( optionMatch(z, "hexdb") ){
p->openMode = SHELL_OPEN_HEXDB;
}else if( optionMatch(z, "maxsize") && iName+1<nArg ){
p->szMax = integerValue(azArg[++iName]);
|
| ︙ | ︙ | |||
16532 16533 16534 16535 16536 16537 16538 |
}
}else
if( (c=='o'
&& (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0))
|| (c=='e' && n==5 && strcmp(azArg[0],"excel")==0)
){
| | > > > > > | < > | | < | > > | > > > > > > > > > > | | | | | > > > | < > > > > < < > | > > > > > < < < < | 17315 17316 17317 17318 17319 17320 17321 17322 17323 17324 17325 17326 17327 17328 17329 17330 17331 17332 17333 17334 17335 17336 17337 17338 17339 17340 17341 17342 17343 17344 17345 17346 17347 17348 17349 17350 17351 17352 17353 17354 17355 17356 17357 17358 17359 17360 17361 17362 17363 17364 17365 17366 17367 17368 17369 17370 17371 17372 17373 17374 17375 17376 17377 17378 17379 17380 17381 17382 17383 17384 17385 17386 17387 17388 17389 17390 17391 17392 17393 17394 17395 17396 17397 17398 17399 17400 17401 17402 17403 17404 17405 17406 17407 17408 17409 17410 17411 17412 17413 17414 17415 17416 17417 17418 17419 17420 17421 17422 17423 17424 17425 17426 17427 17428 17429 17430 17431 17432 17433 17434 17435 |
}
}else
if( (c=='o'
&& (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0))
|| (c=='e' && n==5 && strcmp(azArg[0],"excel")==0)
){
const char *zFile = 0;
int bTxtMode = 0;
int i;
int eMode = 0;
int bBOM = 0;
int bOnce = 0; /* 0: .output, 1: .once, 2: .excel */
if( c=='e' ){
eMode = 'x';
bOnce = 2;
}else if( strncmp(azArg[0],"once",n)==0 ){
bOnce = 1;
}
for(i=1; i<nArg; i++){
char *z = azArg[i];
if( z[0]=='-' ){
if( z[1]=='-' ) z++;
if( strcmp(z,"-bom")==0 ){
bBOM = 1;
}else if( c!='e' && strcmp(z,"-x")==0 ){
eMode = 'x'; /* spreadsheet */
}else if( c!='e' && strcmp(z,"-e")==0 ){
eMode = 'e'; /* text editor */
}else{
utf8_printf(p->out, "ERROR: unknown option: \"%s\". Usage:\n",
azArg[i]);
showHelp(p->out, azArg[0]);
rc = 1;
goto meta_command_exit;
}
}else if( zFile==0 ){
zFile = z;
}else{
utf8_printf(p->out,"ERROR: extra parameter: \"%s\". Usage:\n",
azArg[i]);
showHelp(p->out, azArg[0]);
rc = 1;
goto meta_command_exit;
}
}
if( zFile==0 ) zFile = "stdout";
if( bOnce ){
p->outCount = 2;
}else{
p->outCount = 0;
}
output_reset(p);
#ifndef SQLITE_NOHAVE_SYSTEM
if( eMode=='e' || eMode=='x' ){
p->doXdgOpen = 1;
outputModePush(p);
if( eMode=='x' ){
/* spreadsheet mode. Output as CSV. */
newTempFile(p, "csv");
ShellClearFlag(p, SHFLG_Echo);
p->mode = MODE_Csv;
sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf);
}else{
/* text editor mode */
newTempFile(p, "txt");
bTxtMode = 1;
}
zFile = p->zTempFile;
}
#endif /* SQLITE_NOHAVE_SYSTEM */
if( zFile[0]=='|' ){
#ifdef SQLITE_OMIT_POPEN
raw_printf(stderr, "Error: pipes are not supported in this OS\n");
rc = 1;
p->out = stdout;
#else
p->out = popen(zFile + 1, "w");
if( p->out==0 ){
utf8_printf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1);
p->out = stdout;
rc = 1;
}else{
if( bBOM ) fprintf(p->out,"\357\273\277");
sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
}
#endif
}else{
p->out = output_file_open(zFile, bTxtMode);
if( p->out==0 ){
if( strcmp(zFile,"off")!=0 ){
utf8_printf(stderr,"Error: cannot write to \"%s\"\n", zFile);
}
p->out = stdout;
rc = 1;
} else {
if( bBOM ) fprintf(p->out,"\357\273\277");
sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
}
}
}else
if( c=='p' && n>=3 && strncmp(azArg[0], "parameter", n)==0 ){
open_db(p,0);
if( nArg<=1 ) goto parameter_syntax_error;
/* .parameter clear
** Clear all bind parameters by dropping the TEMP table that holds them.
*/
if( nArg==2 && strcmp(azArg[1],"clear")==0 ){
sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp.sqlite_parameters;",
0, 0, 0);
}else
/* .parameter list
** List all bind parameters.
*/
if( nArg==2 && strcmp(azArg[1],"list")==0 ){
sqlite3_stmt *pStmt = 0;
|
| ︙ | ︙ | |||
16891 16892 16893 16894 16895 16896 16897 |
}else{
raw_printf(stderr, "Usage: .schema ?--indent? ?LIKE-PATTERN?\n");
rc = 1;
goto meta_command_exit;
}
}
if( zName!=0 ){
| | > | > > | | 17696 17697 17698 17699 17700 17701 17702 17703 17704 17705 17706 17707 17708 17709 17710 17711 17712 17713 17714 17715 17716 17717 17718 17719 17720 17721 17722 17723 |
}else{
raw_printf(stderr, "Usage: .schema ?--indent? ?LIKE-PATTERN?\n");
rc = 1;
goto meta_command_exit;
}
}
if( zName!=0 ){
int isSchema = sqlite3_strlike(zName, "sqlite_master", '\\')==0
|| sqlite3_strlike(zName, "sqlite_schema", '\\')==0
|| sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0
|| sqlite3_strlike(zName,"sqlite_temp_schema", '\\')==0;
if( isSchema ){
char *new_argv[2], *new_colv[2];
new_argv[0] = sqlite3_mprintf(
"CREATE TABLE %s (\n"
" type text,\n"
" name text,\n"
" tbl_name text,\n"
" rootpage integer,\n"
" sql text\n"
")", zName);
new_argv[1] = 0;
new_colv[0] = "sql";
new_colv[1] = 0;
callback(&data, 1, new_argv, new_colv);
sqlite3_free(new_argv[0]);
}
}
|
| ︙ | ︙ | |||
16939 16940 16941 16942 16943 16944 16945 |
}
appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0);
appendText(&sSelect, zScNum, 0);
appendText(&sSelect, " AS snum, ", 0);
appendText(&sSelect, zDb, '\'');
appendText(&sSelect, " AS sname FROM ", 0);
appendText(&sSelect, zDb, quoteChar(zDb));
| | | | > | 17747 17748 17749 17750 17751 17752 17753 17754 17755 17756 17757 17758 17759 17760 17761 17762 17763 17764 17765 17766 17767 17768 17769 |
}
appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0);
appendText(&sSelect, zScNum, 0);
appendText(&sSelect, " AS snum, ", 0);
appendText(&sSelect, zDb, '\'');
appendText(&sSelect, " AS sname FROM ", 0);
appendText(&sSelect, zDb, quoteChar(zDb));
appendText(&sSelect, ".sqlite_schema", 0);
}
sqlite3_finalize(pStmt);
#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS
if( zName ){
appendText(&sSelect,
" UNION ALL SELECT shell_module_schema(name),"
" 'table', name, name, name, 9e+99, 'main' FROM pragma_module_list",
0);
}
#endif
appendText(&sSelect, ") WHERE ", 0);
if( zName ){
char *zQarg = sqlite3_mprintf("%Q", zName);
int bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0 ||
strchr(zName, '[') != 0;
|
| ︙ | ︙ | |||
17045 17046 17047 17048 17049 17050 17051 |
*/
if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){
FILE *out = 0;
if( nCmd!=2 ) goto session_syntax_error;
if( pSession->p==0 ) goto session_not_open;
out = fopen(azCmd[1], "wb");
if( out==0 ){
| | > | 17854 17855 17856 17857 17858 17859 17860 17861 17862 17863 17864 17865 17866 17867 17868 17869 |
*/
if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){
FILE *out = 0;
if( nCmd!=2 ) goto session_syntax_error;
if( pSession->p==0 ) goto session_not_open;
out = fopen(azCmd[1], "wb");
if( out==0 ){
utf8_printf(stderr, "ERROR: cannot open \"%s\" for writing\n",
azCmd[1]);
}else{
int szChng;
void *pChng;
if( azCmd[0][0]=='c' ){
rc = sqlite3session_changeset(pSession->p, &szChng, &pChng);
}else{
rc = sqlite3session_patchset(pSession->p, &szChng, &pChng);
|
| ︙ | ︙ | |||
17366 17367 17368 17369 17370 17371 17372 |
}else
if( strcmp(z,"debug")==0 ){
bDebug = 1;
}else
{
utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n",
azArg[i], azArg[0]);
| | < | | | | | | < | 18176 18177 18178 18179 18180 18181 18182 18183 18184 18185 18186 18187 18188 18189 18190 18191 18192 18193 18194 18195 18196 18197 18198 18199 18200 18201 18202 18203 18204 18205 18206 18207 18208 18209 18210 18211 18212 18213 18214 18215 18216 18217 18218 18219 18220 18221 18222 18223 18224 18225 18226 18227 18228 18229 18230 18231 18232 18233 18234 18235 18236 |
}else
if( strcmp(z,"debug")==0 ){
bDebug = 1;
}else
{
utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n",
azArg[i], azArg[0]);
showHelp(p->out, azArg[0]);
rc = 1;
goto meta_command_exit;
}
}else if( zLike ){
raw_printf(stderr, "Usage: .sha3sum ?OPTIONS? ?LIKE-PATTERN?\n");
rc = 1;
goto meta_command_exit;
}else{
zLike = z;
bSeparate = 1;
if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1;
}
}
if( bSchema ){
zSql = "SELECT lower(name) FROM sqlite_schema"
" WHERE type='table' AND coalesce(rootpage,0)>1"
" UNION ALL SELECT 'sqlite_schema'"
" ORDER BY 1 collate nocase";
}else{
zSql = "SELECT lower(name) FROM sqlite_schema"
" WHERE type='table' AND coalesce(rootpage,0)>1"
" AND name NOT LIKE 'sqlite_%'"
" ORDER BY 1 collate nocase";
}
sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
initText(&sQuery);
initText(&sSql);
appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0);
zSep = "VALUES(";
while( SQLITE_ROW==sqlite3_step(pStmt) ){
const char *zTab = (const char*)sqlite3_column_text(pStmt,0);
if( zLike && sqlite3_strlike(zLike, zTab, 0)!=0 ) continue;
if( strncmp(zTab, "sqlite_",7)!=0 ){
appendText(&sQuery,"SELECT * FROM ", 0);
appendText(&sQuery,zTab,'"');
appendText(&sQuery," NOT INDEXED;", 0);
}else if( strcmp(zTab, "sqlite_schema")==0 ){
appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema"
" ORDER BY name;", 0);
}else if( strcmp(zTab, "sqlite_sequence")==0 ){
appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence"
" ORDER BY name;", 0);
}else if( strcmp(zTab, "sqlite_stat1")==0 ){
appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1"
" ORDER BY tbl,idx;", 0);
}else if( strcmp(zTab, "sqlite_stat4")==0 ){
appendText(&sQuery, "SELECT * FROM ", 0);
appendText(&sQuery, zTab, 0);
appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0);
}
appendText(&sSql, zSep, 0);
appendText(&sSql, sQuery.z, '\'');
sQuery.n = 0;
|
| ︙ | ︙ | |||
17500 17501 17502 17503 17504 17505 17506 |
output_c_string(p->out, p->colSeparator);
raw_printf(p->out, "\n");
utf8_printf(p->out,"%12.12s: ", "rowseparator");
output_c_string(p->out, p->rowSeparator);
raw_printf(p->out, "\n");
utf8_printf(p->out, "%12.12s: %s\n","stats", azBool[p->statsOn!=0]);
utf8_printf(p->out, "%12.12s: ", "width");
| | | 18308 18309 18310 18311 18312 18313 18314 18315 18316 18317 18318 18319 18320 18321 18322 |
output_c_string(p->out, p->colSeparator);
raw_printf(p->out, "\n");
utf8_printf(p->out,"%12.12s: ", "rowseparator");
output_c_string(p->out, p->rowSeparator);
raw_printf(p->out, "\n");
utf8_printf(p->out, "%12.12s: %s\n","stats", azBool[p->statsOn!=0]);
utf8_printf(p->out, "%12.12s: ", "width");
for (i=0;i<p->nWidth;i++) {
raw_printf(p->out, "%d ", p->colWidth[i]);
}
raw_printf(p->out, "\n");
utf8_printf(p->out, "%12.12s: %s\n", "filename",
p->zDbFilename ? p->zDbFilename : "");
}else
|
| ︙ | ︙ | |||
17557 17558 17559 17560 17561 17562 17563 |
appendText(&s, "SELECT name FROM ", 0);
}else{
appendText(&s, "SELECT ", 0);
appendText(&s, zDbName, '\'');
appendText(&s, "||'.'||name FROM ", 0);
}
appendText(&s, zDbName, '"');
| | | 18365 18366 18367 18368 18369 18370 18371 18372 18373 18374 18375 18376 18377 18378 18379 |
appendText(&s, "SELECT name FROM ", 0);
}else{
appendText(&s, "SELECT ", 0);
appendText(&s, zDbName, '\'');
appendText(&s, "||'.'||name FROM ", 0);
}
appendText(&s, zDbName, '"');
appendText(&s, ".sqlite_schema ", 0);
if( c=='t' ){
appendText(&s," WHERE type IN ('table','view')"
" AND name NOT LIKE 'sqlite_%'"
" AND name LIKE ?1", 0);
}else{
appendText(&s," WHERE type='index'"
" AND tbl_name LIKE ?1", 0);
|
| ︙ | ︙ | |||
17646 17647 17648 17649 17650 17651 17652 |
#ifndef SQLITE_UNTESTABLE
if( c=='t' && n>=8 && strncmp(azArg[0], "testctrl", n)==0 ){
static const struct {
const char *zCtrlName; /* Name of a test-control option */
int ctrlCode; /* Integer code for that option */
const char *zUsage; /* Usage notes */
} aCtrl[] = {
| | | | | | > | | | | | | | | < | | | | 18454 18455 18456 18457 18458 18459 18460 18461 18462 18463 18464 18465 18466 18467 18468 18469 18470 18471 18472 18473 18474 18475 18476 18477 18478 18479 18480 18481 18482 18483 18484 18485 18486 |
#ifndef SQLITE_UNTESTABLE
if( c=='t' && n>=8 && strncmp(azArg[0], "testctrl", n)==0 ){
static const struct {
const char *zCtrlName; /* Name of a test-control option */
int ctrlCode; /* Integer code for that option */
const char *zUsage; /* Usage notes */
} aCtrl[] = {
{ "always", SQLITE_TESTCTRL_ALWAYS, "BOOLEAN" },
{ "assert", SQLITE_TESTCTRL_ASSERT, "BOOLEAN" },
/*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS, "" },*/
/*{ "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, "" },*/
{ "byteorder", SQLITE_TESTCTRL_BYTEORDER, "" },
{ "extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,"BOOLEAN" },
/*{ "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, "" },*/
{ "imposter", SQLITE_TESTCTRL_IMPOSTER, "SCHEMA ON/OFF ROOTPAGE"},
{ "internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS, "" },
{ "localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,"BOOLEAN" },
{ "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT, "BOOLEAN" },
{ "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS, "DISABLE-MASK" },
#ifdef YYCOVERAGE
{ "parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE, "" },
#endif
{ "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE, "OFFSET " },
{ "prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE, "" },
{ "prng_save", SQLITE_TESTCTRL_PRNG_SAVE, "" },
{ "prng_seed", SQLITE_TESTCTRL_PRNG_SEED, "SEED ?db?" },
};
int testctrl = -1;
int iCtrl = -1;
int rc2 = 0; /* 0: usage. 1: %d 2: %x 3: no-output */
int isOk = 0;
int i, n2;
const char *zCmd = 0;
|
| ︙ | ︙ | |||
17717 17718 17719 17720 17721 17722 17723 |
utf8_printf(stderr,"Error: unknown test-control: %s\n"
"Use \".testctrl --help\" for help\n", zCmd);
}else{
switch(testctrl){
/* sqlite3_test_control(int, db, int) */
case SQLITE_TESTCTRL_OPTIMIZATIONS:
| < | 18525 18526 18527 18528 18529 18530 18531 18532 18533 18534 18535 18536 18537 18538 |
utf8_printf(stderr,"Error: unknown test-control: %s\n"
"Use \".testctrl --help\" for help\n", zCmd);
}else{
switch(testctrl){
/* sqlite3_test_control(int, db, int) */
case SQLITE_TESTCTRL_OPTIMIZATIONS:
if( nArg==3 ){
int opt = (int)strtol(azArg[2], 0, 0);
rc2 = sqlite3_test_control(testctrl, p->db, opt);
isOk = 3;
}
break;
|
| ︙ | ︙ | |||
17744 17745 17746 17747 17748 17749 17750 17751 17752 17753 17754 |
case SQLITE_TESTCTRL_PENDING_BYTE:
if( nArg==3 ){
unsigned int opt = (unsigned int)integerValue(azArg[2]);
rc2 = sqlite3_test_control(testctrl, opt);
isOk = 3;
}
break;
/* sqlite3_test_control(int, int) */
case SQLITE_TESTCTRL_ASSERT:
case SQLITE_TESTCTRL_ALWAYS:
| > > > > > > > > > > > > > > > > > > > > > < > > > > > > | 18551 18552 18553 18554 18555 18556 18557 18558 18559 18560 18561 18562 18563 18564 18565 18566 18567 18568 18569 18570 18571 18572 18573 18574 18575 18576 18577 18578 18579 18580 18581 18582 18583 18584 18585 18586 18587 18588 18589 18590 18591 18592 18593 18594 18595 18596 18597 18598 18599 18600 18601 18602 18603 18604 18605 18606 18607 18608 18609 18610 18611 |
case SQLITE_TESTCTRL_PENDING_BYTE:
if( nArg==3 ){
unsigned int opt = (unsigned int)integerValue(azArg[2]);
rc2 = sqlite3_test_control(testctrl, opt);
isOk = 3;
}
break;
/* sqlite3_test_control(int, int, sqlite3*) */
case SQLITE_TESTCTRL_PRNG_SEED:
if( nArg==3 || nArg==4 ){
int ii = (int)integerValue(azArg[2]);
sqlite3 *db;
if( ii==0 && strcmp(azArg[2],"random")==0 ){
sqlite3_randomness(sizeof(ii),&ii);
printf("-- random seed: %d\n", ii);
}
if( nArg==3 ){
db = 0;
}else{
db = p->db;
/* Make sure the schema has been loaded */
sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0);
}
rc2 = sqlite3_test_control(testctrl, ii, db);
isOk = 3;
}
break;
/* sqlite3_test_control(int, int) */
case SQLITE_TESTCTRL_ASSERT:
case SQLITE_TESTCTRL_ALWAYS:
if( nArg==3 ){
int opt = booleanValue(azArg[2]);
rc2 = sqlite3_test_control(testctrl, opt);
isOk = 1;
}
break;
/* sqlite3_test_control(int, int) */
case SQLITE_TESTCTRL_LOCALTIME_FAULT:
case SQLITE_TESTCTRL_NEVER_CORRUPT:
if( nArg==3 ){
int opt = booleanValue(azArg[2]);
rc2 = sqlite3_test_control(testctrl, opt);
isOk = 3;
}
break;
/* sqlite3_test_control(sqlite3*) */
case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
rc2 = sqlite3_test_control(testctrl, p->db);
isOk = 3;
break;
case SQLITE_TESTCTRL_IMPOSTER:
if( nArg==5 ){
rc2 = sqlite3_test_control(testctrl, p->db,
azArg[2],
integerValue(azArg[3]),
integerValue(azArg[4]));
|
| ︙ | ︙ | |||
17786 17787 17788 17789 17790 17791 17792 |
sqlite3_test_control(testctrl, p->out);
isOk = 3;
}
#endif
}
}
if( isOk==0 && iCtrl>=0 ){
| | | 18619 18620 18621 18622 18623 18624 18625 18626 18627 18628 18629 18630 18631 18632 18633 |
sqlite3_test_control(testctrl, p->out);
isOk = 3;
}
#endif
}
}
if( isOk==0 && iCtrl>=0 ){
utf8_printf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
rc = 1;
}else if( isOk==1 ){
raw_printf(p->out, "%d\n", rc2);
}else if( isOk==2 ){
raw_printf(p->out, "0x%08x\n", rc2);
}
}else
|
| ︙ | ︙ | |||
17863 17864 17865 17866 17867 17868 17869 17870 17871 17872 17873 17874 17875 17876 17877 17878 17879 17880 17881 17882 17883 17884 |
sqlite3_trace_v2(p->db, 0, 0, 0);
}else{
if( mType==0 ) mType = SQLITE_TRACE_STMT;
sqlite3_trace_v2(p->db, mType, sql_trace_callback, p);
}
}else
#endif /* !defined(SQLITE_OMIT_TRACE) */
#if SQLITE_USER_AUTHENTICATION
if( c=='u' && strncmp(azArg[0], "user", n)==0 ){
if( nArg<2 ){
raw_printf(stderr, "Usage: .user SUBCOMMAND ...\n");
rc = 1;
goto meta_command_exit;
}
open_db(p, 0);
if( strcmp(azArg[1],"login")==0 ){
if( nArg!=4 ){
raw_printf(stderr, "Usage: .user login USER PASSWORD\n");
rc = 1;
goto meta_command_exit;
}
| > > > > > > > > > > > > > > > > > > > > > > > > > | > | 18696 18697 18698 18699 18700 18701 18702 18703 18704 18705 18706 18707 18708 18709 18710 18711 18712 18713 18714 18715 18716 18717 18718 18719 18720 18721 18722 18723 18724 18725 18726 18727 18728 18729 18730 18731 18732 18733 18734 18735 18736 18737 18738 18739 18740 18741 18742 18743 18744 18745 18746 18747 18748 18749 18750 18751 |
sqlite3_trace_v2(p->db, 0, 0, 0);
}else{
if( mType==0 ) mType = SQLITE_TRACE_STMT;
sqlite3_trace_v2(p->db, mType, sql_trace_callback, p);
}
}else
#endif /* !defined(SQLITE_OMIT_TRACE) */
#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE)
if( c=='u' && strncmp(azArg[0], "unmodule", n)==0 ){
int ii;
int lenOpt;
char *zOpt;
if( nArg<2 ){
raw_printf(stderr, "Usage: .unmodule [--allexcept] NAME ...\n");
rc = 1;
goto meta_command_exit;
}
open_db(p, 0);
zOpt = azArg[1];
if( zOpt[0]=='-' && zOpt[1]=='-' && zOpt[2]!=0 ) zOpt++;
lenOpt = (int)strlen(zOpt);
if( lenOpt>=3 && strncmp(zOpt, "-allexcept",lenOpt)==0 ){
assert( azArg[nArg]==0 );
sqlite3_drop_modules(p->db, nArg>2 ? (const char**)(azArg+2) : 0);
}else{
for(ii=1; ii<nArg; ii++){
sqlite3_create_module(p->db, azArg[ii], 0, 0);
}
}
}else
#endif
#if SQLITE_USER_AUTHENTICATION
if( c=='u' && strncmp(azArg[0], "user", n)==0 ){
if( nArg<2 ){
raw_printf(stderr, "Usage: .user SUBCOMMAND ...\n");
rc = 1;
goto meta_command_exit;
}
open_db(p, 0);
if( strcmp(azArg[1],"login")==0 ){
if( nArg!=4 ){
raw_printf(stderr, "Usage: .user login USER PASSWORD\n");
rc = 1;
goto meta_command_exit;
}
rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3],
strlen30(azArg[3]));
if( rc ){
utf8_printf(stderr, "Authentication failed for user %s\n", azArg[2]);
rc = 1;
}
}else if( strcmp(azArg[1],"add")==0 ){
if( nArg!=5 ){
raw_printf(stderr, "Usage: .user add USER PASSWORD ISADMIN\n");
|
| ︙ | ︙ | |||
17998 17999 18000 18001 18002 18003 18004 |
sqlite3WhereTrace = nArg>=2 ? booleanValue(azArg[1]) : 0xff;
}else
#endif
if( c=='w' && strncmp(azArg[0], "width", n)==0 ){
int j;
assert( nArg<=ArraySize(azArg) );
| > > > > | | 18857 18858 18859 18860 18861 18862 18863 18864 18865 18866 18867 18868 18869 18870 18871 18872 18873 18874 18875 |
sqlite3WhereTrace = nArg>=2 ? booleanValue(azArg[1]) : 0xff;
}else
#endif
if( c=='w' && strncmp(azArg[0], "width", n)==0 ){
int j;
assert( nArg<=ArraySize(azArg) );
p->nWidth = nArg-1;
p->colWidth = realloc(p->colWidth, p->nWidth*sizeof(int)*2);
if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory();
if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth];
for(j=1; j<nArg; j++){
p->colWidth[j-1] = (int)integerValue(azArg[j]);
}
}else
{
utf8_printf(stderr, "Error: unknown command or invalid arguments: "
" \"%s\". Enter \".help\" for help\n", azArg[0]);
|
| ︙ | ︙ | |||
18343 18344 18345 18346 18347 18348 18349 18350 18351 18352 18353 18354 18355 18356 18357 18358 18359 18360 18361 18362 18363 18364 18365 18366 18367 18368 18369 18370 18371 18372 18373 18374 18375 18376 18377 18378 18379 18380 18381 18382 18383 18384 18385 18386 18387 18388 18389 18390 18391 18392 | #if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) " -A ARGS... run \".archive ARGS\" and exit\n" #endif " -append append the database to the end of the file\n" " -ascii set output mode to 'ascii'\n" " -bail stop after hitting an error\n" " -batch force batch I/O\n" " -column set output mode to 'column'\n" " -cmd COMMAND run \"COMMAND\" before reading stdin\n" " -csv set output mode to 'csv'\n" #if defined(SQLITE_ENABLE_DESERIALIZE) " -deserialize open the database using sqlite3_deserialize()\n" #endif " -echo print commands before execution\n" " -init FILENAME read/process named file\n" " -[no]header turn headers on or off\n" #if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) " -heap SIZE Size of heap for memsys3 or memsys5\n" #endif " -help show this message\n" " -html set output mode to HTML\n" " -interactive force interactive I/O\n" " -line set output mode to 'line'\n" " -list set output mode to 'list'\n" " -lookaside SIZE N use N entries of SZ bytes for lookaside memory\n" #if defined(SQLITE_ENABLE_DESERIALIZE) " -maxsize N maximum size for a --deserialize database\n" #endif " -memtrace trace all memory allocations and deallocations\n" " -mmap N default mmap size set to N\n" #ifdef SQLITE_ENABLE_MULTIPLEX " -multiplex enable the multiplexor VFS\n" #endif " -newline SEP set output row separator. Default: '\\n'\n" " -nullvalue TEXT set text string for NULL values. Default ''\n" " -pagecache SIZE N use N slots of SZ bytes each for page cache memory\n" " -quote set output mode to 'quote'\n" " -readonly open the database read-only\n" " -separator SEP set output column separator. Default: '|'\n" #ifdef SQLITE_ENABLE_SORTER_REFERENCES " -sorterref SIZE sorter references threshold size\n" #endif " -stats print memory stats before each finalize\n" " -version show SQLite version\n" " -vfs NAME use NAME as the default VFS\n" #ifdef SQLITE_ENABLE_VFSTRACE " -vfstrace enable tracing of all VFS calls\n" #endif #ifdef SQLITE_HAVE_ZLIB " -zip open the file as a ZIP Archive\n" | > > > > > | 19206 19207 19208 19209 19210 19211 19212 19213 19214 19215 19216 19217 19218 19219 19220 19221 19222 19223 19224 19225 19226 19227 19228 19229 19230 19231 19232 19233 19234 19235 19236 19237 19238 19239 19240 19241 19242 19243 19244 19245 19246 19247 19248 19249 19250 19251 19252 19253 19254 19255 19256 19257 19258 19259 19260 | #if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) " -A ARGS... run \".archive ARGS\" and exit\n" #endif " -append append the database to the end of the file\n" " -ascii set output mode to 'ascii'\n" " -bail stop after hitting an error\n" " -batch force batch I/O\n" " -box set output mode to 'box'\n" " -column set output mode to 'column'\n" " -cmd COMMAND run \"COMMAND\" before reading stdin\n" " -csv set output mode to 'csv'\n" #if defined(SQLITE_ENABLE_DESERIALIZE) " -deserialize open the database using sqlite3_deserialize()\n" #endif " -echo print commands before execution\n" " -init FILENAME read/process named file\n" " -[no]header turn headers on or off\n" #if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) " -heap SIZE Size of heap for memsys3 or memsys5\n" #endif " -help show this message\n" " -html set output mode to HTML\n" " -interactive force interactive I/O\n" " -json set output mode to 'json'\n" " -line set output mode to 'line'\n" " -list set output mode to 'list'\n" " -lookaside SIZE N use N entries of SZ bytes for lookaside memory\n" " -markdown set output mode to 'markdown'\n" #if defined(SQLITE_ENABLE_DESERIALIZE) " -maxsize N maximum size for a --deserialize database\n" #endif " -memtrace trace all memory allocations and deallocations\n" " -mmap N default mmap size set to N\n" #ifdef SQLITE_ENABLE_MULTIPLEX " -multiplex enable the multiplexor VFS\n" #endif " -newline SEP set output row separator. Default: '\\n'\n" " -nofollow refuse to open symbolic links to database files\n" " -nullvalue TEXT set text string for NULL values. Default ''\n" " -pagecache SIZE N use N slots of SZ bytes each for page cache memory\n" " -quote set output mode to 'quote'\n" " -readonly open the database read-only\n" " -separator SEP set output column separator. Default: '|'\n" #ifdef SQLITE_ENABLE_SORTER_REFERENCES " -sorterref SIZE sorter references threshold size\n" #endif " -stats print memory stats before each finalize\n" " -table set output mode to 'table'\n" " -version show SQLite version\n" " -vfs NAME use NAME as the default VFS\n" #ifdef SQLITE_ENABLE_VFSTRACE " -vfstrace enable tracing of all VFS calls\n" #endif #ifdef SQLITE_HAVE_ZLIB " -zip open the file as a ZIP Archive\n" |
| ︙ | ︙ | |||
18436 18437 18438 18439 18440 18441 18442 18443 18444 18445 18446 18447 18448 18449 18450 18451 18452 18453 18454 18455 18456 18457 |
}
/*
** Output text to the console in a font that attracts extra attention.
*/
#ifdef _WIN32
static void printBold(const char *zText){
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO defaultScreenInfo;
GetConsoleScreenBufferInfo(out, &defaultScreenInfo);
SetConsoleTextAttribute(out,
FOREGROUND_RED|FOREGROUND_INTENSITY
);
printf("%s", zText);
SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes);
}
#else
static void printBold(const char *zText){
printf("\033[1m%s\033[0m", zText);
}
#endif
| > > > > | 19304 19305 19306 19307 19308 19309 19310 19311 19312 19313 19314 19315 19316 19317 19318 19319 19320 19321 19322 19323 19324 19325 19326 19327 19328 19329 |
}
/*
** Output text to the console in a font that attracts extra attention.
*/
#ifdef _WIN32
static void printBold(const char *zText){
#if !SQLITE_OS_WINRT
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO defaultScreenInfo;
GetConsoleScreenBufferInfo(out, &defaultScreenInfo);
SetConsoleTextAttribute(out,
FOREGROUND_RED|FOREGROUND_INTENSITY
);
#endif
printf("%s", zText);
#if !SQLITE_OS_WINRT
SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes);
#endif
}
#else
static void printBold(const char *zText){
printf("\033[1m%s\033[0m", zText);
}
#endif
|
| ︙ | ︙ | |||
18497 18498 18499 18500 18501 18502 18503 18504 18505 18506 18507 18508 18509 18510 18511 18512 18513 18514 18515 18516 18517 18518 18519 18520 18521 |
int argcToFree = 0;
#endif
setBinaryMode(stdin, 0);
setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
stdin_is_interactive = isatty(0);
stdout_is_console = isatty(1);
#if !defined(_WIN32_WCE)
if( getenv("SQLITE_DEBUG_BREAK") ){
if( isatty(0) && isatty(2) ){
fprintf(stderr,
"attach debugger to process %d and press any key to continue.\n",
GETPID());
fgetc(stdin);
}else{
#if defined(_WIN32) || defined(WIN32)
DebugBreak();
#elif defined(SIGTRAP)
raise(SIGTRAP);
#endif
}
}
#endif
| > > > > > > > > | 19369 19370 19371 19372 19373 19374 19375 19376 19377 19378 19379 19380 19381 19382 19383 19384 19385 19386 19387 19388 19389 19390 19391 19392 19393 19394 19395 19396 19397 19398 19399 19400 19401 |
int argcToFree = 0;
#endif
setBinaryMode(stdin, 0);
setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
stdin_is_interactive = isatty(0);
stdout_is_console = isatty(1);
#ifdef SQLITE_DEBUG
registerOomSimulator();
#endif
#if !defined(_WIN32_WCE)
if( getenv("SQLITE_DEBUG_BREAK") ){
if( isatty(0) && isatty(2) ){
fprintf(stderr,
"attach debugger to process %d and press any key to continue.\n",
GETPID());
fgetc(stdin);
}else{
#if defined(_WIN32) || defined(WIN32)
#if SQLITE_OS_WINRT
__debugbreak();
#else
DebugBreak();
#endif
#elif defined(SIGTRAP)
raise(SIGTRAP);
#endif
}
}
#endif
|
| ︙ | ︙ | |||
18680 18681 18682 18683 18684 18685 18686 18687 18688 18689 18690 18691 18692 18693 |
}else if( strcmp(z,"-deserialize")==0 ){
data.openMode = SHELL_OPEN_DESERIALIZE;
}else if( strcmp(z,"-maxsize")==0 && i+1<argc ){
data.szMax = integerValue(argv[++i]);
#endif
}else if( strcmp(z,"-readonly")==0 ){
data.openMode = SHELL_OPEN_READONLY;
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
}else if( strncmp(z, "-A",2)==0 ){
/* All remaining command-line arguments are passed to the ".archive"
** command, so ignore them */
break;
#endif
}else if( strcmp(z, "-memtrace")==0 ){
| > > | 19560 19561 19562 19563 19564 19565 19566 19567 19568 19569 19570 19571 19572 19573 19574 19575 |
}else if( strcmp(z,"-deserialize")==0 ){
data.openMode = SHELL_OPEN_DESERIALIZE;
}else if( strcmp(z,"-maxsize")==0 && i+1<argc ){
data.szMax = integerValue(argv[++i]);
#endif
}else if( strcmp(z,"-readonly")==0 ){
data.openMode = SHELL_OPEN_READONLY;
}else if( strcmp(z,"-nofollow")==0 ){
data.openFlags = SQLITE_OPEN_NOFOLLOW;
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
}else if( strncmp(z, "-A",2)==0 ){
/* All remaining command-line arguments are passed to the ".archive"
** command, so ignore them */
break;
#endif
}else if( strcmp(z, "-memtrace")==0 ){
|
| ︙ | ︙ | |||
18766 18767 18768 18769 18770 18771 18772 18773 18774 18775 18776 18777 18778 18779 18780 18781 18782 18783 18784 18785 18786 18787 18788 18789 18790 18791 18792 18793 18794 18795 18796 |
data.mode = MODE_List;
}else if( strcmp(z,"-quote")==0 ){
data.mode = MODE_Quote;
}else if( strcmp(z,"-line")==0 ){
data.mode = MODE_Line;
}else if( strcmp(z,"-column")==0 ){
data.mode = MODE_Column;
}else if( strcmp(z,"-csv")==0 ){
data.mode = MODE_Csv;
memcpy(data.colSeparator,",",2);
#ifdef SQLITE_HAVE_ZLIB
}else if( strcmp(z,"-zip")==0 ){
data.openMode = SHELL_OPEN_ZIPFILE;
#endif
}else if( strcmp(z,"-append")==0 ){
data.openMode = SHELL_OPEN_APPENDVFS;
#ifdef SQLITE_ENABLE_DESERIALIZE
}else if( strcmp(z,"-deserialize")==0 ){
data.openMode = SHELL_OPEN_DESERIALIZE;
}else if( strcmp(z,"-maxsize")==0 && i+1<argc ){
data.szMax = integerValue(argv[++i]);
#endif
}else if( strcmp(z,"-readonly")==0 ){
data.openMode = SHELL_OPEN_READONLY;
}else if( strcmp(z,"-ascii")==0 ){
data.mode = MODE_Ascii;
sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,
SEP_Unit);
sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,
SEP_Record);
}else if( strcmp(z,"-separator")==0 ){
| > > > > > > > > > > | 19648 19649 19650 19651 19652 19653 19654 19655 19656 19657 19658 19659 19660 19661 19662 19663 19664 19665 19666 19667 19668 19669 19670 19671 19672 19673 19674 19675 19676 19677 19678 19679 19680 19681 19682 19683 19684 19685 19686 19687 19688 |
data.mode = MODE_List;
}else if( strcmp(z,"-quote")==0 ){
data.mode = MODE_Quote;
}else if( strcmp(z,"-line")==0 ){
data.mode = MODE_Line;
}else if( strcmp(z,"-column")==0 ){
data.mode = MODE_Column;
}else if( strcmp(z,"-json")==0 ){
data.mode = MODE_Json;
}else if( strcmp(z,"-markdown")==0 ){
data.mode = MODE_Markdown;
}else if( strcmp(z,"-table")==0 ){
data.mode = MODE_Table;
}else if( strcmp(z,"-box")==0 ){
data.mode = MODE_Box;
}else if( strcmp(z,"-csv")==0 ){
data.mode = MODE_Csv;
memcpy(data.colSeparator,",",2);
#ifdef SQLITE_HAVE_ZLIB
}else if( strcmp(z,"-zip")==0 ){
data.openMode = SHELL_OPEN_ZIPFILE;
#endif
}else if( strcmp(z,"-append")==0 ){
data.openMode = SHELL_OPEN_APPENDVFS;
#ifdef SQLITE_ENABLE_DESERIALIZE
}else if( strcmp(z,"-deserialize")==0 ){
data.openMode = SHELL_OPEN_DESERIALIZE;
}else if( strcmp(z,"-maxsize")==0 && i+1<argc ){
data.szMax = integerValue(argv[++i]);
#endif
}else if( strcmp(z,"-readonly")==0 ){
data.openMode = SHELL_OPEN_READONLY;
}else if( strcmp(z,"-nofollow")==0 ){
data.openFlags |= SQLITE_OPEN_NOFOLLOW;
}else if( strcmp(z,"-ascii")==0 ){
data.mode = MODE_Ascii;
sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,
SEP_Unit);
sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,
SEP_Record);
}else if( strcmp(z,"-separator")==0 ){
|
| ︙ | ︙ | |||
18981 18982 18983 18984 18985 18986 18987 18988 18989 18990 18991 18992 | output_reset(&data); data.doXdgOpen = 0; clearTempFile(&data); #if !SQLITE_SHELL_IS_UTF8 for(i=0; i<argcToFree; i++) free(argvToFree[i]); free(argvToFree); #endif /* Clear the global data structure so that valgrind will detect memory ** leaks */ memset(&data, 0, sizeof(data)); return rc; } | > | 19873 19874 19875 19876 19877 19878 19879 19880 19881 19882 19883 19884 19885 | output_reset(&data); data.doXdgOpen = 0; clearTempFile(&data); #if !SQLITE_SHELL_IS_UTF8 for(i=0; i<argcToFree; i++) free(argvToFree[i]); free(argvToFree); #endif free(data.colWidth); /* Clear the global data structure so that valgrind will detect memory ** leaks */ memset(&data, 0, sizeof(data)); return rc; } |
| ︙ | ︙ | |||
172 173 174 175 176 177 178 | @ <a name="addshun"></a> @ <p>To shun artifacts, enter their artifact hashes (the 40- or @ 64-character lowercase hexadecimal hash of the artifact content) in the @ following box and press the "Shun" button. This will cause the artifacts @ to be removed from the repository and will prevent the artifacts from being @ readded to the repository by subsequent sync operation.</p> @ | | | | | 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 |
@ <a name="addshun"></a>
@ <p>To shun artifacts, enter their artifact hashes (the 40- or
@ 64-character lowercase hexadecimal hash of the artifact content) in the
@ following box and press the "Shun" button. This will cause the artifacts
@ to be removed from the repository and will prevent the artifacts from being
@ readded to the repository by subsequent sync operation.</p>
@
@ <p>Note that you must enter full artifact hashes, not abbreviations
@ or symbolic tags.</p>
@
@ <p>Warning: Shunning should only be used to remove inappropriate content
@ from the repository. Inappropriate content includes such things as
@ spam added to Wiki, files that violate copyright or patent agreements,
@ or artifacts that by design or accident interfere with the processing
@ of the repository. Do not shun artifacts merely to remove them from
@ sight - set the "hidden" tag on such artifacts instead.</p>
@
@ <blockquote>
@ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div>
login_insert_csrf_secret();
@ <textarea class="fullsize-text" cols="70" rows="%d(numRows)" name="uuid">
if( zShun ){
if( strlen(zShun) ){
@ %h(zShun)
}else if( nRcvid ){
db_prepare(&q, "SELECT uuid FROM blob WHERE rcvid=%d", nRcvid);
while( db_step(&q)==SQLITE_ROW ){
@ %s(db_column_text(&q, 0))
|
| ︙ | ︙ | |||
212 213 214 215 216 217 218 | @ restored because the content is unknown. The only change is that @ the formerly shunned artifacts will be accepted on subsequent sync @ operations.</p> @ @ <blockquote> @ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div> login_insert_csrf_secret(); | | | 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
@ restored because the content is unknown. The only change is that
@ the formerly shunned artifacts will be accepted on subsequent sync
@ operations.</p>
@
@ <blockquote>
@ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div>
login_insert_csrf_secret();
@ <textarea class="fullsize-text" cols="70" rows="%d(numRows)" name="uuid">
if( zAccept ){
if( strlen(zAccept) ){
@ %h(zAccept)
}else if( nRcvid ){
db_prepare(&q, "SELECT uuid FROM blob WHERE rcvid=%d", nRcvid);
while( db_step(&q)==SQLITE_ROW ){
@ %s(db_column_text(&q, 0))
|
| ︙ | ︙ |
| ︙ | ︙ | |||
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
}
if( inSublist ){
@ </ul>
inSublist = 0;
}
@ </li>
if( g.perm.Read ){
@ <li>%z(href("%R/tree"))File Browser</a>
@ <ul>
@ <li>%z(href("%R/tree?type=tree&ci=trunk"))Tree-view,
@ Trunk Check-in</a></li>
@ <li>%z(href("%R/tree?type=flat"))Flat-view</a></li>
@ <li>%z(href("%R/fileage?name=trunk"))File ages for Trunk</a></li>
@ <li>%z(href("%R/uvlist"))Unversioned Files</a>
@ </ul>
}
if( g.perm.Read ){
@ <li>%z(href("%R/timeline"))Project Timeline</a>
@ <ul>
@ <li>%z(href("%R/reports"))Activity Reports</a></li>
@ <li>%z(href("%R/timeline?n=all&namechng"))File name changes</a></li>
| > > > > | 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 |
}
if( inSublist ){
@ </ul>
inSublist = 0;
}
@ </li>
if( g.perm.Read ){
const char *zEditGlob = db_get("fileedit-glob","");
@ <li>%z(href("%R/tree"))File Browser</a>
@ <ul>
@ <li>%z(href("%R/tree?type=tree&ci=trunk"))Tree-view,
@ Trunk Check-in</a></li>
@ <li>%z(href("%R/tree?type=flat"))Flat-view</a></li>
@ <li>%z(href("%R/fileage?name=trunk"))File ages for Trunk</a></li>
@ <li>%z(href("%R/uvlist"))Unversioned Files</a>
if( g.perm.Write && zEditGlob[0]!=0 ){
@ <li>%z(href("%R/fileedit"))On-line File Editor</li>
}
@ </ul>
}
if( g.perm.Read ){
@ <li>%z(href("%R/timeline"))Project Timeline</a>
@ <ul>
@ <li>%z(href("%R/reports"))Activity Reports</a></li>
@ <li>%z(href("%R/timeline?n=all&namechng"))File name changes</a></li>
|
| ︙ | ︙ |
| ︙ | ︙ | |||
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
/*
** Return an identifier that is (probably) different for every skin
** but that is (probably) the same if the skin is unchanged. This
** identifier can be attached to resource URLs to force reloading when
** the resources change but allow the resources to be read from cache
** as long as they are unchanged.
*/
unsigned int skin_id(const char *zResource){
unsigned int h = 0;
if( zAltSkinDir ){
h = skin_hash(0, zAltSkinDir);
}else if( pAltSkin ){
h = skin_hash(0, pAltSkin->zLabel);
}else{
char *zMTime = db_get_mtime(zResource, 0, 0);
h = skin_hash(0, zMTime);
fossil_free(zMTime);
}
| > > > > > | | 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 |
/*
** Return an identifier that is (probably) different for every skin
** but that is (probably) the same if the skin is unchanged. This
** identifier can be attached to resource URLs to force reloading when
** the resources change but allow the resources to be read from cache
** as long as they are unchanged.
**
** The zResource argument is the name of a CONFIG setting that
** defines the resource. Examples: "css", "logo-image".
*/
unsigned int skin_id(const char *zResource){
unsigned int h = 0;
if( zAltSkinDir ){
h = skin_hash(0, zAltSkinDir);
}else if( pAltSkin ){
h = skin_hash(0, pAltSkin->zLabel);
}else{
char *zMTime = db_get_mtime(zResource, 0, 0);
h = skin_hash(0, zMTime);
fossil_free(zMTime);
}
/* Change the ID every time Fossil is recompiled */
h = skin_hash(h, fossil_exe_id());
return h;
}
/*
** For a skin named zSkinName, compute the name of the CONFIG table
** entry where that skin is stored and return it.
**
|
| ︙ | ︙ | |||
686 687 688 689 690 691 692 693 694 695 696 697 698 699 |
if( zResult!=0 ) break;
zLabel = "default";
}
}
return zResult;
}
/*
** WEBPAGE: setup_skinedit
**
** Edit aspects of a skin determined by the w= query parameter.
** Requires Admin or Setup privileges.
**
| > > > > > > > | 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 |
if( zResult!=0 ) break;
zLabel = "default";
}
}
return zResult;
}
extern const struct strctCssDefaults {
/* From the generated default_css.h, which we cannot #include here
** without causing an ODR violation.
*/
const char *elementClass; /* Name of element needed */
const char *value; /* CSS text */
} cssDefaultList[];
/*
** WEBPAGE: setup_skinedit
**
** Edit aspects of a skin determined by the w= query parameter.
** Requires Admin or Setup privileges.
**
|
| ︙ | ︙ | |||
1060 1061 1062 1063 1064 1065 1066 |
@ <h1>Step 7: Publish</h1>
@
if( !g.perm.Admin ){
@ <p>Only administrators are allowed to publish draft skins. Contact
@ an administrator to get this "draft%d(iSkin)" skin published.</p>
}else{
@ <p>When the draft%d(iSkin) skin is ready for production use,
| | | 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 |
@ <h1>Step 7: Publish</h1>
@
if( !g.perm.Admin ){
@ <p>Only administrators are allowed to publish draft skins. Contact
@ an administrator to get this "draft%d(iSkin)" skin published.</p>
}else{
@ <p>When the draft%d(iSkin) skin is ready for production use,
@ make it the default skin by clicking the acknowledgements and
@ pressing the button below:</p>
@
@ <form method='POST' action='%R/setup_skin#step7'>
@ <p class='skinInput'>
@ <input type='hidden' name='sk' value='%d(iSkin)'>
@ <input type='checkbox' name='pub7ck1' value='yes'>\
@ Skin draft%d(iSkin) has been tested and found ready for production.<br>
|
| ︙ | ︙ |
| ︙ | ︙ | |||
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 |
**
** Use SMTP to send the email message contained in the file named EMAIL
** to the list of users TO. FROM is the sender of the email.
**
** Options:
**
** --direct Go directly to the TO domain. Bypass MX lookup
** --port N Use TCP port N instead of 25
** --trace Show the SMTP conversation on the console
*/
void test_smtp_send(void){
SmtpSession *p;
const char *zFrom;
int nTo;
const char *zToDomain;
const char *zFromDomain;
const char **azTo;
int smtpPort = 25;
const char *zPort;
Blob body;
u32 smtpFlags = SMTP_PORT;
if( find_option("trace",0,0)!=0 ) smtpFlags |= SMTP_TRACE_STDOUT;
if( find_option("direct",0,0)!=0 ) smtpFlags |= SMTP_DIRECT;
zPort = find_option("port",0,1);
if( zPort ) smtpPort = atoi(zPort);
verify_all_options();
if( g.argc<5 ) usage("EMAIL FROM TO ...");
blob_read_from_file(&body, g.argv[2], ExtFILE);
zFrom = g.argv[3];
nTo = g.argc-4;
azTo = (const char**)g.argv+4;
zFromDomain = domainOfAddr(zFrom);
| > > > > > > > | > | 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 |
**
** Use SMTP to send the email message contained in the file named EMAIL
** to the list of users TO. FROM is the sender of the email.
**
** Options:
**
** --direct Go directly to the TO domain. Bypass MX lookup
** --relayhost R Use R as relay host directly for delivery.
** --port N Use TCP port N instead of 25
** --trace Show the SMTP conversation on the console
*/
void test_smtp_send(void){
SmtpSession *p;
const char *zFrom;
int nTo;
const char *zToDomain;
const char *zFromDomain;
const char *zRelay;
const char **azTo;
int smtpPort = 25;
const char *zPort;
Blob body;
u32 smtpFlags = SMTP_PORT;
if( find_option("trace",0,0)!=0 ) smtpFlags |= SMTP_TRACE_STDOUT;
if( find_option("direct",0,0)!=0 ) smtpFlags |= SMTP_DIRECT;
zPort = find_option("port",0,1);
if( zPort ) smtpPort = atoi(zPort);
zRelay = find_option("relayhost",0,1);
verify_all_options();
if( g.argc<5 ) usage("EMAIL FROM TO ...");
blob_read_from_file(&body, g.argv[2], ExtFILE);
zFrom = g.argv[3];
nTo = g.argc-4;
azTo = (const char**)g.argv+4;
zFromDomain = domainOfAddr(zFrom);
if( zRelay!=0 && zRelay[0]!= 0) {
smtpFlags |= SMTP_DIRECT;
zToDomain = zRelay;
}else{
zToDomain = domainOfAddr(azTo[0]);
}
p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort);
if( p->zErr ){
fossil_fatal("%s", p->zErr);
}
fossil_print("Connection to \"%s\"\n", p->zHostname);
smtp_client_startup(p);
smtp_send_msg(p, zFrom, nTo, azTo, blob_str(&body));
|
| ︙ | ︙ | |||
1518 1519 1520 1521 1522 1523 1524 |
if( strcmp(zCmd,"capa")==0 ){
static const char *const azCap[] = {
"TOP", "USER", "UIDL",
};
int i;
pop3_print(pLog, "+OK");
for(i=0; i<sizeof(azCap)/sizeof(azCap[0]); i++){
| | | 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 |
if( strcmp(zCmd,"capa")==0 ){
static const char *const azCap[] = {
"TOP", "USER", "UIDL",
};
int i;
pop3_print(pLog, "+OK");
for(i=0; i<sizeof(azCap)/sizeof(azCap[0]); i++){
pop3_print(pLog, "%s", azCap[i]);
}
pop3_print(pLog, ".");
continue;
}
if( inAuth ){
if( strcmp(zCmd,"user")==0 ){
if( zA1==0 || zA2!=0 ) goto cmd_error;
|
| ︙ | ︙ |
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
> > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | The *.wav files in this directory contain a human voice speaking each of the 16 hexadecimal digits. If a captcha string consists of just hexadecimal digits (as is the case for captchas generated by the [../captcha.c module](../captcha.c)) then these WAV files can be concatenated together to generate an audio reading of the captcha, which enables visually impaired users to complete the captcha. Each of the WAV files uses 8000 samples per second, 8 bytes per sample and are 6000 samples in length. The recordings are made by Philip Bennefall and are of his own voice. Mr. Bennefall is himself blind and uses this system implemented with these recordings to complete captchas for Fossil. |
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
| ︙ | ︙ | |||
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 |
sqlite3_free(pOut);
sqlite3_result_error_nomem(context);
}else{
sqlite3_free(pOut);
sqlite3_result_error(context, "input is not zlib compressed", -1);
}
}
/*
** Add the content(), compress(), and decompress() SQL functions to
** database connection db.
*/
int add_content_sql_commands(sqlite3 *db){
sqlite3_create_function(db, "content", 1, SQLITE_UTF8, 0,
sqlcmd_content, 0, 0);
sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0,
sqlcmd_compress, 0, 0);
sqlite3_create_function(db, "decompress", 1, SQLITE_UTF8, 0,
sqlcmd_decompress, 0, 0);
return SQLITE_OK;
}
/*
** This is the "automatic extension" initializer that runs right after
** the connection to the repository database is opened. Set up the
** database connection to be more useful to the human operator.
*/
static int sqlcmd_autoinit(
sqlite3 *db,
const char **pzErrMsg,
const void *notUsed
){
add_content_sql_commands(db);
db_add_aux_functions(db);
re_add_sql_func(db);
search_sql_setup(db);
foci_register(db);
deltafunc_init(db);
g.repositoryOpen = 1;
g.db = db;
sqlite3_db_config(db, SQLITE_DBCONFIG_MAINDBNAME, "repository");
db_maybe_set_encryption_key(db, g.zRepositoryName);
if( g.zLocalDbName ){
char *zSql = sqlite3_mprintf("ATTACH %Q AS 'localdb' KEY ''",
g.zLocalDbName);
sqlite3_exec(db, zSql, 0, 0, 0);
sqlite3_free(zSql);
}
if( g.zConfigDbName ){
char *zSql = sqlite3_mprintf("ATTACH %Q AS 'configdb' KEY ''",
g.zConfigDbName);
sqlite3_exec(db, zSql, 0, 0, 0);
sqlite3_free(zSql);
}
return SQLITE_OK;
}
/*
** atexit() handler that cleans up global state modified by this module.
*/
static void sqlcmd_atexit(void) {
| > > > > > > > > > > > > > > > > > > > > > | 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 |
sqlite3_free(pOut);
sqlite3_result_error_nomem(context);
}else{
sqlite3_free(pOut);
sqlite3_result_error(context, "input is not zlib compressed", -1);
}
}
/*
** Implementation of the "gather_artifact_stats(X)" SQL function.
** That function merely calls the gather_artifact_stats() function
** in stat.c to populate the ARTSTAT temporary table.
*/
static void sqlcmd_gather_artifact_stats(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
gather_artifact_stats(1);
}
/*
** Add the content(), compress(), and decompress() SQL functions to
** database connection db.
*/
int add_content_sql_commands(sqlite3 *db){
sqlite3_create_function(db, "content", 1, SQLITE_UTF8, 0,
sqlcmd_content, 0, 0);
sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0,
sqlcmd_compress, 0, 0);
sqlite3_create_function(db, "decompress", 1, SQLITE_UTF8, 0,
sqlcmd_decompress, 0, 0);
sqlite3_create_function(db, "gather_artifact_stats", 0, SQLITE_UTF8, 0,
sqlcmd_gather_artifact_stats, 0, 0);
return SQLITE_OK;
}
/*
** This is the "automatic extension" initializer that runs right after
** the connection to the repository database is opened. Set up the
** database connection to be more useful to the human operator.
*/
static int sqlcmd_autoinit(
sqlite3 *db,
const char **pzErrMsg,
const void *notUsed
){
int mTrace = SQLITE_TRACE_CLOSE;
add_content_sql_commands(db);
db_add_aux_functions(db);
re_add_sql_func(db);
search_sql_setup(db);
foci_register(db);
deltafunc_init(db);
helptext_vtab_register(db);
g.repositoryOpen = 1;
g.db = db;
sqlite3_db_config(db, SQLITE_DBCONFIG_MAINDBNAME, "repository");
db_maybe_set_encryption_key(db, g.zRepositoryName);
if( g.zLocalDbName ){
char *zSql = sqlite3_mprintf("ATTACH %Q AS 'localdb' KEY ''",
g.zLocalDbName);
sqlite3_exec(db, zSql, 0, 0, 0);
sqlite3_free(zSql);
}
if( g.zConfigDbName ){
char *zSql = sqlite3_mprintf("ATTACH %Q AS 'configdb' KEY ''",
g.zConfigDbName);
sqlite3_exec(db, zSql, 0, 0, 0);
sqlite3_free(zSql);
}
/* Arrange to trace close operations so that static prepared statements
** will get cleaned up when the shell closes the database connection */
if( g.fSqlTrace ) mTrace |= SQLITE_TRACE_PROFILE;
sqlite3_trace_v2(db, mTrace, db_sql_trace, 0);
return SQLITE_OK;
}
/*
** atexit() handler that cleans up global state modified by this module.
*/
static void sqlcmd_atexit(void) {
|
| ︙ | ︙ | |||
300 301 302 303 304 305 306 |
** standard SQLite.
**
** symbolic_name_to_rid(X) Return the BLOB.RID corresponding to symbolic
** name X.
*/
void cmd_sqlite3(void){
int noRepository;
| | | | 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 |
** standard SQLite.
**
** symbolic_name_to_rid(X) Return the BLOB.RID corresponding to symbolic
** name X.
*/
void cmd_sqlite3(void){
int noRepository;
char *zConfigDb;
extern int sqlite3_shell(int, char**);
#ifdef FOSSIL_ENABLE_TH1_HOOKS
g.fNoThHook = 1;
#endif
noRepository = find_option("no-repository", 0, 0)!=0;
if( !noRepository ){
db_find_and_open_repository(OPEN_ANY_SCHEMA, 0);
}
db_open_config(1,0);
zConfigDb = fossil_strdup(g.zConfigDbName);
fossil_close(1, noRepository);
sqlite3_shutdown();
#ifndef _WIN32
linenoiseSetMultiLine(1);
#endif
atexit(sqlcmd_atexit);
g.zConfigDbName = zConfigDb;
g.argv[1] = "-quote";
sqlite3_shell(g.argc, g.argv);
sqlite3_cancel_auto_extension((void(*)(void))sqlcmd_autoinit);
fossil_close(0, noRepository);
}
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
/*
** Copyright (c) 2019 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** 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.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file is NOT part of the Fossil executable
**
** This file contains a test program used by ../configure with the
** the --disable-internal-sqlite option to determine whether or
** not the system SQLite library is sufficient to support Fossil.
**
** It is preferred to statically link Fossil with the sqlite3.c source
** file that is part of the source tree and not use any SQLite shared
** library that is included with the system. But some packagers do not
** like to do this. Hence, we provide the option to link Fossil against
** the system SQLite shared library. But Fossil is very particular about
** the version and build options for SQLite. Unless a recent version of
** SQLite is available, and unless that SQLite is built using some
** non-default features, the system library won't meet the needs of
** Fossil. This program attempts to determine if the system library
** SQLite is sufficient for Fossil.
**
** Compile this program, linking it against the system SQLite library,
** and run it. If it returns with a zero exit code, then all is well.
** But if it returns a non-zero exit code, then the system SQLite library
** lacks some capability that Fossil uses. A message on stdout describes
** the missing feature.
*/
#include "sqlite3.h"
#include <stdio.h>
int main(int argc, char **argv){
int i;
static const char *zRequiredOpts[] = {
"ENABLE_FTS4", /* Required for repository search */
"ENABLE_JSON1", /* Required for the check-in locking protocol */
"ENABLE_DBSTAT_VTAB", /* Required by /repo-tabsize page */
};
/* Check minimum SQLite version number */
if( sqlite3_libversion_number()<3028000 ){
printf("found SQLite version %s but need 3.28.0 or later\n",
sqlite3_libversion());
return 1;
}
for(i=0; i<sizeof(zRequiredOpts)/sizeof(zRequiredOpts[0]); i++){
if( !sqlite3_compileoption_used(zRequiredOpts[i]) ){
printf("system SQLite library omits required build option -DSQLITE_%s\n",
zRequiredOpts[i]);
return 1;
}
}
/* Success! */
return 0;
}
|
more than 10,000 changes
| ︙ | ︙ | |||
119 120 121 122 123 124 125 | ** been edited in any way since it was last checked in, then the last ** four hexadecimal digits of the hash may be modified. ** ** See also: [sqlite3_libversion()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ | | | | | 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | ** been edited in any way since it was last checked in, then the last ** four hexadecimal digits of the hash may be modified. ** ** See also: [sqlite3_libversion()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ #define SQLITE_VERSION "3.33.0" #define SQLITE_VERSION_NUMBER 3033000 #define SQLITE_SOURCE_ID "2020-06-20 03:43:46 067291143a63db924ead4810defb4bc6f195557412f5d1c22299f30d2d9f2a79" /* ** CAPI3REF: Run-Time Library Version Numbers ** KEYWORDS: sqlite3_version sqlite3_sourceid ** ** These interfaces provide the same information as the [SQLITE_VERSION], ** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros |
| ︙ | ︙ | |||
295 296 297 298 299 300 301 302 | ** ** ^The sqlite3_close() and sqlite3_close_v2() routines are destructors ** for the [sqlite3] object. ** ^Calls to sqlite3_close() and sqlite3_close_v2() return [SQLITE_OK] if ** the [sqlite3] object is successfully destroyed and all associated ** resources are deallocated. ** ** ^If the database connection is associated with unfinalized prepared | > > > > | | | | > > | | | | | < < < < < < < < < < | 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 | ** ** ^The sqlite3_close() and sqlite3_close_v2() routines are destructors ** for the [sqlite3] object. ** ^Calls to sqlite3_close() and sqlite3_close_v2() return [SQLITE_OK] if ** the [sqlite3] object is successfully destroyed and all associated ** resources are deallocated. ** ** Ideally, applications should [sqlite3_finalize | finalize] all ** [prepared statements], [sqlite3_blob_close | close] all [BLOB handles], and ** [sqlite3_backup_finish | finish] all [sqlite3_backup] objects associated ** with the [sqlite3] object prior to attempting to close the object. ** ^If the database connection is associated with unfinalized prepared ** statements, BLOB handlers, and/or unfinished sqlite3_backup objects then ** sqlite3_close() will leave the database connection open and return ** [SQLITE_BUSY]. ^If sqlite3_close_v2() is called with unfinalized prepared ** statements, unclosed BLOB handlers, and/or unfinished sqlite3_backups, ** it returns [SQLITE_OK] regardless, but instead of deallocating the database ** connection immediately, it marks the database connection as an unusable ** "zombie" and makes arrangements to automatically deallocate the database ** connection after all prepared statements are finalized, all BLOB handles ** are closed, and all backups have finished. The sqlite3_close_v2() interface ** is intended for use with host languages that are garbage collected, and ** where the order in which destructors are called is arbitrary. ** ** ^If an [sqlite3] object is destroyed while a transaction is open, ** the transaction is automatically rolled back. ** ** The C parameter to [sqlite3_close(C)] and [sqlite3_close_v2(C)] ** must be either a NULL ** pointer or an [sqlite3] object pointer obtained |
| ︙ | ︙ | |||
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 | #define SQLITE_IOERR_GETTEMPPATH (SQLITE_IOERR | (25<<8)) #define SQLITE_IOERR_CONVPATH (SQLITE_IOERR | (26<<8)) #define SQLITE_IOERR_VNODE (SQLITE_IOERR | (27<<8)) #define SQLITE_IOERR_AUTH (SQLITE_IOERR | (28<<8)) #define SQLITE_IOERR_BEGIN_ATOMIC (SQLITE_IOERR | (29<<8)) #define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8)) #define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) #define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8)) #define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8)) #define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2<<8)) #define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3<<8)) #define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4<<8)) #define SQLITE_CANTOPEN_DIRTYWAL (SQLITE_CANTOPEN | (5<<8)) /* Not Used */ #define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8)) #define SQLITE_CORRUPT_SEQUENCE (SQLITE_CORRUPT | (2<<8)) #define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8)) #define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8)) #define SQLITE_READONLY_ROLLBACK (SQLITE_READONLY | (3<<8)) #define SQLITE_READONLY_DBMOVED (SQLITE_READONLY | (4<<8)) #define SQLITE_READONLY_CANTINIT (SQLITE_READONLY | (5<<8)) #define SQLITE_READONLY_DIRECTORY (SQLITE_READONLY | (6<<8)) #define SQLITE_ABORT_ROLLBACK (SQLITE_ABORT | (2<<8)) #define SQLITE_CONSTRAINT_CHECK (SQLITE_CONSTRAINT | (1<<8)) #define SQLITE_CONSTRAINT_COMMITHOOK (SQLITE_CONSTRAINT | (2<<8)) #define SQLITE_CONSTRAINT_FOREIGNKEY (SQLITE_CONSTRAINT | (3<<8)) #define SQLITE_CONSTRAINT_FUNCTION (SQLITE_CONSTRAINT | (4<<8)) #define SQLITE_CONSTRAINT_NOTNULL (SQLITE_CONSTRAINT | (5<<8)) #define SQLITE_CONSTRAINT_PRIMARYKEY (SQLITE_CONSTRAINT | (6<<8)) #define SQLITE_CONSTRAINT_TRIGGER (SQLITE_CONSTRAINT | (7<<8)) #define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8<<8)) #define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9<<8)) #define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT |(10<<8)) #define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8)) #define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8)) #define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8)) #define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8)) #define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8)) /* ** CAPI3REF: Flags For File Open Operations ** ** These bit values are intended for use in the ** 3rd parameter to the [sqlite3_open_v2()] interface and ** in the 4th parameter to the [sqlite3_vfs.xOpen] method. | > > > > > > | 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 | #define SQLITE_IOERR_GETTEMPPATH (SQLITE_IOERR | (25<<8)) #define SQLITE_IOERR_CONVPATH (SQLITE_IOERR | (26<<8)) #define SQLITE_IOERR_VNODE (SQLITE_IOERR | (27<<8)) #define SQLITE_IOERR_AUTH (SQLITE_IOERR | (28<<8)) #define SQLITE_IOERR_BEGIN_ATOMIC (SQLITE_IOERR | (29<<8)) #define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8)) #define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) #define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) #define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8)) #define SQLITE_BUSY_TIMEOUT (SQLITE_BUSY | (3<<8)) #define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8)) #define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2<<8)) #define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3<<8)) #define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4<<8)) #define SQLITE_CANTOPEN_DIRTYWAL (SQLITE_CANTOPEN | (5<<8)) /* Not Used */ #define SQLITE_CANTOPEN_SYMLINK (SQLITE_CANTOPEN | (6<<8)) #define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8)) #define SQLITE_CORRUPT_SEQUENCE (SQLITE_CORRUPT | (2<<8)) #define SQLITE_CORRUPT_INDEX (SQLITE_CORRUPT | (3<<8)) #define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8)) #define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8)) #define SQLITE_READONLY_ROLLBACK (SQLITE_READONLY | (3<<8)) #define SQLITE_READONLY_DBMOVED (SQLITE_READONLY | (4<<8)) #define SQLITE_READONLY_CANTINIT (SQLITE_READONLY | (5<<8)) #define SQLITE_READONLY_DIRECTORY (SQLITE_READONLY | (6<<8)) #define SQLITE_ABORT_ROLLBACK (SQLITE_ABORT | (2<<8)) #define SQLITE_CONSTRAINT_CHECK (SQLITE_CONSTRAINT | (1<<8)) #define SQLITE_CONSTRAINT_COMMITHOOK (SQLITE_CONSTRAINT | (2<<8)) #define SQLITE_CONSTRAINT_FOREIGNKEY (SQLITE_CONSTRAINT | (3<<8)) #define SQLITE_CONSTRAINT_FUNCTION (SQLITE_CONSTRAINT | (4<<8)) #define SQLITE_CONSTRAINT_NOTNULL (SQLITE_CONSTRAINT | (5<<8)) #define SQLITE_CONSTRAINT_PRIMARYKEY (SQLITE_CONSTRAINT | (6<<8)) #define SQLITE_CONSTRAINT_TRIGGER (SQLITE_CONSTRAINT | (7<<8)) #define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8<<8)) #define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9<<8)) #define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT |(10<<8)) #define SQLITE_CONSTRAINT_PINNED (SQLITE_CONSTRAINT |(11<<8)) #define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8)) #define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8)) #define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8)) #define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8)) #define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8)) #define SQLITE_OK_SYMLINK (SQLITE_OK | (2<<8)) /* ** CAPI3REF: Flags For File Open Operations ** ** These bit values are intended for use in the ** 3rd parameter to the [sqlite3_open_v2()] interface and ** in the 4th parameter to the [sqlite3_vfs.xOpen] method. |
| ︙ | ︙ | |||
558 559 560 561 562 563 564 | #define SQLITE_OPEN_MEMORY 0x00000080 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_MAIN_DB 0x00000100 /* VFS only */ #define SQLITE_OPEN_TEMP_DB 0x00000200 /* VFS only */ #define SQLITE_OPEN_TRANSIENT_DB 0x00000400 /* VFS only */ #define SQLITE_OPEN_MAIN_JOURNAL 0x00000800 /* VFS only */ #define SQLITE_OPEN_TEMP_JOURNAL 0x00001000 /* VFS only */ #define SQLITE_OPEN_SUBJOURNAL 0x00002000 /* VFS only */ | | > > > > | 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 | #define SQLITE_OPEN_MEMORY 0x00000080 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_MAIN_DB 0x00000100 /* VFS only */ #define SQLITE_OPEN_TEMP_DB 0x00000200 /* VFS only */ #define SQLITE_OPEN_TRANSIENT_DB 0x00000400 /* VFS only */ #define SQLITE_OPEN_MAIN_JOURNAL 0x00000800 /* VFS only */ #define SQLITE_OPEN_TEMP_JOURNAL 0x00001000 /* VFS only */ #define SQLITE_OPEN_SUBJOURNAL 0x00002000 /* VFS only */ #define SQLITE_OPEN_SUPER_JOURNAL 0x00004000 /* VFS only */ #define SQLITE_OPEN_NOMUTEX 0x00008000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_SHAREDCACHE 0x00020000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_WAL 0x00080000 /* VFS only */ #define SQLITE_OPEN_NOFOLLOW 0x01000000 /* Ok for sqlite3_open_v2() */ /* Reserved: 0x00F00000 */ /* Legacy compatibility: */ #define SQLITE_OPEN_MASTER_JOURNAL 0x00004000 /* VFS only */ /* ** CAPI3REF: Device Characteristics ** ** The xDeviceCharacteristics method of the [sqlite3_io_methods] ** object returns an integer which is a vector of these ** bit values expressing I/O characteristics of the mass storage |
| ︙ | ︙ | |||
863 864 865 866 867 868 869 | ** sent to the VFS immediately before the xSync method is invoked on a ** database file descriptor. Or, if the xSync method is not invoked ** because the user has configured SQLite with ** [PRAGMA synchronous | PRAGMA synchronous=OFF] it is invoked in place ** of the xSync method. In most cases, the pointer argument passed with ** this file-control is NULL. However, if the database file is being synced ** as part of a multi-database commit, the argument points to a nul-terminated | | | 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 | ** sent to the VFS immediately before the xSync method is invoked on a ** database file descriptor. Or, if the xSync method is not invoked ** because the user has configured SQLite with ** [PRAGMA synchronous | PRAGMA synchronous=OFF] it is invoked in place ** of the xSync method. In most cases, the pointer argument passed with ** this file-control is NULL. However, if the database file is being synced ** as part of a multi-database commit, the argument points to a nul-terminated ** string containing the transactions super-journal file name. VFSes that ** do not need this signal should silently ignore this opcode. Applications ** should not call [sqlite3_file_control()] with this opcode as doing so may ** disrupt the operation of the specialized VFSes that do require it. ** ** <li>[[SQLITE_FCNTL_COMMIT_PHASETWO]] ** The [SQLITE_FCNTL_COMMIT_PHASETWO] opcode is generated internally by SQLite ** and sent to the VFS after a transaction has been committed immediately |
| ︙ | ︙ | |||
975 976 977 978 979 980 981 | ** file control occurs at the beginning of pragma statement analysis and so ** it is able to override built-in [PRAGMA] statements. ** ** <li>[[SQLITE_FCNTL_BUSYHANDLER]] ** ^The [SQLITE_FCNTL_BUSYHANDLER] ** file-control may be invoked by SQLite on the database file handle ** shortly after it is opened in order to provide a custom VFS with access | | | | | 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 | ** file control occurs at the beginning of pragma statement analysis and so ** it is able to override built-in [PRAGMA] statements. ** ** <li>[[SQLITE_FCNTL_BUSYHANDLER]] ** ^The [SQLITE_FCNTL_BUSYHANDLER] ** file-control may be invoked by SQLite on the database file handle ** shortly after it is opened in order to provide a custom VFS with access ** to the connection's busy-handler callback. The argument is of type (void**) ** - an array of two (void *) values. The first (void *) actually points ** to a function of type (int (*)(void *)). In order to invoke the connection's ** busy-handler, this function should be invoked with the second (void *) in ** the array as the only argument. If it returns non-zero, then the operation ** should be retried. If it returns zero, the custom VFS should abandon the ** current operation. ** ** <li>[[SQLITE_FCNTL_TEMPFILENAME]] ** ^Applications can invoke the [SQLITE_FCNTL_TEMPFILENAME] file-control ** to have SQLite generate a ** temporary filename using the same algorithm that is followed to generate ** temporary filenames for TEMP tables and other internal uses. The ** argument should be a char** which will be filled with the filename ** written into memory obtained from [sqlite3_malloc()]. The caller should ** invoke [sqlite3_free()] on the result to avoid a memory leak. ** |
| ︙ | ︙ | |||
1079 1080 1081 1082 1083 1084 1085 | ** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be rolled back. ** ^This file control takes the file descriptor out of batch write mode ** so that all subsequent write operations are independent. ** ^SQLite will never invoke SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE without ** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]. ** ** <li>[[SQLITE_FCNTL_LOCK_TIMEOUT]] | | > | < | > > | > > > > > > > > > > > | 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 | ** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be rolled back. ** ^This file control takes the file descriptor out of batch write mode ** so that all subsequent write operations are independent. ** ^SQLite will never invoke SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE without ** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]. ** ** <li>[[SQLITE_FCNTL_LOCK_TIMEOUT]] ** The [SQLITE_FCNTL_LOCK_TIMEOUT] opcode is used to configure a VFS ** to block for up to M milliseconds before failing when attempting to ** obtain a file lock using the xLock or xShmLock methods of the VFS. ** The parameter is a pointer to a 32-bit signed integer that contains ** the value that M is to be set to. Before returning, the 32-bit signed ** integer is overwritten with the previous value of M. ** ** <li>[[SQLITE_FCNTL_DATA_VERSION]] ** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to ** a database file. The argument is a pointer to a 32-bit unsigned integer. ** The "data version" for the pager is written into the pointer. The ** "data version" changes whenever any change occurs to the corresponding ** database file, either through SQL statements on the same database ** connection or through transactions committed by separate database ** connections possibly in other processes. The [sqlite3_total_changes()] ** interface can be used to find if any database on the connection has changed, ** but that interface responds to changes on TEMP as well as MAIN and does ** not provide a mechanism to detect changes to MAIN only. Also, the ** [sqlite3_total_changes()] interface responds to internal changes only and ** omits changes made by other database connections. The ** [PRAGMA data_version] command provides a mechanism to detect changes to ** a single attached database that occur due to other database connections, ** but omits changes implemented by the database connection on which it is ** called. This file control is the only mechanism to detect changes that ** happen either internally or externally and that are associated with ** a particular attached database. ** ** <li>[[SQLITE_FCNTL_CKPT_START]] ** The [SQLITE_FCNTL_CKPT_START] opcode is invoked from within a checkpoint ** in wal mode before the client starts to copy pages from the wal ** file to the database file. ** ** <li>[[SQLITE_FCNTL_CKPT_DONE]] ** The [SQLITE_FCNTL_CKPT_DONE] opcode is invoked from within a checkpoint ** in wal mode after the client has finished copying pages from the wal ** file to the database file, but before the *-shm file is updated to ** record the fact that the pages have been checkpointed. ** </ul> */ #define SQLITE_FCNTL_LOCKSTATE 1 #define SQLITE_FCNTL_GET_LOCKPROXYFILE 2 #define SQLITE_FCNTL_SET_LOCKPROXYFILE 3 #define SQLITE_FCNTL_LAST_ERRNO 4 #define SQLITE_FCNTL_SIZE_HINT 5 |
| ︙ | ︙ | |||
1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 | #define SQLITE_FCNTL_PDB 30 #define SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31 #define SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32 #define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33 #define SQLITE_FCNTL_LOCK_TIMEOUT 34 #define SQLITE_FCNTL_DATA_VERSION 35 #define SQLITE_FCNTL_SIZE_LIMIT 36 /* deprecated names */ #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE #define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE #define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO | > > > | 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 | #define SQLITE_FCNTL_PDB 30 #define SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31 #define SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32 #define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33 #define SQLITE_FCNTL_LOCK_TIMEOUT 34 #define SQLITE_FCNTL_DATA_VERSION 35 #define SQLITE_FCNTL_SIZE_LIMIT 36 #define SQLITE_FCNTL_CKPT_DONE 37 #define SQLITE_FCNTL_RESERVE_BYTES 38 #define SQLITE_FCNTL_CKPT_START 39 /* deprecated names */ #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE #define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE #define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO |
| ︙ | ︙ | |||
1185 1186 1187 1188 1189 1190 1191 | ** the end. Each time such an extension occurs, the iVersion field ** is incremented. The iVersion value started out as 1 in ** SQLite [version 3.5.0] on [dateof:3.5.0], then increased to 2 ** with SQLite [version 3.7.0] on [dateof:3.7.0], and then increased ** to 3 with SQLite [version 3.7.6] on [dateof:3.7.6]. Additional fields ** may be appended to the sqlite3_vfs object and the iVersion value ** may increase again in future versions of SQLite. | | | | | 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 | ** the end. Each time such an extension occurs, the iVersion field ** is incremented. The iVersion value started out as 1 in ** SQLite [version 3.5.0] on [dateof:3.5.0], then increased to 2 ** with SQLite [version 3.7.0] on [dateof:3.7.0], and then increased ** to 3 with SQLite [version 3.7.6] on [dateof:3.7.6]. Additional fields ** may be appended to the sqlite3_vfs object and the iVersion value ** may increase again in future versions of SQLite. ** Note that due to an oversight, the structure ** of the sqlite3_vfs object changed in the transition from ** SQLite [version 3.5.9] to [version 3.6.0] on [dateof:3.6.0] ** and yet the iVersion field was not increased. ** ** The szOsFile field is the size of the subclassed [sqlite3_file] ** structure used by this VFS. mxPathname is the maximum length of ** a pathname in this VFS. ** ** Registered sqlite3_vfs objects are kept on a linked list formed by ** the pNext pointer. The [sqlite3_vfs_register()] |
| ︙ | ︙ | |||
1244 1245 1246 1247 1248 1249 1250 | ** <ul> ** <li> [SQLITE_OPEN_MAIN_DB] ** <li> [SQLITE_OPEN_MAIN_JOURNAL] ** <li> [SQLITE_OPEN_TEMP_DB] ** <li> [SQLITE_OPEN_TEMP_JOURNAL] ** <li> [SQLITE_OPEN_TRANSIENT_DB] ** <li> [SQLITE_OPEN_SUBJOURNAL] | | | 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 | ** <ul> ** <li> [SQLITE_OPEN_MAIN_DB] ** <li> [SQLITE_OPEN_MAIN_JOURNAL] ** <li> [SQLITE_OPEN_TEMP_DB] ** <li> [SQLITE_OPEN_TEMP_JOURNAL] ** <li> [SQLITE_OPEN_TRANSIENT_DB] ** <li> [SQLITE_OPEN_SUBJOURNAL] ** <li> [SQLITE_OPEN_SUPER_JOURNAL] ** <li> [SQLITE_OPEN_WAL] ** </ul>)^ ** ** The file I/O implementation can use the object type flags to ** change the way it deals with files. For example, an application ** that does not care about crash recovery or rollback might make ** the open of a journal file a no-op. Writes to this journal would |
| ︙ | ︙ | |||
1279 1280 1281 1282 1283 1284 1285 | ** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the ** SQLITE_OPEN_CREATE, is used to indicate that file should always ** be created, and that it is an error if it already exists. ** It is <i>not</i> used to indicate the file should be opened ** for exclusive access. ** ** ^At least szOsFile bytes of memory are allocated by SQLite | | | 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 | ** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the ** SQLITE_OPEN_CREATE, is used to indicate that file should always ** be created, and that it is an error if it already exists. ** It is <i>not</i> used to indicate the file should be opened ** for exclusive access. ** ** ^At least szOsFile bytes of memory are allocated by SQLite ** to hold the [sqlite3_file] structure passed as the third ** argument to xOpen. The xOpen method does not have to ** allocate the structure; it should just fill it in. Note that ** the xOpen method must set the sqlite3_file.pMethods to either ** a valid [sqlite3_io_methods] object or to NULL. xOpen must do ** this even if the open fails. SQLite expects that the sqlite3_file.pMethods ** element will be valid after xOpen returns regardless of the success ** or failure of the xOpen call. |
| ︙ | ︙ | |||
1616 1617 1618 1619 1620 1621 1622 | ** allocators round up memory allocations at least to the next multiple ** of 8. Some allocators round up to a larger multiple or to a power of 2. ** Every memory allocation request coming in through [sqlite3_malloc()] ** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0, ** that causes the corresponding memory allocation to fail. ** ** The xInit method initializes the memory allocator. For example, | | | | 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 | ** allocators round up memory allocations at least to the next multiple ** of 8. Some allocators round up to a larger multiple or to a power of 2. ** Every memory allocation request coming in through [sqlite3_malloc()] ** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0, ** that causes the corresponding memory allocation to fail. ** ** The xInit method initializes the memory allocator. For example, ** it might allocate any required mutexes or initialize internal data ** structures. The xShutdown method is invoked (indirectly) by ** [sqlite3_shutdown()] and should deallocate any resources acquired ** by xInit. The pAppData pointer is used as the only parameter to ** xInit and xShutdown. ** ** SQLite holds the [SQLITE_MUTEX_STATIC_MAIN] mutex when it invokes ** the xInit method, so the xInit method need not be threadsafe. The ** xShutdown method is only called from [sqlite3_shutdown()] so it does ** not need to be threadsafe either. For all other methods, SQLite ** holds the [SQLITE_MUTEX_STATIC_MEM] mutex as long as the ** [SQLITE_CONFIG_MEMSTATUS] configuration option is turned on (which ** it is by default) and so the methods are automatically serialized. ** However, if [SQLITE_CONFIG_MEMSTATUS] is disabled, then the other |
| ︙ | ︙ | |||
1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 | ** ** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt> ** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int, ** interpreted as a boolean, which enables or disables the collection of ** memory allocation statistics. ^(When memory allocation statistics are ** disabled, the following SQLite interfaces become non-operational: ** <ul> ** <li> [sqlite3_memory_used()] ** <li> [sqlite3_memory_highwater()] ** <li> [sqlite3_soft_heap_limit64()] ** <li> [sqlite3_status64()] ** </ul>)^ ** ^Memory allocation statistics are enabled by default unless SQLite is ** compiled with [SQLITE_DEFAULT_MEMSTATUS]=0 in which case memory ** allocation statistics are disabled by default. ** </dd> ** ** [[SQLITE_CONFIG_SCRATCH]] <dt>SQLITE_CONFIG_SCRATCH</dt> ** <dd> The SQLITE_CONFIG_SCRATCH option is no longer used. ** </dd> ** ** [[SQLITE_CONFIG_PAGECACHE]] <dt>SQLITE_CONFIG_PAGECACHE</dt> ** <dd> ^The SQLITE_CONFIG_PAGECACHE option specifies a memory pool ** that SQLite can use for the database page cache with the default page ** cache implementation. | > | | 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 | ** ** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt> ** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int, ** interpreted as a boolean, which enables or disables the collection of ** memory allocation statistics. ^(When memory allocation statistics are ** disabled, the following SQLite interfaces become non-operational: ** <ul> ** <li> [sqlite3_hard_heap_limit64()] ** <li> [sqlite3_memory_used()] ** <li> [sqlite3_memory_highwater()] ** <li> [sqlite3_soft_heap_limit64()] ** <li> [sqlite3_status64()] ** </ul>)^ ** ^Memory allocation statistics are enabled by default unless SQLite is ** compiled with [SQLITE_DEFAULT_MEMSTATUS]=0 in which case memory ** allocation statistics are disabled by default. ** </dd> ** ** [[SQLITE_CONFIG_SCRATCH]] <dt>SQLITE_CONFIG_SCRATCH</dt> ** <dd> The SQLITE_CONFIG_SCRATCH option is no longer used. ** </dd> ** ** [[SQLITE_CONFIG_PAGECACHE]] <dt>SQLITE_CONFIG_PAGECACHE</dt> ** <dd> ^The SQLITE_CONFIG_PAGECACHE option specifies a memory pool ** that SQLite can use for the database page cache with the default page ** cache implementation. ** This configuration option is a no-op if an application-defined page ** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2]. ** ^There are three arguments to SQLITE_CONFIG_PAGECACHE: A pointer to ** 8-byte aligned memory (pMem), the size of each page cache line (sz), ** and the number of cache lines (N). ** The sz argument should be the size of the largest database page ** (a power of two between 512 and 65536) plus some extra bytes for each ** page header. ^The number of extra bytes needed by the page header |
| ︙ | ︙ | |||
2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 | ** The first argument is an integer which is 0 to disable triggers, ** positive to enable triggers or negative to leave the setting unchanged. ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether triggers are disabled or enabled ** following this call. The second parameter may be a NULL pointer, in ** which case the trigger setting is not reported back. </dd> ** ** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] ** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt> ** <dd> ^This option is used to enable or disable the ** [fts3_tokenizer()] function which is part of the ** [FTS3] full-text search engine extension. ** There should be two additional arguments. ** The first argument is an integer which is 0 to disable fts3_tokenizer() or | > > > > > > > > > > > | 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 | ** The first argument is an integer which is 0 to disable triggers, ** positive to enable triggers or negative to leave the setting unchanged. ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether triggers are disabled or enabled ** following this call. The second parameter may be a NULL pointer, in ** which case the trigger setting is not reported back. </dd> ** ** [[SQLITE_DBCONFIG_ENABLE_VIEW]] ** <dt>SQLITE_DBCONFIG_ENABLE_VIEW</dt> ** <dd> ^This option is used to enable or disable [CREATE VIEW | views]. ** There should be two additional arguments. ** The first argument is an integer which is 0 to disable views, ** positive to enable views or negative to leave the setting unchanged. ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether views are disabled or enabled ** following this call. The second parameter may be a NULL pointer, in ** which case the view setting is not reported back. </dd> ** ** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] ** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt> ** <dd> ^This option is used to enable or disable the ** [fts3_tokenizer()] function which is part of the ** [FTS3] full-text search engine extension. ** There should be two additional arguments. ** The first argument is an integer which is 0 to disable fts3_tokenizer() or |
| ︙ | ︙ | |||
2230 2231 2232 2233 2234 2235 2236 | ** additional information. This feature can also be turned on and off ** using the [PRAGMA legacy_alter_table] statement. ** </dd> ** ** [[SQLITE_DBCONFIG_DQS_DML]] ** <dt>SQLITE_DBCONFIG_DQS_DML</td> ** <dd>The SQLITE_DBCONFIG_DQS_DML option activates or deactivates | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 | ** additional information. This feature can also be turned on and off ** using the [PRAGMA legacy_alter_table] statement. ** </dd> ** ** [[SQLITE_DBCONFIG_DQS_DML]] ** <dt>SQLITE_DBCONFIG_DQS_DML</td> ** <dd>The SQLITE_DBCONFIG_DQS_DML option activates or deactivates ** the legacy [double-quoted string literal] misfeature for DML statements ** only, that is DELETE, INSERT, SELECT, and UPDATE statements. The ** default value of this setting is determined by the [-DSQLITE_DQS] ** compile-time option. ** </dd> ** ** [[SQLITE_DBCONFIG_DQS_DDL]] ** <dt>SQLITE_DBCONFIG_DQS_DDL</td> ** <dd>The SQLITE_DBCONFIG_DQS option activates or deactivates ** the legacy [double-quoted string literal] misfeature for DDL statements, ** such as CREATE TABLE and CREATE INDEX. The ** default value of this setting is determined by the [-DSQLITE_DQS] ** compile-time option. ** </dd> ** ** [[SQLITE_DBCONFIG_TRUSTED_SCHEMA]] ** <dt>SQLITE_DBCONFIG_TRUSTED_SCHEMA</td> ** <dd>The SQLITE_DBCONFIG_TRUSTED_SCHEMA option tells SQLite to ** assume that database schemas are untainted by malicious content. ** When the SQLITE_DBCONFIG_TRUSTED_SCHEMA option is disabled, SQLite ** takes additional defensive steps to protect the application from harm ** including: ** <ul> ** <li> Prohibit the use of SQL functions inside triggers, views, ** CHECK constraints, DEFAULT clauses, expression indexes, ** partial indexes, or generated columns ** unless those functions are tagged with [SQLITE_INNOCUOUS]. ** <li> Prohibit the use of virtual tables inside of triggers or views ** unless those virtual tables are tagged with [SQLITE_VTAB_INNOCUOUS]. ** </ul> ** This setting defaults to "on" for legacy compatibility, however ** all applications are advised to turn it off if possible. This setting ** can also be controlled using the [PRAGMA trusted_schema] statement. ** </dd> ** ** [[SQLITE_DBCONFIG_LEGACY_FILE_FORMAT]] ** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</td> ** <dd>The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates ** the legacy file format flag. When activated, this flag causes all newly ** created database file to have a schema format version number (the 4-byte ** integer found at offset 44 into the database header) of 1. This in turn ** means that the resulting database file will be readable and writable by ** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting, ** newly created databases are generally not understandable by SQLite versions ** prior to 3.3.0 ([dateof:3.3.0]). As these words are written, there ** is now scarcely any need to generated database files that are compatible ** all the way back to version 3.0.0, and so this setting is of little ** practical use, but is provided so that SQLite can continue to claim the ** ability to generate new database files that are compatible with version ** 3.0.0. ** <p>Note that when the SQLITE_DBCONFIG_LEGACY_FILE_FORMAT setting is on, ** the [VACUUM] command will fail with an obscure error when attempting to ** process a table with generated columns and a descending index. This is ** not considered a bug since SQLite versions 3.3.0 and earlier do not support ** either generated columns or decending indexes. ** </dd> ** </dl> */ #define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ #define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ #define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005 /* int int* */ #define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_QPSG 1007 /* int int* */ #define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */ #define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */ #define SQLITE_DBCONFIG_DEFENSIVE 1010 /* int int* */ #define SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011 /* int int* */ #define SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012 /* int int* */ #define SQLITE_DBCONFIG_DQS_DML 1013 /* int int* */ #define SQLITE_DBCONFIG_DQS_DDL 1014 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_VIEW 1015 /* int int* */ #define SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016 /* int int* */ #define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017 /* int int* */ #define SQLITE_DBCONFIG_MAX 1017 /* Largest DBCONFIG */ /* ** CAPI3REF: Enable Or Disable Extended Result Codes ** METHOD: sqlite3 ** ** ^The sqlite3_extended_result_codes() routine enables or disables the ** [extended result codes] feature of SQLite. ^The extended result |
| ︙ | ︙ | |||
2467 2468 2469 2470 2471 2472 2473 | ** ^If the interrupted SQL operation is an INSERT, UPDATE, or DELETE ** that is inside an explicit transaction, then the entire transaction ** will be rolled back automatically. ** ** ^The sqlite3_interrupt(D) call is in effect until all currently running ** SQL statements on [database connection] D complete. ^Any new SQL statements ** that are started after the sqlite3_interrupt() call and before the | | | 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 | ** ^If the interrupted SQL operation is an INSERT, UPDATE, or DELETE ** that is inside an explicit transaction, then the entire transaction ** will be rolled back automatically. ** ** ^The sqlite3_interrupt(D) call is in effect until all currently running ** SQL statements on [database connection] D complete. ^Any new SQL statements ** that are started after the sqlite3_interrupt() call and before the ** running statement count reaches zero are interrupted as if they had been ** running prior to the sqlite3_interrupt() call. ^New SQL statements ** that are started after the running statement count reaches zero are ** not effected by the sqlite3_interrupt(). ** ^A call to sqlite3_interrupt(D) that occurs when there are no running ** SQL statements is a no-op and has no effect on SQL statements ** that are started after the sqlite3_interrupt() call returns. */ |
| ︙ | ︙ | |||
2635 2636 2637 2638 2639 2640 2641 | ** Name | Age ** ----------------------- ** Alice | 43 ** Bob | 28 ** Cindy | 21 ** </pre></blockquote> ** | | | | 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 | ** Name | Age ** ----------------------- ** Alice | 43 ** Bob | 28 ** Cindy | 21 ** </pre></blockquote> ** ** There are two columns (M==2) and three rows (N==3). Thus the ** result table has 8 entries. Suppose the result table is stored ** in an array named azResult. Then azResult holds this content: ** ** <blockquote><pre> ** azResult[0] = "Name"; ** azResult[1] = "Age"; ** azResult[2] = "Alice"; ** azResult[3] = "43"; ** azResult[4] = "Bob"; |
| ︙ | ︙ | |||
2730 2731 2732 2733 2734 2735 2736 | SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list); /* ** CAPI3REF: Memory Allocation Subsystem ** ** The SQLite core uses these three routines for all of its own ** internal memory allocation needs. "Core" in the previous sentence | | | 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 | SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list); /* ** CAPI3REF: Memory Allocation Subsystem ** ** The SQLite core uses these three routines for all of its own ** internal memory allocation needs. "Core" in the previous sentence ** does not include operating-system specific [VFS] implementation. The ** Windows VFS uses native malloc() and free() for some operations. ** ** ^The sqlite3_malloc() routine returns a pointer to a block ** of memory at least N bytes in length, where N is the parameter. ** ^If sqlite3_malloc() is unable to obtain sufficient free ** memory, it returns a NULL pointer. ^If the parameter N to ** sqlite3_malloc() is zero or negative then sqlite3_malloc() returns |
| ︙ | ︙ | |||
2791 2792 2793 2794 2795 2796 2797 | ** ** ^The memory returned by sqlite3_malloc(), sqlite3_realloc(), ** sqlite3_malloc64(), and sqlite3_realloc64() ** is always aligned to at least an 8 byte boundary, or to a ** 4 byte boundary if the [SQLITE_4_BYTE_ALIGNED_MALLOC] compile-time ** option is used. ** | < < < < < < < < < < < < < | 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 | ** ** ^The memory returned by sqlite3_malloc(), sqlite3_realloc(), ** sqlite3_malloc64(), and sqlite3_realloc64() ** is always aligned to at least an 8 byte boundary, or to a ** 4 byte boundary if the [SQLITE_4_BYTE_ALIGNED_MALLOC] compile-time ** option is used. ** ** The pointer arguments to [sqlite3_free()] and [sqlite3_realloc()] ** must be either NULL or else pointers obtained from a prior ** invocation of [sqlite3_malloc()] or [sqlite3_realloc()] that have ** not yet been released. ** ** The application must not read or write any part of ** a block of memory after it has been released using |
| ︙ | ︙ | |||
2852 2853 2854 2855 2856 2857 2858 | /* ** CAPI3REF: Pseudo-Random Number Generator ** ** SQLite contains a high-quality pseudo-random number generator (PRNG) used to ** select random [ROWID | ROWIDs] when inserting new records into a table that ** already uses the largest possible [ROWID]. The PRNG is also used for | | | 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 | /* ** CAPI3REF: Pseudo-Random Number Generator ** ** SQLite contains a high-quality pseudo-random number generator (PRNG) used to ** select random [ROWID | ROWIDs] when inserting new records into a table that ** already uses the largest possible [ROWID]. The PRNG is also used for ** the built-in random() and randomblob() SQL functions. This interface allows ** applications to access the same PRNG for other purposes. ** ** ^A call to this routine stores N bytes of randomness into buffer P. ** ^The P parameter can be a NULL pointer. ** ** ^If this routine has not been previously called or if the previous ** call had N less than one or a NULL pointer for P, then the PRNG is |
| ︙ | ︙ | |||
3226 3227 3228 3229 3230 3231 3232 | ** Whether or not an error occurs when it is opened, resources ** associated with the [database connection] handle should be released by ** passing it to [sqlite3_close()] when it is no longer required. ** ** The sqlite3_open_v2() interface works like sqlite3_open() ** except that it accepts two additional parameters for additional control ** over the new database connection. ^(The flags parameter to | | | < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < < < < < < < < < < < < | 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 | ** Whether or not an error occurs when it is opened, resources ** associated with the [database connection] handle should be released by ** passing it to [sqlite3_close()] when it is no longer required. ** ** The sqlite3_open_v2() interface works like sqlite3_open() ** except that it accepts two additional parameters for additional control ** over the new database connection. ^(The flags parameter to ** sqlite3_open_v2() must include, at a minimum, one of the following ** three flag combinations:)^ ** ** <dl> ** ^(<dt>[SQLITE_OPEN_READONLY]</dt> ** <dd>The database is opened in read-only mode. If the database does not ** already exist, an error is returned.</dd>)^ ** ** ^(<dt>[SQLITE_OPEN_READWRITE]</dt> ** <dd>The database is opened for reading and writing if possible, or reading ** only if the file is write protected by the operating system. In either ** case the database must already exist, otherwise an error is returned.</dd>)^ ** ** ^(<dt>[SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]</dt> ** <dd>The database is opened for reading and writing, and is created if ** it does not already exist. This is the behavior that is always used for ** sqlite3_open() and sqlite3_open16().</dd>)^ ** </dl> ** ** In addition to the required flags, the following optional flags are ** also supported: ** ** <dl> ** ^(<dt>[SQLITE_OPEN_URI]</dt> ** <dd>The filename can be interpreted as a URI if this flag is set.</dd>)^ ** ** ^(<dt>[SQLITE_OPEN_MEMORY]</dt> ** <dd>The database will be opened as an in-memory database. The database ** is named by the "filename" argument for the purposes of cache-sharing, ** if shared cache mode is enabled, but the "filename" is otherwise ignored. ** </dd>)^ ** ** ^(<dt>[SQLITE_OPEN_NOMUTEX]</dt> ** <dd>The new database connection will use the "multi-thread" ** [threading mode].)^ This means that separate threads are allowed ** to use SQLite at the same time, as long as each thread is using ** a different [database connection]. ** ** ^(<dt>[SQLITE_OPEN_FULLMUTEX]</dt> ** <dd>The new database connection will use the "serialized" ** [threading mode].)^ This means the multiple threads can safely ** attempt to use the same database connection at the same time. ** (Mutexes will block any actual concurrency, but in this mode ** there is no harm in trying.) ** ** ^(<dt>[SQLITE_OPEN_SHAREDCACHE]</dt> ** <dd>The database is opened [shared cache] enabled, overriding ** the default shared cache setting provided by ** [sqlite3_enable_shared_cache()].)^ ** ** ^(<dt>[SQLITE_OPEN_PRIVATECACHE]</dt> ** <dd>The database is opened [shared cache] disabled, overriding ** the default shared cache setting provided by ** [sqlite3_enable_shared_cache()].)^ ** ** [[OPEN_NOFOLLOW]] ^(<dt>[SQLITE_OPEN_NOFOLLOW]</dt> ** <dd>The database filename is not allowed to be a symbolic link</dd> ** </dl>)^ ** ** If the 3rd parameter to sqlite3_open_v2() is not one of the ** required combinations shown above optionally combined with other ** [SQLITE_OPEN_READONLY | SQLITE_OPEN_* bits] ** then the behavior is undefined. ** ** ^The fourth parameter to sqlite3_open_v2() is the name of the ** [sqlite3_vfs] object that defines the operating system interface that ** the new database connection should use. ^If the fourth parameter is ** a NULL pointer then the default [sqlite3_vfs] object is used. ** ** ^If the filename is ":memory:", then a private, temporary in-memory database ** is created for the connection. ^This in-memory database will vanish when |
| ︙ | ︙ | |||
3443 3444 3445 3446 3447 3448 3449 | int flags, /* Flags */ const char *zVfs /* Name of VFS module to use */ ); /* ** CAPI3REF: Obtain Values For URI Parameters ** | | | > > > > | > > > | > | > > | | | > > > > > > > | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 | int flags, /* Flags */ const char *zVfs /* Name of VFS module to use */ ); /* ** CAPI3REF: Obtain Values For URI Parameters ** ** These are utility routines, useful to [VFS|custom VFS implementations], ** that check if a database file was a URI that contained a specific query ** parameter, and if so obtains the value of that query parameter. ** ** The first parameter to these interfaces (hereafter referred to ** as F) must be one of: ** <ul> ** <li> A database filename pointer created by the SQLite core and ** passed into the xOpen() method of a VFS implemention, or ** <li> A filename obtained from [sqlite3_db_filename()], or ** <li> A new filename constructed using [sqlite3_create_filename()]. ** </ul> ** If the F parameter is not one of the above, then the behavior is ** undefined and probably undesirable. Older versions of SQLite were ** more tolerant of invalid F parameters than newer versions. ** ** If F is a suitable filename (as described in the previous paragraph) ** and if P is the name of the query parameter, then ** sqlite3_uri_parameter(F,P) returns the value of the P ** parameter if it exists or a NULL pointer if P does not appear as a ** query parameter on F. If P is a query parameter of F and it ** has no explicit value, then sqlite3_uri_parameter(F,P) returns ** a pointer to an empty string. ** ** The sqlite3_uri_boolean(F,P,B) routine assumes that P is a boolean ** parameter and returns true (1) or false (0) according to the value ** of P. The sqlite3_uri_boolean(F,P,B) routine returns true (1) if the ** value of query parameter P is one of "yes", "true", or "on" in any ** case or if the value begins with a non-zero number. The ** sqlite3_uri_boolean(F,P,B) routines returns false (0) if the value of ** query parameter P is one of "no", "false", or "off" in any case or ** if the value begins with a numeric zero. If P is not a query ** parameter on F or if the value of P does not match any of the ** above, then sqlite3_uri_boolean(F,P,B) returns (B!=0). ** ** The sqlite3_uri_int64(F,P,D) routine converts the value of P into a ** 64-bit signed integer and returns that integer, or D if P does not ** exist. If the value of P is something other than an integer, then ** zero is returned. ** ** The sqlite3_uri_key(F,N) returns a pointer to the name (not ** the value) of the N-th query parameter for filename F, or a NULL ** pointer if N is less than zero or greater than the number of query ** parameters minus 1. The N value is zero-based so N should be 0 to obtain ** the name of the first query parameter, 1 for the second parameter, and ** so forth. ** ** If F is a NULL pointer, then sqlite3_uri_parameter(F,P) returns NULL and ** sqlite3_uri_boolean(F,P,B) returns B. If F is not a NULL pointer and ** is not a database file pathname pointer that the SQLite core passed ** into the xOpen VFS method, then the behavior of this routine is undefined ** and probably undesirable. ** ** Beginning with SQLite [version 3.31.0] ([dateof:3.31.0]) the input F ** parameter can also be the name of a rollback journal file or WAL file ** in addition to the main database file. Prior to version 3.31.0, these ** routines would only work if F was the name of the main database file. ** When the F parameter is the name of the rollback journal or WAL file, ** it has access to all the same query parameters as were found on the ** main database file. ** ** See the [URI filename] documentation for additional information. */ SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam); SQLITE_API int sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault); SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64); SQLITE_API const char *sqlite3_uri_key(const char *zFilename, int N); /* ** CAPI3REF: Translate filenames ** ** These routines are available to [VFS|custom VFS implementations] for ** translating filenames between the main database file, the journal file, ** and the WAL file. ** ** If F is the name of an sqlite database file, journal file, or WAL file ** passed by the SQLite core into the VFS, then sqlite3_filename_database(F) ** returns the name of the corresponding database file. ** ** If F is the name of an sqlite database file, journal file, or WAL file ** passed by the SQLite core into the VFS, or if F is a database filename ** obtained from [sqlite3_db_filename()], then sqlite3_filename_journal(F) ** returns the name of the corresponding rollback journal file. ** ** If F is the name of an sqlite database file, journal file, or WAL file ** that was passed by the SQLite core into the VFS, or if F is a database ** filename obtained from [sqlite3_db_filename()], then ** sqlite3_filename_wal(F) returns the name of the corresponding ** WAL file. ** ** In all of the above, if F is not the name of a database, journal or WAL ** filename passed into the VFS from the SQLite core and F is not the ** return value from [sqlite3_db_filename()], then the result is ** undefined and is likely a memory access violation. */ SQLITE_API const char *sqlite3_filename_database(const char*); SQLITE_API const char *sqlite3_filename_journal(const char*); SQLITE_API const char *sqlite3_filename_wal(const char*); /* ** CAPI3REF: Database File Corresponding To A Journal ** ** ^If X is the name of a rollback or WAL-mode journal file that is ** passed into the xOpen method of [sqlite3_vfs], then ** sqlite3_database_file_object(X) returns a pointer to the [sqlite3_file] ** object that represents the main database file. ** ** This routine is intended for use in custom [VFS] implementations ** only. It is not a general-purpose interface. ** The argument sqlite3_file_object(X) must be a filename pointer that ** has been passed into [sqlite3_vfs].xOpen method where the ** flags parameter to xOpen contains one of the bits ** [SQLITE_OPEN_MAIN_JOURNAL] or [SQLITE_OPEN_WAL]. Any other use ** of this routine results in undefined and probably undesirable ** behavior. */ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*); /* ** CAPI3REF: Create and Destroy VFS Filenames ** ** These interfces are provided for use by [VFS shim] implementations and ** are not useful outside of that context. ** ** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of ** database filename D with corresponding journal file J and WAL file W and ** with N URI parameters key/values pairs in the array P. The result from ** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that ** is safe to pass to routines like: ** <ul> ** <li> [sqlite3_uri_parameter()], ** <li> [sqlite3_uri_boolean()], ** <li> [sqlite3_uri_int64()], ** <li> [sqlite3_uri_key()], ** <li> [sqlite3_filename_database()], ** <li> [sqlite3_filename_journal()], or ** <li> [sqlite3_filename_wal()]. ** </ul> ** If a memory allocation error occurs, sqlite3_create_filename() might ** return a NULL pointer. The memory obtained from sqlite3_create_filename(X) ** must be released by a corresponding call to sqlite3_free_filename(Y). ** ** The P parameter in sqlite3_create_filename(D,J,W,N,P) should be an array ** of 2*N pointers to strings. Each pair of pointers in this array corresponds ** to a key and value for a query parameter. The P parameter may be a NULL ** pointer if N is zero. None of the 2*N pointers in the P array may be ** NULL pointers and key pointers should not be empty strings. ** None of the D, J, or W parameters to sqlite3_create_filename(D,J,W,N,P) may ** be NULL pointers, though they can be empty strings. ** ** The sqlite3_free_filename(Y) routine releases a memory allocation ** previously obtained from sqlite3_create_filename(). Invoking ** sqlite3_free_filename(Y) where Y is a NULL pointer is a harmless no-op. ** ** If the Y parameter to sqlite3_free_filename(Y) is anything other ** than a NULL pointer or a pointer previously acquired from ** sqlite3_create_filename(), then bad things such as heap ** corruption or segfaults may occur. The value Y should be ** used again after sqlite3_free_filename(Y) has been called. This means ** that if the [sqlite3_vfs.xOpen()] method of a VFS has been called using Y, ** then the corresponding [sqlite3_module.xClose() method should also be ** invoked prior to calling sqlite3_free_filename(Y). */ SQLITE_API char *sqlite3_create_filename( const char *zDatabase, const char *zJournal, const char *zWal, int nParam, const char **azParam ); SQLITE_API void sqlite3_free_filename(char*); /* ** CAPI3REF: Error Codes And Messages ** METHOD: sqlite3 ** ** ^If the most recent sqlite3_* API call associated with ** [database connection] D failed, then the sqlite3_errcode(D) interface |
| ︙ | ︙ | |||
3802 3803 3804 3805 3806 3807 3808 | ** [sqlite3_step()] would only return a generic [SQLITE_ERROR] result code ** and the application would have to make a second call to [sqlite3_reset()] ** in order to find the underlying cause of the problem. With the "v2" prepare ** interfaces, the underlying reason for the error is returned immediately. ** </li> ** ** <li> | | | | | | 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 | ** [sqlite3_step()] would only return a generic [SQLITE_ERROR] result code ** and the application would have to make a second call to [sqlite3_reset()] ** in order to find the underlying cause of the problem. With the "v2" prepare ** interfaces, the underlying reason for the error is returned immediately. ** </li> ** ** <li> ** ^If the specific value bound to a [parameter | host parameter] in the ** WHERE clause might influence the choice of query plan for a statement, ** then the statement will be automatically recompiled, as if there had been ** a schema change, on the first [sqlite3_step()] call following any change ** to the [sqlite3_bind_text | bindings] of that [parameter]. ** ^The specific value of a WHERE-clause [parameter] might influence the ** choice of query plan if the parameter is the left-hand side of a [LIKE] ** or [GLOB] operator or if the parameter is compared to an indexed column ** and the [SQLITE_ENABLE_STAT4] compile-time option is enabled. ** </li> ** </ol> ** ** <p>^sqlite3_prepare_v3() differs from sqlite3_prepare_v2() only in having ** the extra prepFlags parameter, which is a bit array consisting of zero or ** more of the [SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_*] flags. ^The ** sqlite3_prepare_v2() interface works exactly the same as |
| ︙ | ︙ | |||
4067 4068 4069 4070 4071 4072 4073 | ** ^The leftmost SQL parameter has an index of 1. ^When the same named ** SQL parameter is used more than once, second and subsequent ** occurrences have the same index as the first occurrence. ** ^The index for named parameters can be looked up using the ** [sqlite3_bind_parameter_index()] API if desired. ^The index ** for "?NNN" parameters is the value of NNN. ** ^The NNN value must be between 1 and the [sqlite3_limit()] | | > > > > > > > > > > > > > > > > > > | | 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 | ** ^The leftmost SQL parameter has an index of 1. ^When the same named ** SQL parameter is used more than once, second and subsequent ** occurrences have the same index as the first occurrence. ** ^The index for named parameters can be looked up using the ** [sqlite3_bind_parameter_index()] API if desired. ^The index ** for "?NNN" parameters is the value of NNN. ** ^The NNN value must be between 1 and the [sqlite3_limit()] ** parameter [SQLITE_LIMIT_VARIABLE_NUMBER] (default value: 32766). ** ** ^The third argument is the value to bind to the parameter. ** ^If the third parameter to sqlite3_bind_text() or sqlite3_bind_text16() ** or sqlite3_bind_blob() is a NULL pointer then the fourth parameter ** is ignored and the end result is the same as sqlite3_bind_null(). ** ^If the third parameter to sqlite3_bind_text() is not NULL, then ** it should be a pointer to well-formed UTF8 text. ** ^If the third parameter to sqlite3_bind_text16() is not NULL, then ** it should be a pointer to well-formed UTF16 text. ** ^If the third parameter to sqlite3_bind_text64() is not NULL, then ** it should be a pointer to a well-formed unicode string that is ** either UTF8 if the sixth parameter is SQLITE_UTF8, or UTF16 ** otherwise. ** ** [[byte-order determination rules]] ^The byte-order of ** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF) ** found in first character, which is removed, or in the absence of a BOM ** the byte order is the native byte order of the host ** machine for sqlite3_bind_text16() or the byte order specified in ** the 6th parameter for sqlite3_bind_text64().)^ ** ^If UTF16 input text contains invalid unicode ** characters, then SQLite might change those invalid characters ** into the unicode replacement character: U+FFFD. ** ** ^(In those routines that have a fourth argument, its value is the ** number of bytes in the parameter. To be clear: the value is the ** number of <u>bytes</u> in the value, not the number of characters.)^ ** ^If the fourth parameter to sqlite3_bind_text() or sqlite3_bind_text16() ** is negative, then the length of the string is ** the number of bytes up to the first zero terminator. ** If the fourth parameter to sqlite3_bind_blob() is negative, then ** the behavior is undefined. ** If a non-negative fourth parameter is provided to sqlite3_bind_text() ** or sqlite3_bind_text16() or sqlite3_bind_text64() then ** that parameter must be the byte offset ** where the NUL terminator would occur assuming the string were NUL ** terminated. If any NUL characters occurs at byte offsets less than ** the value of the fourth parameter then the resulting string value will ** contain embedded NULs. The result of expressions involving strings ** with embedded NULs is undefined. ** ** ^The fifth argument to the BLOB and string binding interfaces ** is a destructor used to dispose of the BLOB or ** string after SQLite has finished with it. ^The destructor is called |
| ︙ | ︙ | |||
4316 4317 4318 4319 4320 4321 4322 | ** ^The first argument to these interfaces is a [prepared statement]. ** ^These functions return information about the Nth result column returned by ** the statement, where N is the second function argument. ** ^The left-most column is column 0 for these routines. ** ** ^If the Nth column returned by the statement is an expression or ** subquery and is not a column value, then all of these functions return | | < < < < | 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 | ** ^The first argument to these interfaces is a [prepared statement]. ** ^These functions return information about the Nth result column returned by ** the statement, where N is the second function argument. ** ^The left-most column is column 0 for these routines. ** ** ^If the Nth column returned by the statement is an expression or ** subquery and is not a column value, then all of these functions return ** NULL. ^These routines might also return NULL if a memory allocation error ** occurs. ^Otherwise, they return the name of the attached database, table, ** or column that query result column was extracted from. ** ** ^As with all other SQLite APIs, those whose names end with "16" return ** UTF-16 encoded strings and the other functions return UTF-8. ** ** ^These APIs are only available if the library was compiled with the ** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol. ** ** If two or more threads call one or more ** [sqlite3_column_database_name | column metadata interfaces] ** for the same [prepared statement] and result column ** at the same time then the results are undefined. */ SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt*,int); SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt*,int); |
| ︙ | ︙ | |||
4466 4467 4468 4469 4470 4471 4472 | /* ** CAPI3REF: Number of columns in a result set ** METHOD: sqlite3_stmt ** ** ^The sqlite3_data_count(P) interface returns the number of columns in the ** current row of the result set of [prepared statement] P. ** ^If prepared statement P does not have results ready to return | | | 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 | /* ** CAPI3REF: Number of columns in a result set ** METHOD: sqlite3_stmt ** ** ^The sqlite3_data_count(P) interface returns the number of columns in the ** current row of the result set of [prepared statement] P. ** ^If prepared statement P does not have results ready to return ** (via calls to the [sqlite3_column_int | sqlite3_column()] family of ** interfaces) then sqlite3_data_count(P) returns 0. ** ^The sqlite3_data_count(P) routine also returns 0 if P is a NULL pointer. ** ^The sqlite3_data_count(P) routine returns 0 if the previous call to ** [sqlite3_step](P) returned [SQLITE_DONE]. ^The sqlite3_data_count(P) ** will return non-zero if previous call to [sqlite3_step](P) returned ** [SQLITE_ROW], except in the case of the [PRAGMA incremental_vacuum] ** where it always returns zero since each step of that multi-step |
| ︙ | ︙ | |||
4790 4791 4792 4793 4794 4795 4796 |
** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S.
*/
SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
/*
** CAPI3REF: Create Or Redefine SQL Functions
** KEYWORDS: {function creation routines}
| < < | 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 |
** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S.
*/
SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
/*
** CAPI3REF: Create Or Redefine SQL Functions
** KEYWORDS: {function creation routines}
** METHOD: sqlite3
**
** ^These functions (collectively known as "function creation routines")
** are used to add SQL functions or aggregates or to redefine the behavior
** of existing SQL functions or aggregates. The only differences between
** the three "sqlite3_create_function*" routines are the text encoding
** expected for the second parameter (the name of the function being
|
| ︙ | ︙ | |||
4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 | ** ^The fourth parameter may optionally be ORed with [SQLITE_DETERMINISTIC] ** to signal that the function will always return the same result given ** the same inputs within a single SQL statement. Most SQL functions are ** deterministic. The built-in [random()] SQL function is an example of a ** function that is not deterministic. The SQLite query planner is able to ** perform additional optimizations on deterministic functions, so use ** of the [SQLITE_DETERMINISTIC] flag is recommended where possible. ** ** ^(The fifth parameter is an arbitrary pointer. The implementation of the ** function can gain access to this pointer using [sqlite3_user_data()].)^ ** ** ^The sixth, seventh and eighth parameters passed to the three ** "sqlite3_create_function*" functions, xFunc, xStep and xFinal, are ** pointers to C-language functions that implement the SQL function or | > > > > > > > > > > > > > > > > > | 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 | ** ^The fourth parameter may optionally be ORed with [SQLITE_DETERMINISTIC] ** to signal that the function will always return the same result given ** the same inputs within a single SQL statement. Most SQL functions are ** deterministic. The built-in [random()] SQL function is an example of a ** function that is not deterministic. The SQLite query planner is able to ** perform additional optimizations on deterministic functions, so use ** of the [SQLITE_DETERMINISTIC] flag is recommended where possible. ** ** ^The fourth parameter may also optionally include the [SQLITE_DIRECTONLY] ** flag, which if present prevents the function from being invoked from ** within VIEWs, TRIGGERs, CHECK constraints, generated column expressions, ** index expressions, or the WHERE clause of partial indexes. ** ** <span style="background-color:#ffff90;"> ** For best security, the [SQLITE_DIRECTONLY] flag is recommended for ** all application-defined SQL functions that do not need to be ** used inside of triggers, view, CHECK constraints, or other elements of ** the database schema. This flags is especially recommended for SQL ** functions that have side effects or reveal internal application state. ** Without this flag, an attacker might be able to modify the schema of ** a database file to include invocations of the function with parameters ** chosen by the attacker, which the application will then execute when ** the database file is opened and read. ** </span> ** ** ^(The fifth parameter is an arbitrary pointer. The implementation of the ** function can gain access to this pointer using [sqlite3_user_data()].)^ ** ** ^The sixth, seventh and eighth parameters passed to the three ** "sqlite3_create_function*" functions, xFunc, xStep and xFinal, are ** pointers to C-language functions that implement the SQL function or |
| ︙ | ︙ | |||
4961 4962 4963 4964 4965 4966 4967 4968 | /* ** CAPI3REF: Function Flags ** ** These constants may be ORed together with the ** [SQLITE_UTF8 | preferred text encoding] as the fourth argument ** to [sqlite3_create_function()], [sqlite3_create_function16()], or ** [sqlite3_create_function_v2()]. */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > | 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 | /* ** CAPI3REF: Function Flags ** ** These constants may be ORed together with the ** [SQLITE_UTF8 | preferred text encoding] as the fourth argument ** to [sqlite3_create_function()], [sqlite3_create_function16()], or ** [sqlite3_create_function_v2()]. ** ** <dl> ** [[SQLITE_DETERMINISTIC]] <dt>SQLITE_DETERMINISTIC</dt><dd> ** The SQLITE_DETERMINISTIC flag means that the new function always gives ** the same output when the input parameters are the same. ** The [abs|abs() function] is deterministic, for example, but ** [randomblob|randomblob()] is not. Functions must ** be deterministic in order to be used in certain contexts such as ** with the WHERE clause of [partial indexes] or in [generated columns]. ** SQLite might also optimize deterministic functions by factoring them ** out of inner loops. ** </dd> ** ** [[SQLITE_DIRECTONLY]] <dt>SQLITE_DIRECTONLY</dt><dd> ** The SQLITE_DIRECTONLY flag means that the function may only be invoked ** from top-level SQL, and cannot be used in VIEWs or TRIGGERs nor in ** schema structures such as [CHECK constraints], [DEFAULT clauses], ** [expression indexes], [partial indexes], or [generated columns]. ** The SQLITE_DIRECTONLY flags is a security feature which is recommended ** for all [application-defined SQL functions], and especially for functions ** that have side-effects or that could potentially leak sensitive ** information. ** </dd> ** ** [[SQLITE_INNOCUOUS]] <dt>SQLITE_INNOCUOUS</dt><dd> ** The SQLITE_INNOCUOUS flag means that the function is unlikely ** to cause problems even if misused. An innocuous function should have ** no side effects and should not depend on any values other than its ** input parameters. The [abs|abs() function] is an example of an ** innocuous function. ** The [load_extension() SQL function] is not innocuous because of its ** side effects. ** <p> SQLITE_INNOCUOUS is similar to SQLITE_DETERMINISTIC, but is not ** exactly the same. The [random|random() function] is an example of a ** function that is innocuous but not deterministic. ** <p>Some heightened security settings ** ([SQLITE_DBCONFIG_TRUSTED_SCHEMA] and [PRAGMA trusted_schema=OFF]) ** disable the use of SQL functions inside views and triggers and in ** schema structures such as [CHECK constraints], [DEFAULT clauses], ** [expression indexes], [partial indexes], and [generated columns] unless ** the function is tagged with SQLITE_INNOCUOUS. Most built-in functions ** are innocuous. Developers are advised to avoid using the ** SQLITE_INNOCUOUS flag for application-defined functions unless the ** function has been carefully audited and found to be free of potentially ** security-adverse side-effects and information-leaks. ** </dd> ** ** [[SQLITE_SUBTYPE]] <dt>SQLITE_SUBTYPE</dt><dd> ** The SQLITE_SUBTYPE flag indicates to SQLite that a function may call ** [sqlite3_value_subtype()] to inspect the sub-types of its arguments. ** Specifying this flag makes no difference for scalar or aggregate user ** functions. However, if it is not specified for a user-defined window ** function, then any sub-types belonging to arguments passed to the window ** function may be discarded before the window function is called (i.e. ** sqlite3_value_subtype() will always return 0). ** </dd> ** </dl> */ #define SQLITE_DETERMINISTIC 0x000000800 #define SQLITE_DIRECTONLY 0x000080000 #define SQLITE_SUBTYPE 0x000100000 #define SQLITE_INNOCUOUS 0x000200000 /* ** CAPI3REF: Deprecated Functions ** DEPRECATED ** ** These functions are [deprecated]. In order to maintain ** backwards compatibility with older code, these functions continue |
| ︙ | ︙ | |||
5021 5022 5023 5024 5025 5026 5027 | ** <td>→ <td>True if value originated from a [bound parameter] ** </table></blockquote> ** ** <b>Details:</b> ** ** These routines extract type, size, and content information from ** [protected sqlite3_value] objects. Protected sqlite3_value objects | | | | 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 | ** <td>→ <td>True if value originated from a [bound parameter] ** </table></blockquote> ** ** <b>Details:</b> ** ** These routines extract type, size, and content information from ** [protected sqlite3_value] objects. Protected sqlite3_value objects ** are used to pass parameter information into the functions that ** implement [application-defined SQL functions] and [virtual tables]. ** ** These routines work only with [protected sqlite3_value] objects. ** Any attempt to use these routines on an [unprotected sqlite3_value] ** is not threadsafe. ** ** ^These routines work just like the corresponding [column access functions] ** except that these routines take a single [protected sqlite3_value] object |
| ︙ | ︙ | |||
5079 5080 5081 5082 5083 5084 5085 | ** to be a NULL value. If sqlite3_value_nochange(X) is invoked anywhere other ** than within an [xUpdate] method call for an UPDATE statement, then ** the return value is arbitrary and meaningless. ** ** ^The sqlite3_value_frombind(X) interface returns non-zero if the ** value X originated from one of the [sqlite3_bind_int|sqlite3_bind()] ** interfaces. ^If X comes from an SQL literal value, or a table column, | | | 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 | ** to be a NULL value. If sqlite3_value_nochange(X) is invoked anywhere other ** than within an [xUpdate] method call for an UPDATE statement, then ** the return value is arbitrary and meaningless. ** ** ^The sqlite3_value_frombind(X) interface returns non-zero if the ** value X originated from one of the [sqlite3_bind_int|sqlite3_bind()] ** interfaces. ^If X comes from an SQL literal value, or a table column, ** or an expression, then sqlite3_value_frombind(X) returns zero. ** ** Please pay particular attention to the fact that the pointer returned ** from [sqlite3_value_blob()], [sqlite3_value_text()], or ** [sqlite3_value_text16()] can be invalidated by a subsequent call to ** [sqlite3_value_bytes()], [sqlite3_value_bytes16()], [sqlite3_value_text()], ** or [sqlite3_value_text16()]. ** |
| ︙ | ︙ | |||
5165 5166 5167 5168 5169 5170 5171 | ** CAPI3REF: Obtain Aggregate Function Context ** METHOD: sqlite3_context ** ** Implementations of aggregate SQL functions use this ** routine to allocate memory for storing their state. ** ** ^The first time the sqlite3_aggregate_context(C,N) routine is called | | | | | 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 | ** CAPI3REF: Obtain Aggregate Function Context ** METHOD: sqlite3_context ** ** Implementations of aggregate SQL functions use this ** routine to allocate memory for storing their state. ** ** ^The first time the sqlite3_aggregate_context(C,N) routine is called ** for a particular aggregate function, SQLite allocates ** N bytes of memory, zeroes out that memory, and returns a pointer ** to the new memory. ^On second and subsequent calls to ** sqlite3_aggregate_context() for the same aggregate function instance, ** the same buffer is returned. Sqlite3_aggregate_context() is normally ** called once for each invocation of the xStep callback and then one ** last time when the xFinal callback is invoked. ^(When no rows match ** an aggregate query, the xStep() callback of the aggregate function ** implementation is never called and xFinal() is called exactly once. ** In those cases, sqlite3_aggregate_context() might be called for the ** first time from within xFinal().)^ ** ** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer ** when first called if N is less than or equal to zero or if a memory ** allocate error occurs. ** ** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is ** determined by the N parameter on first successful call. Changing the ** value of N in any subsequent call to sqlite3_aggregate_context() within ** the same aggregate function instance will not resize the memory ** allocation.)^ Within the xFinal callback, it is customary to set ** N=0 in calls to sqlite3_aggregate_context(C,N) so that no ** pointless memory allocations occur. ** ** ^SQLite automatically frees the memory allocated by ** sqlite3_aggregate_context() when the aggregate query concludes. |
| ︙ | ︙ | |||
5340 5341 5342 5343 5344 5345 5346 | ** ** ^The sqlite3_result_error() and sqlite3_result_error16() functions ** cause the implemented SQL function to throw an exception. ** ^SQLite uses the string pointed to by the ** 2nd parameter of sqlite3_result_error() or sqlite3_result_error16() ** as the text of an error message. ^SQLite interprets the error ** message string from sqlite3_result_error() as UTF-8. ^SQLite | | > | | 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 | ** ** ^The sqlite3_result_error() and sqlite3_result_error16() functions ** cause the implemented SQL function to throw an exception. ** ^SQLite uses the string pointed to by the ** 2nd parameter of sqlite3_result_error() or sqlite3_result_error16() ** as the text of an error message. ^SQLite interprets the error ** message string from sqlite3_result_error() as UTF-8. ^SQLite ** interprets the string from sqlite3_result_error16() as UTF-16 using ** the same [byte-order determination rules] as [sqlite3_bind_text16()]. ** ^If the third parameter to sqlite3_result_error() ** or sqlite3_result_error16() is negative then SQLite takes as the error ** message all text up through the first zero character. ** ^If the third parameter to sqlite3_result_error() or ** sqlite3_result_error16() is non-negative then SQLite takes that many ** bytes (not characters) from the 2nd parameter as the error message. ** ^The sqlite3_result_error() and sqlite3_result_error16() ** routines make a private copy of the error message text before |
| ︙ | ︙ | |||
5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 | ** assumes that the text or BLOB result is in constant space and does not ** copy the content of the parameter nor call a destructor on the content ** when it has finished using that result. ** ^If the 4th parameter to the sqlite3_result_text* interfaces ** or sqlite3_result_blob is the special constant SQLITE_TRANSIENT ** then SQLite makes a copy of the result into space obtained ** from [sqlite3_malloc()] before it returns. ** ** ^The sqlite3_result_value() interface sets the result of ** the application-defined function to be a copy of the ** [unprotected sqlite3_value] object specified by the 2nd parameter. ^The ** sqlite3_result_value() interface makes a copy of the [sqlite3_value] ** so that the [sqlite3_value] specified in the parameter may change or ** be deallocated after sqlite3_result_value() returns without harm. | > > > > > > > > > > > > > > > > > > > | 5719 5720 5721 5722 5723 5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 5734 5735 5736 5737 5738 5739 5740 5741 5742 5743 5744 5745 5746 5747 5748 5749 5750 5751 | ** assumes that the text or BLOB result is in constant space and does not ** copy the content of the parameter nor call a destructor on the content ** when it has finished using that result. ** ^If the 4th parameter to the sqlite3_result_text* interfaces ** or sqlite3_result_blob is the special constant SQLITE_TRANSIENT ** then SQLite makes a copy of the result into space obtained ** from [sqlite3_malloc()] before it returns. ** ** ^For the sqlite3_result_text16(), sqlite3_result_text16le(), and ** sqlite3_result_text16be() routines, and for sqlite3_result_text64() ** when the encoding is not UTF8, if the input UTF16 begins with a ** byte-order mark (BOM, U+FEFF) then the BOM is removed from the ** string and the rest of the string is interpreted according to the ** byte-order specified by the BOM. ^The byte-order specified by ** the BOM at the beginning of the text overrides the byte-order ** specified by the interface procedure. ^So, for example, if ** sqlite3_result_text16le() is invoked with text that begins ** with bytes 0xfe, 0xff (a big-endian byte-order mark) then the ** first two bytes of input are skipped and the remaining input ** is interpreted as UTF16BE text. ** ** ^For UTF16 input text to the sqlite3_result_text16(), ** sqlite3_result_text16be(), sqlite3_result_text16le(), and ** sqlite3_result_text64() routines, if the text contains invalid ** UTF16 characters, the invalid characters might be converted ** into the unicode replacement character, U+FFFD. ** ** ^The sqlite3_result_value() interface sets the result of ** the application-defined function to be a copy of the ** [unprotected sqlite3_value] object specified by the 2nd parameter. ^The ** sqlite3_result_value() interface makes a copy of the [sqlite3_value] ** so that the [sqlite3_value] specified in the parameter may change or ** be deallocated after sqlite3_result_value() returns without harm. |
| ︙ | ︙ | |||
5494 5495 5496 5497 5498 5499 5500 | ** <li> [SQLITE_UTF8], ** <li> [SQLITE_UTF16LE], ** <li> [SQLITE_UTF16BE], ** <li> [SQLITE_UTF16], or ** <li> [SQLITE_UTF16_ALIGNED]. ** </ul>)^ ** ^The eTextRep argument determines the encoding of strings passed | | | | | > | | | 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 5844 5845 5846 5847 5848 5849 5850 5851 5852 5853 5854 5855 5856 5857 5858 5859 5860 5861 5862 5863 5864 5865 5866 5867 5868 5869 5870 5871 5872 5873 5874 5875 5876 | ** <li> [SQLITE_UTF8], ** <li> [SQLITE_UTF16LE], ** <li> [SQLITE_UTF16BE], ** <li> [SQLITE_UTF16], or ** <li> [SQLITE_UTF16_ALIGNED]. ** </ul>)^ ** ^The eTextRep argument determines the encoding of strings passed ** to the collating function callback, xCompare. ** ^The [SQLITE_UTF16] and [SQLITE_UTF16_ALIGNED] values for eTextRep ** force strings to be UTF16 with native byte order. ** ^The [SQLITE_UTF16_ALIGNED] value for eTextRep forces strings to begin ** on an even byte address. ** ** ^The fourth argument, pArg, is an application data pointer that is passed ** through as the first argument to the collating function callback. ** ** ^The fifth argument, xCompare, is a pointer to the collating function. ** ^Multiple collating functions can be registered using the same name but ** with different eTextRep parameters and SQLite will use whichever ** function requires the least amount of data transformation. ** ^If the xCompare argument is NULL then the collating function is ** deleted. ^When all collating functions having the same name are deleted, ** that collation is no longer usable. ** ** ^The collating function callback is invoked with a copy of the pArg ** application data pointer and with two strings in the encoding specified ** by the eTextRep argument. The two integer parameters to the collating ** function callback are the length of the two strings, in bytes. The collating ** function must return an integer that is negative, zero, or positive ** if the first string is less than, equal to, or greater than the second, ** respectively. A collating function must always return the same answer ** given the same inputs. If two or more collating functions are registered ** to the same collation name (using different eTextRep values) then all ** must give an equivalent answer when invoked with equivalent strings. ** The collating function must obey the following properties for all ** strings A, B, and C: ** ** <ol> ** <li> If A==B then B==A. ** <li> If A==B and B==C then A==C. ** <li> If A<B THEN B>A. ** <li> If A<B and B<C then A<C. ** </ol> ** ** If a collating function fails any of the above constraints and that ** collating function is registered and used, then the behavior of SQLite ** is undefined. ** ** ^The sqlite3_create_collation_v2() works like sqlite3_create_collation() ** with the addition that the xDestroy callback is invoked on pArg when ** the collating function is deleted. ** ^Collating functions are deleted when they are overridden by later ** calls to the collation creation functions or when the |
| ︙ | ︙ | |||
5613 5614 5615 5616 5617 5618 5619 | ); SQLITE_API int sqlite3_collation_needed16( sqlite3*, void*, void(*)(void*,sqlite3*,int eTextRep,const void*) ); | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 | ); SQLITE_API int sqlite3_collation_needed16( sqlite3*, void*, void(*)(void*,sqlite3*,int eTextRep,const void*) ); #ifdef SQLITE_ENABLE_CEROD /* ** Specify the activation key for a CEROD database. Unless ** activated, none of the CEROD routines will work. */ SQLITE_API void sqlite3_activate_cerod( const char *zPassPhrase /* Activation phrase */ |
| ︙ | ︙ | |||
5858 5859 5860 5861 5862 5863 5864 | */ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); /* ** CAPI3REF: Return The Filename For A Database Connection ** METHOD: sqlite3 ** | | | | > > > > > > > > > > > > > > > | 6144 6145 6146 6147 6148 6149 6150 6151 6152 6153 6154 6155 6156 6157 6158 6159 6160 6161 6162 6163 6164 6165 6166 6167 6168 6169 6170 6171 6172 6173 6174 6175 6176 6177 6178 6179 6180 6181 6182 | */ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); /* ** CAPI3REF: Return The Filename For A Database Connection ** METHOD: sqlite3 ** ** ^The sqlite3_db_filename(D,N) interface returns a pointer to the filename ** associated with database N of connection D. ** ^If there is no attached database N on the database ** connection D, or if database N is a temporary or in-memory database, then ** this function will return either a NULL pointer or an empty string. ** ** ^The string value returned by this routine is owned and managed by ** the database connection. ^The value will be valid until the database N ** is [DETACH]-ed or until the database connection closes. ** ** ^The filename returned by this function is the output of the ** xFullPathname method of the [VFS]. ^In other words, the filename ** will be an absolute pathname, even if the filename used ** to open the database originally was a URI or relative pathname. ** ** If the filename pointer returned by this routine is not NULL, then it ** can be used as the filename input parameter to these routines: ** <ul> ** <li> [sqlite3_uri_parameter()] ** <li> [sqlite3_uri_boolean()] ** <li> [sqlite3_uri_int64()] ** <li> [sqlite3_filename_database()] ** <li> [sqlite3_filename_journal()] ** <li> [sqlite3_filename_wal()] ** </ul> */ SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName); /* ** CAPI3REF: Determine if a database is read-only ** METHOD: sqlite3 ** |
| ︙ | ︙ | |||
5971 5972 5973 5974 5975 5976 5977 | ** to be invoked. ** ^The third and fourth arguments to the callback contain pointers to the ** database and table name containing the affected row. ** ^The final callback parameter is the [rowid] of the row. ** ^In the case of an update, this is the [rowid] after the update takes place. ** ** ^(The update hook is not invoked when internal system tables are | | | 6272 6273 6274 6275 6276 6277 6278 6279 6280 6281 6282 6283 6284 6285 6286 | ** to be invoked. ** ^The third and fourth arguments to the callback contain pointers to the ** database and table name containing the affected row. ** ^The final callback parameter is the [rowid] of the row. ** ^In the case of an update, this is the [rowid] after the update takes place. ** ** ^(The update hook is not invoked when internal system tables are ** modified (i.e. sqlite_sequence).)^ ** ^The update hook is not invoked when [WITHOUT ROWID] tables are modified. ** ** ^In the current implementation, the update hook ** is not invoked when conflicting rows are deleted because of an ** [ON CONFLICT | ON CONFLICT REPLACE] clause. ^Nor is the update hook ** invoked when rows are deleted using the [truncate optimization]. ** The exceptions defined in this paragraph might change in a future |
| ︙ | ︙ | |||
6017 6018 6019 6020 6021 6022 6023 | ** ^Cache sharing is enabled and disabled for an entire process. ** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). ** In prior versions of SQLite, ** sharing was enabled or disabled for each thread separately. ** ** ^(The cache sharing mode set by this interface effects all subsequent ** calls to [sqlite3_open()], [sqlite3_open_v2()], and [sqlite3_open16()]. | | | | | > > > > | 6318 6319 6320 6321 6322 6323 6324 6325 6326 6327 6328 6329 6330 6331 6332 6333 6334 6335 6336 6337 6338 6339 6340 6341 6342 6343 6344 | ** ^Cache sharing is enabled and disabled for an entire process. ** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). ** In prior versions of SQLite, ** sharing was enabled or disabled for each thread separately. ** ** ^(The cache sharing mode set by this interface effects all subsequent ** calls to [sqlite3_open()], [sqlite3_open_v2()], and [sqlite3_open16()]. ** Existing database connections continue to use the sharing mode ** that was in effect at the time they were opened.)^ ** ** ^(This routine returns [SQLITE_OK] if shared cache was enabled or disabled ** successfully. An [error code] is returned otherwise.)^ ** ** ^Shared cache is disabled by default. It is recommended that it stay ** that way. In other words, do not use this routine. This interface ** continues to be provided for historical compatibility, but its use is ** discouraged. Any use of shared cache is discouraged. If shared cache ** must be used, it is recommended that shared cache only be enabled for ** individual database connections using the [sqlite3_open_v2()] interface ** with the [SQLITE_OPEN_SHAREDCACHE] flag. ** ** Note: This method is disabled on MacOS X 10.7 and iOS version 5.0 ** and will always return SQLITE_MISUSE. On those systems, ** shared cache mode should be enabled per-database connection via ** [sqlite3_open_v2()] with [SQLITE_OPEN_SHAREDCACHE]. ** ** This interface is threadsafe on processors where writing a |
| ︙ | ︙ | |||
6071 6072 6073 6074 6075 6076 6077 6078 6079 6080 6081 6082 6083 6084 6085 6086 6087 6088 | ** ** See also: [sqlite3_release_memory()] */ SQLITE_API int sqlite3_db_release_memory(sqlite3*); /* ** CAPI3REF: Impose A Limit On Heap Size ** ** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the ** soft limit on the amount of heap memory that may be allocated by SQLite. ** ^SQLite strives to keep heap memory utilization below the soft heap ** limit by reducing the number of pages held in the page cache ** as heap memory usages approaches the limit. ** ^The soft heap limit is "soft" because even though SQLite strives to stay ** below the limit, it will exceed the limit rather than generate ** an [SQLITE_NOMEM] error. In other words, the soft heap limit ** is advisory only. ** | > > > > > > > > > > | | | | | > > > > > | > > > > > > > > > | | < < < < < < < < < < < | > | 6376 6377 6378 6379 6380 6381 6382 6383 6384 6385 6386 6387 6388 6389 6390 6391 6392 6393 6394 6395 6396 6397 6398 6399 6400 6401 6402 6403 6404 6405 6406 6407 6408 6409 6410 6411 6412 6413 6414 6415 6416 6417 6418 6419 6420 6421 6422 6423 6424 6425 6426 6427 6428 6429 6430 6431 6432 6433 6434 6435 6436 6437 6438 6439 6440 6441 6442 6443 6444 6445 6446 6447 6448 6449 6450 6451 6452 6453 | ** ** See also: [sqlite3_release_memory()] */ SQLITE_API int sqlite3_db_release_memory(sqlite3*); /* ** CAPI3REF: Impose A Limit On Heap Size ** ** These interfaces impose limits on the amount of heap memory that will be ** by all database connections within a single process. ** ** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the ** soft limit on the amount of heap memory that may be allocated by SQLite. ** ^SQLite strives to keep heap memory utilization below the soft heap ** limit by reducing the number of pages held in the page cache ** as heap memory usages approaches the limit. ** ^The soft heap limit is "soft" because even though SQLite strives to stay ** below the limit, it will exceed the limit rather than generate ** an [SQLITE_NOMEM] error. In other words, the soft heap limit ** is advisory only. ** ** ^The sqlite3_hard_heap_limit64(N) interface sets a hard upper bound of ** N bytes on the amount of memory that will be allocated. ^The ** sqlite3_hard_heap_limit64(N) interface is similar to ** sqlite3_soft_heap_limit64(N) except that memory allocations will fail ** when the hard heap limit is reached. ** ** ^The return value from both sqlite3_soft_heap_limit64() and ** sqlite3_hard_heap_limit64() is the size of ** the heap limit prior to the call, or negative in the case of an ** error. ^If the argument N is negative ** then no change is made to the heap limit. Hence, the current ** size of heap limits can be determined by invoking ** sqlite3_soft_heap_limit64(-1) or sqlite3_hard_heap_limit(-1). ** ** ^Setting the heap limits to zero disables the heap limiter mechanism. ** ** ^The soft heap limit may not be greater than the hard heap limit. ** ^If the hard heap limit is enabled and if sqlite3_soft_heap_limit(N) ** is invoked with a value of N that is greater than the hard heap limit, ** the the soft heap limit is set to the value of the hard heap limit. ** ^The soft heap limit is automatically enabled whenever the hard heap ** limit is enabled. ^When sqlite3_hard_heap_limit64(N) is invoked and ** the soft heap limit is outside the range of 1..N, then the soft heap ** limit is set to N. ^Invoking sqlite3_soft_heap_limit64(0) when the ** hard heap limit is enabled makes the soft heap limit equal to the ** hard heap limit. ** ** The memory allocation limits can also be adjusted using ** [PRAGMA soft_heap_limit] and [PRAGMA hard_heap_limit]. ** ** ^(The heap limits are not enforced in the current implementation ** if one or more of following conditions are true: ** ** <ul> ** <li> The limit value is set to zero. ** <li> Memory accounting is disabled using a combination of the ** [sqlite3_config]([SQLITE_CONFIG_MEMSTATUS],...) start-time option and ** the [SQLITE_DEFAULT_MEMSTATUS] compile-time option. ** <li> An alternative page cache implementation is specified using ** [sqlite3_config]([SQLITE_CONFIG_PCACHE2],...). ** <li> The page cache allocates from its own memory pool supplied ** by [sqlite3_config]([SQLITE_CONFIG_PAGECACHE],...) rather than ** from the heap. ** </ul>)^ ** ** The circumstances under which SQLite will enforce the heap limits may ** changes in future releases of SQLite. */ SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N); SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N); /* ** CAPI3REF: Deprecated Soft Heap Limit Interface ** DEPRECATED ** ** This is a deprecated version of the [sqlite3_soft_heap_limit64()] ** interface. This routine is provided for historical compatibility |
| ︙ | ︙ | |||
6144 6145 6146 6147 6148 6149 6150 | ** ** ^(The sqlite3_table_column_metadata(X,D,T,C,....) routine returns ** information about column C of table T in database D ** on [database connection] X.)^ ^The sqlite3_table_column_metadata() ** interface returns SQLITE_OK and fills in the non-NULL pointers in ** the final five arguments with appropriate values if the specified ** column exists. ^The sqlite3_table_column_metadata() interface returns | | | 6463 6464 6465 6466 6467 6468 6469 6470 6471 6472 6473 6474 6475 6476 6477 | ** ** ^(The sqlite3_table_column_metadata(X,D,T,C,....) routine returns ** information about column C of table T in database D ** on [database connection] X.)^ ^The sqlite3_table_column_metadata() ** interface returns SQLITE_OK and fills in the non-NULL pointers in ** the final five arguments with appropriate values if the specified ** column exists. ^The sqlite3_table_column_metadata() interface returns ** SQLITE_ERROR if the specified column does not exist. ** ^If the column-name parameter to sqlite3_table_column_metadata() is a ** NULL pointer, then this routine simply checks for the existence of the ** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it ** does not. If the table name parameter T in a call to ** sqlite3_table_column_metadata(X,D,T,C,...) is NULL then the result is ** undefined behavior. ** |
| ︙ | ︙ | |||
6286 6287 6288 6289 6290 6291 6292 | ** ** ^This interface enables or disables both the C-API ** [sqlite3_load_extension()] and the SQL function [load_extension()]. ** ^(Use [sqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],..) ** to enable or disable only the C-API.)^ ** ** <b>Security warning:</b> It is recommended that extension loading | | | 6605 6606 6607 6608 6609 6610 6611 6612 6613 6614 6615 6616 6617 6618 6619 | ** ** ^This interface enables or disables both the C-API ** [sqlite3_load_extension()] and the SQL function [load_extension()]. ** ^(Use [sqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],..) ** to enable or disable only the C-API.)^ ** ** <b>Security warning:</b> It is recommended that extension loading ** be enabled using the [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method ** rather than this interface, so the [load_extension()] SQL function ** remains disabled. This will prevent SQL injections from giving attackers ** access to extension loading capabilities. */ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff); /* |
| ︙ | ︙ | |||
6373 6374 6375 6376 6377 6378 6379 |
typedef struct sqlite3_module sqlite3_module;
/*
** CAPI3REF: Virtual Table Object
** KEYWORDS: sqlite3_module {virtual table module}
**
** This structure, sometimes called a "virtual table module",
| | | 6692 6693 6694 6695 6696 6697 6698 6699 6700 6701 6702 6703 6704 6705 6706 |
typedef struct sqlite3_module sqlite3_module;
/*
** CAPI3REF: Virtual Table Object
** KEYWORDS: sqlite3_module {virtual table module}
**
** This structure, sometimes called a "virtual table module",
** defines the implementation of a [virtual table].
** This structure consists mostly of methods for the module.
**
** ^A virtual table module is created by filling in a persistent
** instance of this structure and passing a pointer to that instance
** to [sqlite3_create_module()] or [sqlite3_create_module_v2()].
** ^The registration remains valid until it is replaced by a different
** module or until the [database connection] closes. The content
|
| ︙ | ︙ | |||
6470 6471 6472 6473 6474 6475 6476 | ** non-zero. ** ** The [xBestIndex] method must fill aConstraintUsage[] with information ** about what parameters to pass to xFilter. ^If argvIndex>0 then ** the right-hand side of the corresponding aConstraint[] is evaluated ** and becomes the argvIndex-th entry in argv. ^(If aConstraintUsage[].omit ** is true, then the constraint is assumed to be fully handled by the | | > > > > > > | 6789 6790 6791 6792 6793 6794 6795 6796 6797 6798 6799 6800 6801 6802 6803 6804 6805 6806 6807 6808 6809 | ** non-zero. ** ** The [xBestIndex] method must fill aConstraintUsage[] with information ** about what parameters to pass to xFilter. ^If argvIndex>0 then ** the right-hand side of the corresponding aConstraint[] is evaluated ** and becomes the argvIndex-th entry in argv. ^(If aConstraintUsage[].omit ** is true, then the constraint is assumed to be fully handled by the ** virtual table and might not be checked again by the byte code.)^ ^(The ** aConstraintUsage[].omit flag is an optimization hint. When the omit flag ** is left in its default setting of false, the constraint will always be ** checked separately in byte code. If the omit flag is change to true, then ** the constraint may or may not be checked in byte code. In other words, ** when the omit flag is true there is no guarantee that the constraint will ** not be checked again using byte code.)^ ** ** ^The idxNum and idxPtr values are recorded and passed into the ** [xFilter] method. ** ^[sqlite3_free()] is used to free idxPtr if and only if ** needToFreeIdxPtr is true. ** ** ^The orderByConsumed means that output from [xFilter]/[xNext] will occur in |
| ︙ | ︙ | |||
6510 6511 6512 6513 6514 6515 6516 | ** the xUpdate method are automatically rolled back by SQLite. ** ** IMPORTANT: The estimatedRows field was added to the sqlite3_index_info ** structure for SQLite [version 3.8.2] ([dateof:3.8.2]). ** If a virtual table extension is ** used with an SQLite version earlier than 3.8.2, the results of attempting ** to read or write the estimatedRows field are undefined (but are likely | | | 6835 6836 6837 6838 6839 6840 6841 6842 6843 6844 6845 6846 6847 6848 6849 | ** the xUpdate method are automatically rolled back by SQLite. ** ** IMPORTANT: The estimatedRows field was added to the sqlite3_index_info ** structure for SQLite [version 3.8.2] ([dateof:3.8.2]). ** If a virtual table extension is ** used with an SQLite version earlier than 3.8.2, the results of attempting ** to read or write the estimatedRows field are undefined (but are likely ** to include crashing the application). The estimatedRows field should ** therefore only be used if [sqlite3_libversion_number()] returns a ** value greater than or equal to 3008002. Similarly, the idxFlags field ** was added for [version 3.9.0] ([dateof:3.9.0]). ** It may therefore only be used if ** sqlite3_libversion_number() returns a value greater than or equal to ** 3009000. */ |
| ︙ | ︙ | |||
6562 6563 6564 6565 6566 6567 6568 | ** these bits. */ #define SQLITE_INDEX_SCAN_UNIQUE 1 /* Scan visits at most 1 row */ /* ** CAPI3REF: Virtual Table Constraint Operator Codes ** | | | 6887 6888 6889 6890 6891 6892 6893 6894 6895 6896 6897 6898 6899 6900 6901 | ** these bits. */ #define SQLITE_INDEX_SCAN_UNIQUE 1 /* Scan visits at most 1 row */ /* ** CAPI3REF: Virtual Table Constraint Operator Codes ** ** These macros define the allowed values for the ** [sqlite3_index_info].aConstraint[].op field. Each value represents ** an operator that is part of a constraint term in the wHERE clause of ** a query that uses a [virtual table]. */ #define SQLITE_INDEX_CONSTRAINT_EQ 2 #define SQLITE_INDEX_CONSTRAINT_GT 4 #define SQLITE_INDEX_CONSTRAINT_LE 8 |
| ︙ | ︙ | |||
6608 6609 6610 6611 6612 6613 6614 6615 6616 6617 6618 6619 6620 6621 6622 6623 6624 6625 6626 6627 6628 6629 6630 6631 6632 6633 6634 6635 | ** is a pointer to a destructor for the pClientData. ^SQLite will ** invoke the destructor function (if it is not NULL) when SQLite ** no longer needs the pClientData pointer. ^The destructor will also ** be invoked if the call to sqlite3_create_module_v2() fails. ** ^The sqlite3_create_module() ** interface is equivalent to sqlite3_create_module_v2() with a NULL ** destructor. */ SQLITE_API int sqlite3_create_module( sqlite3 *db, /* SQLite connection to register module with */ const char *zName, /* Name of the module */ const sqlite3_module *p, /* Methods for the module */ void *pClientData /* Client data for xCreate/xConnect */ ); SQLITE_API int sqlite3_create_module_v2( sqlite3 *db, /* SQLite connection to register module with */ const char *zName, /* Name of the module */ const sqlite3_module *p, /* Methods for the module */ void *pClientData, /* Client data for xCreate/xConnect */ void(*xDestroy)(void*) /* Module destructor function */ ); /* ** CAPI3REF: Virtual Table Instance Object ** KEYWORDS: sqlite3_vtab ** ** Every [virtual table module] implementation uses a subclass ** of this object to describe a particular instance | > > > > > > > > > > > > > > > > > > > > > > > | 6933 6934 6935 6936 6937 6938 6939 6940 6941 6942 6943 6944 6945 6946 6947 6948 6949 6950 6951 6952 6953 6954 6955 6956 6957 6958 6959 6960 6961 6962 6963 6964 6965 6966 6967 6968 6969 6970 6971 6972 6973 6974 6975 6976 6977 6978 6979 6980 6981 6982 6983 | ** is a pointer to a destructor for the pClientData. ^SQLite will ** invoke the destructor function (if it is not NULL) when SQLite ** no longer needs the pClientData pointer. ^The destructor will also ** be invoked if the call to sqlite3_create_module_v2() fails. ** ^The sqlite3_create_module() ** interface is equivalent to sqlite3_create_module_v2() with a NULL ** destructor. ** ** ^If the third parameter (the pointer to the sqlite3_module object) is ** NULL then no new module is create and any existing modules with the ** same name are dropped. ** ** See also: [sqlite3_drop_modules()] */ SQLITE_API int sqlite3_create_module( sqlite3 *db, /* SQLite connection to register module with */ const char *zName, /* Name of the module */ const sqlite3_module *p, /* Methods for the module */ void *pClientData /* Client data for xCreate/xConnect */ ); SQLITE_API int sqlite3_create_module_v2( sqlite3 *db, /* SQLite connection to register module with */ const char *zName, /* Name of the module */ const sqlite3_module *p, /* Methods for the module */ void *pClientData, /* Client data for xCreate/xConnect */ void(*xDestroy)(void*) /* Module destructor function */ ); /* ** CAPI3REF: Remove Unnecessary Virtual Table Implementations ** METHOD: sqlite3 ** ** ^The sqlite3_drop_modules(D,L) interface removes all virtual ** table modules from database connection D except those named on list L. ** The L parameter must be either NULL or a pointer to an array of pointers ** to strings where the array is terminated by a single NULL pointer. ** ^If the L parameter is NULL, then all virtual table modules are removed. ** ** See also: [sqlite3_create_module()] */ SQLITE_API int sqlite3_drop_modules( sqlite3 *db, /* Remove modules from this connection */ const char **azKeep /* Except, do not remove the ones named here */ ); /* ** CAPI3REF: Virtual Table Instance Object ** KEYWORDS: sqlite3_vtab ** ** Every [virtual table module] implementation uses a subclass ** of this object to describe a particular instance |
| ︙ | ︙ | |||
7026 7027 7028 7029 7030 7031 7032 | ** routine returns NULL if it is unable to allocate the requested ** mutex. The argument to sqlite3_mutex_alloc() must one of these ** integer constants: ** ** <ul> ** <li> SQLITE_MUTEX_FAST ** <li> SQLITE_MUTEX_RECURSIVE | | | 7374 7375 7376 7377 7378 7379 7380 7381 7382 7383 7384 7385 7386 7387 7388 | ** routine returns NULL if it is unable to allocate the requested ** mutex. The argument to sqlite3_mutex_alloc() must one of these ** integer constants: ** ** <ul> ** <li> SQLITE_MUTEX_FAST ** <li> SQLITE_MUTEX_RECURSIVE ** <li> SQLITE_MUTEX_STATIC_MAIN ** <li> SQLITE_MUTEX_STATIC_MEM ** <li> SQLITE_MUTEX_STATIC_OPEN ** <li> SQLITE_MUTEX_STATIC_PRNG ** <li> SQLITE_MUTEX_STATIC_LRU ** <li> SQLITE_MUTEX_STATIC_PMEM ** <li> SQLITE_MUTEX_STATIC_APP1 ** <li> SQLITE_MUTEX_STATIC_APP2 |
| ︙ | ︙ | |||
7149 7150 7151 7152 7153 7154 7155 | ** <li> [sqlite3_mutex_held()] </li> ** <li> [sqlite3_mutex_notheld()] </li> ** </ul>)^ ** ** The only difference is that the public sqlite3_XXX functions enumerated ** above silently ignore any invocations that pass a NULL pointer instead ** of a valid mutex handle. The implementations of the methods defined | | | 7497 7498 7499 7500 7501 7502 7503 7504 7505 7506 7507 7508 7509 7510 7511 | ** <li> [sqlite3_mutex_held()] </li> ** <li> [sqlite3_mutex_notheld()] </li> ** </ul>)^ ** ** The only difference is that the public sqlite3_XXX functions enumerated ** above silently ignore any invocations that pass a NULL pointer instead ** of a valid mutex handle. The implementations of the methods defined ** by this structure are not required to handle this case. The results ** of passing a NULL pointer instead of a valid mutex handle are undefined ** (i.e. it is acceptable to provide an implementation that segfaults if ** it is passed a NULL pointer). ** ** The xMutexInit() method must be threadsafe. It must be harmless to ** invoke xMutexInit() multiple times within the same process and without ** intervening calls to xMutexEnd(). Second and subsequent calls to |
| ︙ | ︙ | |||
7228 7229 7230 7231 7232 7233 7234 | ** ** The set of static mutexes may change from one SQLite release to the ** next. Applications that override the built-in mutex logic must be ** prepared to accommodate additional static mutexes. */ #define SQLITE_MUTEX_FAST 0 #define SQLITE_MUTEX_RECURSIVE 1 | | > > > > | 7576 7577 7578 7579 7580 7581 7582 7583 7584 7585 7586 7587 7588 7589 7590 7591 7592 7593 7594 7595 7596 7597 7598 7599 7600 7601 7602 7603 7604 7605 7606 7607 | ** ** The set of static mutexes may change from one SQLite release to the ** next. Applications that override the built-in mutex logic must be ** prepared to accommodate additional static mutexes. */ #define SQLITE_MUTEX_FAST 0 #define SQLITE_MUTEX_RECURSIVE 1 #define SQLITE_MUTEX_STATIC_MAIN 2 #define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */ #define SQLITE_MUTEX_STATIC_MEM2 4 /* NOT USED */ #define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */ #define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_randomness() */ #define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */ #define SQLITE_MUTEX_STATIC_LRU2 7 /* NOT USED */ #define SQLITE_MUTEX_STATIC_PMEM 7 /* sqlite3PageMalloc() */ #define SQLITE_MUTEX_STATIC_APP1 8 /* For use by application */ #define SQLITE_MUTEX_STATIC_APP2 9 /* For use by application */ #define SQLITE_MUTEX_STATIC_APP3 10 /* For use by application */ #define SQLITE_MUTEX_STATIC_VFS1 11 /* For use by built-in VFS */ #define SQLITE_MUTEX_STATIC_VFS2 12 /* For use by extension VFS */ #define SQLITE_MUTEX_STATIC_VFS3 13 /* For use by application VFS */ /* Legacy compatibility: */ #define SQLITE_MUTEX_STATIC_MASTER 2 /* ** CAPI3REF: Retrieve the mutex for a database connection ** METHOD: sqlite3 ** ** ^This interface returns a pointer the [sqlite3_mutex] object that ** serializes access to the [database connection] given in the argument |
| ︙ | ︙ | |||
7331 7332 7333 7334 7335 7336 7337 | ** without notice. These values are for testing purposes only. ** Applications should not use any of these parameters or the ** [sqlite3_test_control()] interface. */ #define SQLITE_TESTCTRL_FIRST 5 #define SQLITE_TESTCTRL_PRNG_SAVE 5 #define SQLITE_TESTCTRL_PRNG_RESTORE 6 | | | > > | | 7683 7684 7685 7686 7687 7688 7689 7690 7691 7692 7693 7694 7695 7696 7697 7698 7699 7700 7701 7702 7703 7704 7705 7706 7707 7708 7709 7710 7711 7712 7713 7714 7715 7716 7717 7718 7719 7720 7721 7722 | ** without notice. These values are for testing purposes only. ** Applications should not use any of these parameters or the ** [sqlite3_test_control()] interface. */ #define SQLITE_TESTCTRL_FIRST 5 #define SQLITE_TESTCTRL_PRNG_SAVE 5 #define SQLITE_TESTCTRL_PRNG_RESTORE 6 #define SQLITE_TESTCTRL_PRNG_RESET 7 /* NOT USED */ #define SQLITE_TESTCTRL_BITVEC_TEST 8 #define SQLITE_TESTCTRL_FAULT_INSTALL 9 #define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10 #define SQLITE_TESTCTRL_PENDING_BYTE 11 #define SQLITE_TESTCTRL_ASSERT 12 #define SQLITE_TESTCTRL_ALWAYS 13 #define SQLITE_TESTCTRL_RESERVE 14 /* NOT USED */ #define SQLITE_TESTCTRL_OPTIMIZATIONS 15 #define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */ #define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */ #define SQLITE_TESTCTRL_INTERNAL_FUNCTIONS 17 #define SQLITE_TESTCTRL_LOCALTIME_FAULT 18 #define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */ #define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19 #define SQLITE_TESTCTRL_NEVER_CORRUPT 20 #define SQLITE_TESTCTRL_VDBE_COVERAGE 21 #define SQLITE_TESTCTRL_BYTEORDER 22 #define SQLITE_TESTCTRL_ISINIT 23 #define SQLITE_TESTCTRL_SORTER_MMAP 24 #define SQLITE_TESTCTRL_IMPOSTER 25 #define SQLITE_TESTCTRL_PARSER_COVERAGE 26 #define SQLITE_TESTCTRL_RESULT_INTREAL 27 #define SQLITE_TESTCTRL_PRNG_SEED 28 #define SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS 29 #define SQLITE_TESTCTRL_LAST 29 /* Largest TESTCTRL */ /* ** CAPI3REF: SQL Keyword Checking ** ** These routines provide access to the set of SQL language keywords ** recognized by SQLite. Applications can uses these routines to determine ** whether or not a specific identifier needs to be escaped (for example, |
| ︙ | ︙ | |||
7620 7621 7622 7623 7624 7625 7626 | ** returned value includes allocations that overflowed because they ** where too large (they were larger than the "sz" parameter to ** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because ** no space was left in the page cache.</dd>)^ ** ** [[SQLITE_STATUS_PAGECACHE_SIZE]] ^(<dt>SQLITE_STATUS_PAGECACHE_SIZE</dt> ** <dd>This parameter records the largest memory allocation request | | | 7974 7975 7976 7977 7978 7979 7980 7981 7982 7983 7984 7985 7986 7987 7988 | ** returned value includes allocations that overflowed because they ** where too large (they were larger than the "sz" parameter to ** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because ** no space was left in the page cache.</dd>)^ ** ** [[SQLITE_STATUS_PAGECACHE_SIZE]] ^(<dt>SQLITE_STATUS_PAGECACHE_SIZE</dt> ** <dd>This parameter records the largest memory allocation request ** handed to the [pagecache memory allocator]. Only the value returned in the ** *pHighwater parameter to [sqlite3_status()] is of interest. ** The value written into the *pCurrent parameter is undefined.</dd>)^ ** ** [[SQLITE_STATUS_SCRATCH_USED]] <dt>SQLITE_STATUS_SCRATCH_USED</dt> ** <dd>No longer used.</dd> ** ** [[SQLITE_STATUS_SCRATCH_OVERFLOW]] ^(<dt>SQLITE_STATUS_SCRATCH_OVERFLOW</dt> |
| ︙ | ︙ | |||
7696 7697 7698 7699 7700 7701 7702 | ** ** <dl> ** [[SQLITE_DBSTATUS_LOOKASIDE_USED]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_USED</dt> ** <dd>This parameter returns the number of lookaside memory slots currently ** checked out.</dd>)^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt> | | | 8050 8051 8052 8053 8054 8055 8056 8057 8058 8059 8060 8061 8062 8063 8064 | ** ** <dl> ** [[SQLITE_DBSTATUS_LOOKASIDE_USED]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_USED</dt> ** <dd>This parameter returns the number of lookaside memory slots currently ** checked out.</dd>)^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt> ** <dd>This parameter returns the number of malloc attempts that were ** satisfied using lookaside memory. Only the high-water value is meaningful; ** the current value is always zero.)^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]] ** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE</dt> ** <dd>This parameter returns the number malloc attempts that might have ** been satisfied using lookaside memory but failed due to the amount of |
| ︙ | ︙ | |||
7778 7779 7780 7781 7782 7783 7784 | ** ** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt> ** <dd>This parameter returns the number of dirty cache entries that have ** been written to disk in the middle of a transaction due to the page ** cache overflowing. Transactions are more efficient if they are written ** to disk all at once. When pages spill mid-transaction, that introduces ** additional overhead. This parameter can be used help identify | | | 8132 8133 8134 8135 8136 8137 8138 8139 8140 8141 8142 8143 8144 8145 8146 | ** ** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt> ** <dd>This parameter returns the number of dirty cache entries that have ** been written to disk in the middle of a transaction due to the page ** cache overflowing. Transactions are more efficient if they are written ** to disk all at once. When pages spill mid-transaction, that introduces ** additional overhead. This parameter can be used help identify ** inefficiencies that can be resolved by increasing the cache size. ** </dd> ** ** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(<dt>SQLITE_DBSTATUS_DEFERRED_FKS</dt> ** <dd>This parameter returns zero for the current value if and only if ** all foreign key constraints (deferred or immediate) have been ** resolved.)^ ^The highwater mark is always 0. ** </dd> |
| ︙ | ︙ | |||
7867 7868 7869 7870 7871 7872 7873 | ** to 2147483647. The number of virtual machine operations can be ** used as a proxy for the total work done by the prepared statement. ** If the number of virtual machine operations exceeds 2147483647 ** then the value returned by this statement status code is undefined. ** ** [[SQLITE_STMTSTATUS_REPREPARE]] <dt>SQLITE_STMTSTATUS_REPREPARE</dt> ** <dd>^This is the number of times that the prepare statement has been | | | 8221 8222 8223 8224 8225 8226 8227 8228 8229 8230 8231 8232 8233 8234 8235 | ** to 2147483647. The number of virtual machine operations can be ** used as a proxy for the total work done by the prepared statement. ** If the number of virtual machine operations exceeds 2147483647 ** then the value returned by this statement status code is undefined. ** ** [[SQLITE_STMTSTATUS_REPREPARE]] <dt>SQLITE_STMTSTATUS_REPREPARE</dt> ** <dd>^This is the number of times that the prepare statement has been ** automatically regenerated due to schema changes or changes to ** [bound parameters] that might affect the query plan. ** ** [[SQLITE_STMTSTATUS_RUN]] <dt>SQLITE_STMTSTATUS_RUN</dt> ** <dd>^This is the number of times that the prepared statement has ** been run. A single "run" for the purposes of this counter is one ** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()]. ** The counter is incremented on the first [sqlite3_step()] call of each |
| ︙ | ︙ | |||
8038 8039 8040 8041 8042 8043 8044 | ** Otherwise return NULL. ** <tr><td> 2 <td> Make every effort to allocate a new page. Only return ** NULL if allocating a new page is effectively impossible. ** </table> ** ** ^(SQLite will normally invoke xFetch() with a createFlag of 0 or 1. SQLite ** will only use a createFlag of 2 after a prior call with a createFlag of 1 | | | 8392 8393 8394 8395 8396 8397 8398 8399 8400 8401 8402 8403 8404 8405 8406 | ** Otherwise return NULL. ** <tr><td> 2 <td> Make every effort to allocate a new page. Only return ** NULL if allocating a new page is effectively impossible. ** </table> ** ** ^(SQLite will normally invoke xFetch() with a createFlag of 0 or 1. SQLite ** will only use a createFlag of 2 after a prior call with a createFlag of 1 ** failed.)^ In between the xFetch() calls, SQLite may ** attempt to unpin one or more cache pages by spilling the content of ** pinned pages to disk and synching the operating system disk cache. ** ** [[the xUnpin() page cache method]] ** ^xUnpin() is called by SQLite with a pointer to a currently pinned page ** as its second argument. If the third parameter, discard, is non-zero, ** then the page must be evicted from the cache. |
| ︙ | ︙ | |||
8356 8357 8358 8359 8360 8361 8362 | ** identity of the database connection (the blocking connection) that ** has locked the required resource is stored internally. ^After an ** application receives an SQLITE_LOCKED error, it may call the ** sqlite3_unlock_notify() method with the blocked connection handle as ** the first argument to register for a callback that will be invoked ** when the blocking connections current transaction is concluded. ^The ** callback is invoked from within the [sqlite3_step] or [sqlite3_close] | | | 8710 8711 8712 8713 8714 8715 8716 8717 8718 8719 8720 8721 8722 8723 8724 | ** identity of the database connection (the blocking connection) that ** has locked the required resource is stored internally. ^After an ** application receives an SQLITE_LOCKED error, it may call the ** sqlite3_unlock_notify() method with the blocked connection handle as ** the first argument to register for a callback that will be invoked ** when the blocking connections current transaction is concluded. ^The ** callback is invoked from within the [sqlite3_step] or [sqlite3_close] ** call that concludes the blocking connection's transaction. ** ** ^(If sqlite3_unlock_notify() is called in a multi-threaded application, ** there is a chance that the blocking connection will have already ** concluded its transaction by the time sqlite3_unlock_notify() is invoked. ** If this happens, then the specified callback is invoked immediately, ** from within the call to sqlite3_unlock_notify().)^ ** |
| ︙ | ︙ | |||
8394 8395 8396 8397 8398 8399 8400 | ** When an unlock-notify callback is registered, the application provides a ** single void* pointer that is passed to the callback when it is invoked. ** However, the signature of the callback function allows SQLite to pass ** it an array of void* context pointers. The first argument passed to ** an unlock-notify callback is a pointer to an array of void* pointers, ** and the second is the number of entries in the array. ** | | | 8748 8749 8750 8751 8752 8753 8754 8755 8756 8757 8758 8759 8760 8761 8762 | ** When an unlock-notify callback is registered, the application provides a ** single void* pointer that is passed to the callback when it is invoked. ** However, the signature of the callback function allows SQLite to pass ** it an array of void* context pointers. The first argument passed to ** an unlock-notify callback is a pointer to an array of void* pointers, ** and the second is the number of entries in the array. ** ** When a blocking connection's transaction is concluded, there may be ** more than one blocked connection that has registered for an unlock-notify ** callback. ^If two or more such blocked connections have specified the ** same callback function, then instead of invoking the callback function ** multiple times, it is invoked once with the set of void* context pointers ** specified by the blocked connections bundled together into an array. ** This gives the application an opportunity to prioritize any actions ** related to the set of unblocked database connections. |
| ︙ | ︙ | |||
8742 8743 8744 8745 8746 8747 8748 | ** This function may be called by either the [xConnect] or [xCreate] method ** of a [virtual table] implementation to configure ** various facets of the virtual table interface. ** ** If this interface is invoked outside the context of an xConnect or ** xCreate virtual table method then the behavior is undefined. ** | | | > > > > | > > | | 9096 9097 9098 9099 9100 9101 9102 9103 9104 9105 9106 9107 9108 9109 9110 9111 9112 9113 9114 9115 9116 9117 9118 9119 9120 9121 9122 9123 9124 9125 9126 9127 9128 9129 9130 9131 |
** This function may be called by either the [xConnect] or [xCreate] method
** of a [virtual table] implementation to configure
** various facets of the virtual table interface.
**
** If this interface is invoked outside the context of an xConnect or
** xCreate virtual table method then the behavior is undefined.
**
** In the call sqlite3_vtab_config(D,C,...) the D parameter is the
** [database connection] in which the virtual table is being created and
** which is passed in as the first argument to the [xConnect] or [xCreate]
** method that is invoking sqlite3_vtab_config(). The C parameter is one
** of the [virtual table configuration options]. The presence and meaning
** of parameters after C depend on which [virtual table configuration option]
** is used.
*/
SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
/*
** CAPI3REF: Virtual Table Configuration Options
** KEYWORDS: {virtual table configuration options}
** KEYWORDS: {virtual table configuration option}
**
** These macros define the various options to the
** [sqlite3_vtab_config()] interface that [virtual table] implementations
** can use to customize and optimize their behavior.
**
** <dl>
** [[SQLITE_VTAB_CONSTRAINT_SUPPORT]]
** <dt>SQLITE_VTAB_CONSTRAINT_SUPPORT</dt>
** <dd>Calls of the form
** [sqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported,
** where X is an integer. If X is zero, then the [virtual table] whose
** [xCreate] or [xConnect] method invoked [sqlite3_vtab_config()] does not
** support constraints. In this configuration (which is the default) if
** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire
** statement is rolled back as if [ON CONFLICT | OR ABORT] had been
|
| ︙ | ︙ | |||
8786 8787 8788 8789 8790 8791 8792 8793 8794 8795 8796 8797 8798 8799 8800 8801 8802 | ** must do so within the [xUpdate] method. If a call to the ** [sqlite3_vtab_on_conflict()] function indicates that the current ON ** CONFLICT policy is REPLACE, the virtual table implementation should ** silently replace the appropriate rows within the xUpdate callback and ** return SQLITE_OK. Or, if this is not possible, it may return ** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT ** constraint handling. ** </dl> */ #define SQLITE_VTAB_CONSTRAINT_SUPPORT 1 /* ** CAPI3REF: Determine The Virtual Table Conflict Policy ** ** This function may only be called from within a call to the [xUpdate] method ** of a [virtual table] implementation for an INSERT or UPDATE operation. ^The ** value returned is one of [SQLITE_ROLLBACK], [SQLITE_IGNORE], [SQLITE_FAIL], | > > > > > > > > > > > > > > > > > > > > > > | 9146 9147 9148 9149 9150 9151 9152 9153 9154 9155 9156 9157 9158 9159 9160 9161 9162 9163 9164 9165 9166 9167 9168 9169 9170 9171 9172 9173 9174 9175 9176 9177 9178 9179 9180 9181 9182 9183 9184 | ** must do so within the [xUpdate] method. If a call to the ** [sqlite3_vtab_on_conflict()] function indicates that the current ON ** CONFLICT policy is REPLACE, the virtual table implementation should ** silently replace the appropriate rows within the xUpdate callback and ** return SQLITE_OK. Or, if this is not possible, it may return ** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT ** constraint handling. ** </dd> ** ** [[SQLITE_VTAB_DIRECTONLY]]<dt>SQLITE_VTAB_DIRECTONLY</dt> ** <dd>Calls of the form ** [sqlite3_vtab_config](db,SQLITE_VTAB_DIRECTONLY) from within the ** the [xConnect] or [xCreate] methods of a [virtual table] implmentation ** prohibits that virtual table from being used from within triggers and ** views. ** </dd> ** ** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt> ** <dd>Calls of the form ** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the ** the [xConnect] or [xCreate] methods of a [virtual table] implmentation ** identify that virtual table as being safe to use from within triggers ** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the ** virtual table can do no serious harm even if it is controlled by a ** malicious hacker. Developers should avoid setting the SQLITE_VTAB_INNOCUOUS ** flag unless absolutely necessary. ** </dd> ** </dl> */ #define SQLITE_VTAB_CONSTRAINT_SUPPORT 1 #define SQLITE_VTAB_INNOCUOUS 2 #define SQLITE_VTAB_DIRECTONLY 3 /* ** CAPI3REF: Determine The Virtual Table Conflict Policy ** ** This function may only be called from within a call to the [xUpdate] method ** of a [virtual table] implementation for an INSERT or UPDATE operation. ^The ** value returned is one of [SQLITE_ROLLBACK], [SQLITE_IGNORE], [SQLITE_FAIL], |
| ︙ | ︙ | |||
8868 8869 8870 8871 8872 8873 8874 | ** ** When the value returned to V is a string, space to hold that string is ** managed by the prepared statement S and will be automatically freed when ** S is finalized. ** ** <dl> ** [[SQLITE_SCANSTAT_NLOOP]] <dt>SQLITE_SCANSTAT_NLOOP</dt> | | | | | | | | 9250 9251 9252 9253 9254 9255 9256 9257 9258 9259 9260 9261 9262 9263 9264 9265 9266 9267 9268 9269 9270 9271 9272 9273 9274 9275 9276 9277 9278 9279 9280 9281 9282 9283 9284 9285 9286 9287 9288 9289 9290 | ** ** When the value returned to V is a string, space to hold that string is ** managed by the prepared statement S and will be automatically freed when ** S is finalized. ** ** <dl> ** [[SQLITE_SCANSTAT_NLOOP]] <dt>SQLITE_SCANSTAT_NLOOP</dt> ** <dd>^The [sqlite3_int64] variable pointed to by the V parameter will be ** set to the total number of times that the X-th loop has run.</dd> ** ** [[SQLITE_SCANSTAT_NVISIT]] <dt>SQLITE_SCANSTAT_NVISIT</dt> ** <dd>^The [sqlite3_int64] variable pointed to by the V parameter will be set ** to the total number of rows examined by all iterations of the X-th loop.</dd> ** ** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt> ** <dd>^The "double" variable pointed to by the V parameter will be set to the ** query planner's estimate for the average number of rows output from each ** iteration of the X-th loop. If the query planner's estimates was accurate, ** then this value will approximate the quotient NVISIT/NLOOP and the ** product of this value for all prior loops with the same SELECTID will ** be the NLOOP value for the current loop. ** ** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt> ** <dd>^The "const char *" variable pointed to by the V parameter will be set ** to a zero-terminated UTF-8 string containing the name of the index or table ** used for the X-th loop. ** ** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt> ** <dd>^The "const char *" variable pointed to by the V parameter will be set ** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN] ** description for the X-th loop. ** ** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECT</dt> ** <dd>^The "int" variable pointed to by the V parameter will be set to the ** "select-id" for the X-th loop. The select-id identifies which query or ** subquery the loop is part of. The main query has a select-id of zero. ** The select-id is the same value as is output in the first column ** of an [EXPLAIN QUERY PLAN] query. ** </dl> */ #define SQLITE_SCANSTAT_NLOOP 0 |
| ︙ | ︙ | |||
9008 9009 9010 9011 9012 9013 9014 | ** ^The preupdate hook is disabled by invoking [sqlite3_preupdate_hook()] ** with a NULL pointer as the second parameter. ** ^The third parameter to [sqlite3_preupdate_hook()] is passed through as ** the first parameter to callbacks. ** ** ^The preupdate hook only fires for changes to real database tables; the ** preupdate hook is not invoked for changes to [virtual tables] or to | | | 9390 9391 9392 9393 9394 9395 9396 9397 9398 9399 9400 9401 9402 9403 9404 | ** ^The preupdate hook is disabled by invoking [sqlite3_preupdate_hook()] ** with a NULL pointer as the second parameter. ** ^The third parameter to [sqlite3_preupdate_hook()] is passed through as ** the first parameter to callbacks. ** ** ^The preupdate hook only fires for changes to real database tables; the ** preupdate hook is not invoked for changes to [virtual tables] or to ** system tables like sqlite_sequence or sqlite_stat1. ** ** ^The second parameter to the preupdate callback is a pointer to ** the [database connection] that registered the preupdate hook. ** ^The third parameter to the preupdate callback is one of the constants ** [SQLITE_INSERT], [SQLITE_DELETE], or [SQLITE_UPDATE] to identify the ** kind of update operation that is about to occur. ** ^(The fourth parameter to the preupdate callback is the name of the |
| ︙ | ︙ | |||
9749 9750 9751 9752 9753 9754 9755 | /* ** CAPI3REF: Set a table filter on a Session Object. ** METHOD: sqlite3_session ** ** The second argument (xFilter) is the "filter callback". For changes to rows ** in tables that are not attached to the Session object, the filter is called ** to determine whether changes to the table's rows should be tracked or not. | | | 10131 10132 10133 10134 10135 10136 10137 10138 10139 10140 10141 10142 10143 10144 10145 |
/*
** CAPI3REF: Set a table filter on a Session Object.
** METHOD: sqlite3_session
**
** The second argument (xFilter) is the "filter callback". For changes to rows
** in tables that are not attached to the Session object, the filter is called
** to determine whether changes to the table's rows should be tracked or not.
** If xFilter returns 0, changes are not tracked. Note that once a table is
** attached, xFilter will not be called again.
*/
SQLITE_API void sqlite3session_table_filter(
sqlite3_session *pSession, /* Session object */
int(*xFilter)(
void *pCtx, /* Copy of third arg to _filter_table() */
const char *zTab /* Table name */
|
| ︙ | ︙ | |||
9923 9924 9925 9926 9927 9928 9929 | ** using [sqlite3session_changeset()], then after applying that changeset to ** database zFrom the contents of the two compatible tables would be ** identical. ** ** It an error if database zFrom does not exist or does not contain the ** required compatible table. ** | | | 10305 10306 10307 10308 10309 10310 10311 10312 10313 10314 10315 10316 10317 10318 10319 | ** using [sqlite3session_changeset()], then after applying that changeset to ** database zFrom the contents of the two compatible tables would be ** identical. ** ** It an error if database zFrom does not exist or does not contain the ** required compatible table. ** ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg ** may be set to point to a buffer containing an English language error ** message. It is the responsibility of the caller to free this buffer using ** sqlite3_free(). */ SQLITE_API int sqlite3session_diff( sqlite3_session *pSession, |
| ︙ | ︙ | |||
10060 10061 10062 10063 10064 10065 10066 | #define SQLITE_CHANGESETSTART_INVERT 0x0002 /* ** CAPI3REF: Advance A Changeset Iterator ** METHOD: sqlite3_changeset_iter ** | | | 10442 10443 10444 10445 10446 10447 10448 10449 10450 10451 10452 10453 10454 10455 10456 | #define SQLITE_CHANGESETSTART_INVERT 0x0002 /* ** CAPI3REF: Advance A Changeset Iterator ** METHOD: sqlite3_changeset_iter ** ** This function may only be used with iterators created by the function ** [sqlite3changeset_start()]. If it is called on an iterator passed to ** a conflict-handler callback by [sqlite3changeset_apply()], SQLITE_MISUSE ** is returned and the call has no effect. ** ** Immediately after an iterator is created by sqlite3changeset_start(), it ** does not point to any change in the changeset. Assuming the changeset ** is not empty, the first call to this function advances the iterator to |
| ︙ | ︙ | |||
10476 10477 10478 10479 10480 10481 10482 | ** ** If the new changeset contains changes to a table that is already present ** in the changegroup, then the number of columns and the position of the ** primary key columns for the table must be consistent. If this is not the ** case, this function fails with SQLITE_SCHEMA. If the input changeset ** appears to be corrupt and the corruption is detected, SQLITE_CORRUPT is ** returned. Or, if an out-of-memory condition occurs during processing, this | | | | 10858 10859 10860 10861 10862 10863 10864 10865 10866 10867 10868 10869 10870 10871 10872 10873 | ** ** If the new changeset contains changes to a table that is already present ** in the changegroup, then the number of columns and the position of the ** primary key columns for the table must be consistent. If this is not the ** case, this function fails with SQLITE_SCHEMA. If the input changeset ** appears to be corrupt and the corruption is detected, SQLITE_CORRUPT is ** returned. Or, if an out-of-memory condition occurs during processing, this ** function returns SQLITE_NOMEM. In all cases, if an error occurs the state ** of the final contents of the changegroup is undefined. ** ** If no error occurs, SQLITE_OK is returned. */ SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); /* ** CAPI3REF: Obtain A Composite Changeset From A Changegroup |
| ︙ | ︙ | |||
10652 10653 10654 10655 10656 10657 10658 | ** This includes the case where the UPDATE operation is attempted after ** an earlier call to the conflict handler function returned ** [SQLITE_CHANGESET_REPLACE]. ** </dl> ** ** It is safe to execute SQL statements, including those that write to the ** table that the callback related to, from within the xConflict callback. | | | 11034 11035 11036 11037 11038 11039 11040 11041 11042 11043 11044 11045 11046 11047 11048 | ** This includes the case where the UPDATE operation is attempted after ** an earlier call to the conflict handler function returned ** [SQLITE_CHANGESET_REPLACE]. ** </dl> ** ** It is safe to execute SQL statements, including those that write to the ** table that the callback related to, from within the xConflict callback. ** This can be used to further customize the application's conflict ** resolution strategy. ** ** All changes made by these functions are enclosed in a savepoint transaction. ** If any other error (aside from a constraint failure when attempting to ** write to the target database) occurs, then the savepoint transaction is ** rolled back, restoring the target database to its original state, and an ** SQLite error code returned. |
| ︙ | ︙ | |||
10962 10963 10964 10965 10966 10967 10968 | /* ** CAPI3REF: Rebase a changeset ** EXPERIMENTAL ** ** Argument pIn must point to a buffer containing a changeset nIn bytes ** in size. This function allocates and populates a buffer with a copy | | | 11344 11345 11346 11347 11348 11349 11350 11351 11352 11353 11354 11355 11356 11357 11358 | /* ** CAPI3REF: Rebase a changeset ** EXPERIMENTAL ** ** Argument pIn must point to a buffer containing a changeset nIn bytes ** in size. This function allocates and populates a buffer with a copy ** of the changeset rebased according to the configuration of the ** rebaser object passed as the first argument. If successful, (*ppOut) ** is set to point to the new buffer containing the rebased changeset and ** (*pnOut) to its size in bytes and SQLITE_OK returned. It is the ** responsibility of the caller to eventually free the new buffer using ** sqlite3_free(). Otherwise, if an error occurs, (*ppOut) and (*pnOut) ** are set to zero and an SQLite error code returned. */ |
| ︙ | ︙ | |||
11370 11371 11372 11373 11374 11375 11376 | ** If the query runs to completion without incident, SQLITE_OK is returned. ** Or, if some error occurs before the query completes or is aborted by ** the callback, an SQLite error code is returned. ** ** ** xSetAuxdata(pFts5, pAux, xDelete) ** | | | 11752 11753 11754 11755 11756 11757 11758 11759 11760 11761 11762 11763 11764 11765 11766 | ** If the query runs to completion without incident, SQLITE_OK is returned. ** Or, if some error occurs before the query completes or is aborted by ** the callback, an SQLite error code is returned. ** ** ** xSetAuxdata(pFts5, pAux, xDelete) ** ** Save the pointer passed as the second argument as the extension function's ** "auxiliary data". The pointer may then be retrieved by the current or any ** future invocation of the same fts5 extension function made as part of ** the same MATCH query using the xGetAuxdata() API. ** ** Each extension function is allocated a single auxiliary data slot for ** each FTS query (MATCH expression). If the extension function is invoked ** more than once for a single FTS query, then all invocations share a |
| ︙ | ︙ | |||
11612 11613 11614 11615 11616 11617 11618 | ** of "first place" within the document set, but not alternative forms ** such as "1st place". In some applications, it would be better to match ** all instances of "first place" or "1st place" regardless of which form ** the user specified in the MATCH query text. ** ** There are several ways to approach this in FTS5: ** | | | | 11994 11995 11996 11997 11998 11999 12000 12001 12002 12003 12004 12005 12006 12007 12008 12009 | ** of "first place" within the document set, but not alternative forms ** such as "1st place". In some applications, it would be better to match ** all instances of "first place" or "1st place" regardless of which form ** the user specified in the MATCH query text. ** ** There are several ways to approach this in FTS5: ** ** <ol><li> By mapping all synonyms to a single token. In this case, using ** the above example, this means that the tokenizer returns the ** same token for inputs "first" and "1st". Say that token is in ** fact "first", so that when the user inserts the document "I won ** 1st place" entries are added to the index for tokens "i", "won", ** "first" and "place". If the user then queries for '1st + place', ** the tokenizer substitutes "first" for "1st" and the query works ** as expected. ** |
| ︙ | ︙ |
| ︙ | ︙ | |||
357 358 359 360 361 362 363 |
}else{
int rc;
if( isLink || isNewLink ){
rc = -1;
blob_zero(&b); /* because we reset it later */
fossil_print("***** Cannot merge symlink %s\n", zNew);
}else{
| | | 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 |
}else{
int rc;
if( isLink || isNewLink ){
rc = -1;
blob_zero(&b); /* because we reset it later */
fossil_print("***** Cannot merge symlink %s\n", zNew);
}else{
rc = merge_3way(&a, zOPath, &b, &out, MERGE_KEEP_FILES);
blob_write_to_file(&out, zNPath);
blob_reset(&out);
file_setexe(zNPath, isExec);
}
if( rc ){
fossil_print("CONFLICT %s\n", zNew);
nConflict++;
|
| ︙ | ︙ | |||
508 509 510 511 512 513 514 | } /* ** COMMAND: stash ** ** Usage: %fossil stash SUBCOMMAND ARGS... ** | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > | 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 |
}
/*
** COMMAND: stash
**
** Usage: %fossil stash SUBCOMMAND ARGS...
**
** > fossil stash
** > fossil stash save ?-m|--comment COMMENT? ?FILES...?
** > fossil stash snapshot ?-m|--comment COMMENT? ?FILES...?
**
** Save the current changes in the working tree as a new stash.
** Then revert the changes back to the last check-in. If FILES
** are listed, then only stash and revert the named files. The
** "save" verb can be omitted if and only if there are no other
** arguments. The "snapshot" verb works the same as "save" but
** omits the revert, keeping the checkout unchanged.
**
** > fossil stash list|ls ?-v|--verbose? ?-W|--width <num>?
**
** List all changes sets currently stashed. Show information about
** individual files in each changeset if -v or --verbose is used.
**
** > fossil stash show|cat ?STASHID? ?DIFF-OPTIONS?
** > fossil stash gshow|gcat ?STASHID? ?DIFF-OPTIONS?
**
** Show the contents of a stash as a diff against its baseline.
** With gshow and gcat, gdiff-command is used instead of internal
** diff logic.
**
** > fossil stash pop
** > fossil stash apply ?STASHID?
**
** Apply STASHID or the most recently created stash to the current
** working checkout. The "pop" command deletes that changeset from
** the stash after applying it but the "apply" command retains the
** changeset.
**
** > fossil stash goto ?STASHID?
**
** Update to the baseline checkout for STASHID then apply the
** changes of STASHID. Keep STASHID so that it can be reused
** This command is undoable.
**
** > fossil stash drop|rm ?STASHID? ?-a|--all?
**
** Forget everything about STASHID. Forget the whole stash if the
** -a|--all flag is used. Individual drops are undoable but -a|--all
** is not.
**
** > fossil stash diff ?STASHID? ?DIFF-OPTIONS?
** > fossil stash gdiff ?STASHID? ?DIFF-OPTIONS?
**
** Show diffs of the current working directory and what that
** directory would be if STASHID were applied. With gdiff,
** gdiff-command is used instead of internal diff logic.
**
** SUMMARY:
** * fossil stash
** * fossil stash save ?-m|--comment COMMENT? ?FILES...?
** * fossil stash snapshot ?-m|--comment COMMENT? ?FILES...?
** * fossil stash list|ls ?-v|--verbose? ?-W|--width <num>?
** * fossil stash show|cat ?STASHID? ?DIFF-OPTIONS?
** * fossil stash gshow|gcat ?STASHID? ?DIFF-OPTIONS?
** * fossil stash pop
** * fossil stash apply|goto ?STASHID?
** * fossil stash drop|rm ?STASHID? ?-a|--all?
** * fossil stash diff ?STASHID? ?DIFF-OPTIONS?
** * fossil stash gdiff ?STASHID? ?DIFF-OPTIONS?
*/
void stash_cmd(void){
const char *zCmd;
int nCmd;
int stashid = 0;
undo_capture_command_line();
db_must_be_within_tree();
db_open_config(0, 0);
db_begin_transaction();
stash_tables_exist_and_current();
if( g.argc<=2 ){
zCmd = "save";
}else{
zCmd = g.argv[2];
}
nCmd = strlen(zCmd);
if( memcmp(zCmd, "save", nCmd)==0 ){
if( unsaved_changes(0)==0 ){
fossil_fatal("nothing to stash");
}
stashid = stash_create();
undo_disable();
if( g.argc>=2 ){
int nFile = db_int(0, "SELECT count(*) FROM stashfile WHERE stashid=%d",
stashid);
char **newArgv = fossil_malloc( sizeof(char*)*(nFile+2) );
int i = 2;
|
| ︙ | ︙ | |||
611 612 613 614 615 616 617 618 619 620 621 622 623 624 |
if( nFile==0 ) return;
}
/* Make sure the stash has committed before running the revert, so that
** we have a copy of the changes before deleting them. */
db_commit_transaction();
g.argv[1] = "revert";
revert_cmd();
}else
if( memcmp(zCmd, "snapshot", nCmd)==0 ){
stash_create();
}else
if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){
Stmt q, q2;
int n = 0, width;
| > | 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 |
if( nFile==0 ) return;
}
/* Make sure the stash has committed before running the revert, so that
** we have a copy of the changes before deleting them. */
db_commit_transaction();
g.argv[1] = "revert";
revert_cmd();
return;
}else
if( memcmp(zCmd, "snapshot", nCmd)==0 ){
stash_create();
}else
if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){
Stmt q, q2;
int n = 0, width;
|
| ︙ | ︙ |
| ︙ | ︙ | |||
215 216 217 218 219 220 221 |
@ %,d(n)
@ </td></tr>
@ <tr><th>Number Of Wiki Pages:</th><td>
n = db_int(0, "SELECT count(*) FROM tag /*scan*/"
" WHERE +tagname GLOB 'wiki-*'");
@ %,d(n)
@ </td></tr>
| < > | > > > > > > > | > > | 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 |
@ %,d(n)
@ </td></tr>
@ <tr><th>Number Of Wiki Pages:</th><td>
n = db_int(0, "SELECT count(*) FROM tag /*scan*/"
" WHERE +tagname GLOB 'wiki-*'");
@ %,d(n)
@ </td></tr>
n = db_int(0, "SELECT count(*) FROM tag /*scan*/"
" WHERE +tagname GLOB 'tkt-*'");
if( n>0 ){
@ <tr><th>Number Of Tickets:</th><td>%,d(n)</td></tr>
}
if( db_table_exists("repository","forumpost") ){
n = db_int(0, "SELECT count(*) FROM forumpost/*scan*/");
if( n>0 ){
int nThread = db_int(0, "SELECT count(*) FROM forumpost"
" WHERE froot=fpid");
@ <tr><th>Number Of Forum Posts:</th>
@ <td>%,d(n) on %d(nThread) threads</td></tr>
}
}
}
@ <tr><th>Duration Of Project:</th><td>
n = db_int(0, "SELECT julianday('now') - (SELECT min(mtime) FROM event)"
" + 0.99");
@ %,d(n) days or approximately %.2f(n/365.2425) years.
@ </td></tr>
p = db_get("project-code", 0);
|
| ︙ | ︙ | |||
501 502 503 504 505 506 507 |
style_adunit_config(ADUNIT_RIGHT_OK);
style_submenu_element("Stat", "stat");
style_submenu_element("URLs", "urllist");
if( sqlite3_compileoption_used("ENABLE_DBSTAT_VTAB") ){
style_submenu_element("Table Sizes", "repo-tabsize");
}
blob_init(&sql,
| | | 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 |
style_adunit_config(ADUNIT_RIGHT_OK);
style_submenu_element("Stat", "stat");
style_submenu_element("URLs", "urllist");
if( sqlite3_compileoption_used("ENABLE_DBSTAT_VTAB") ){
style_submenu_element("Table Sizes", "repo-tabsize");
}
blob_init(&sql,
"SELECT sql FROM repository.sqlite_schema WHERE sql IS NOT NULL", -1);
if( zArg ){
style_submenu_element("All", "repo_schema");
blob_appendf(&sql, " AND (tbl_name=%Q OR name=%Q)", zArg, zArg);
}
blob_appendf(&sql, " ORDER BY tbl_name, type<>'table', name");
db_prepare(&q, "%s", blob_str(&sql)/*safe-for-%s*/);
blob_reset(&sql);
|
| ︙ | ︙ | |||
592 593 594 595 596 597 598 |
style_submenu_element("Stat", "stat");
if( g.perm.Admin ){
style_submenu_element("Schema", "repo_schema");
}
db_multi_exec(
"CREATE TEMP TABLE trans(name TEXT PRIMARY KEY,tabname TEXT)WITHOUT ROWID;"
"INSERT INTO trans(name,tabname)"
| | | | | | | | 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 |
style_submenu_element("Stat", "stat");
if( g.perm.Admin ){
style_submenu_element("Schema", "repo_schema");
}
db_multi_exec(
"CREATE TEMP TABLE trans(name TEXT PRIMARY KEY,tabname TEXT)WITHOUT ROWID;"
"INSERT INTO trans(name,tabname)"
" SELECT name, tbl_name FROM repository.sqlite_schema;"
"CREATE TEMP TABLE piechart(amt REAL, label TEXT);"
"INSERT INTO piechart(amt,label)"
" SELECT sum(pageno),"
" coalesce((SELECT tabname FROM trans WHERE trans.name=dbstat.name),name)"
" FROM dbstat('repository',TRUE)"
" GROUP BY 2 ORDER BY 2;"
);
nPageFree = db_int(0, "PRAGMA repository.freelist_count");
if( nPageFree>0 ){
db_multi_exec(
"INSERT INTO piechart(amt,label) VALUES(%d,'freelist')",
nPageFree
);
}
fsize = file_size(g.zRepositoryName, ExtFILE);
approxSizeName(sizeof(zBuf), zBuf, fsize);
@ <h2>Repository Size: %s(zBuf)</h2>
@ <center><svg width='800' height='500'>
piechart_render(800,500,PIE_OTHER|PIE_PERCENT);
@ </svg></center>
if( g.localOpen ){
db_multi_exec(
"DELETE FROM trans;"
"INSERT INTO trans(name,tabname)"
" SELECT name, tbl_name FROM localdb.sqlite_schema;"
"DELETE FROM piechart;"
"INSERT INTO piechart(amt,label)"
" SELECT sum(pageno), "
" coalesce((SELECT tabname FROM trans WHERE trans.name=dbstat.name),name)"
" FROM dbstat('localdb',TRUE)"
" GROUP BY 2 ORDER BY 2;"
);
nPageFree = db_int(0, "PRAGMA localdb.freelist_count");
if( nPageFree>0 ){
db_multi_exec(
"INSERT INTO piechart(amt,label) VALUES(%d,'freelist')",
nPageFree
|
| ︙ | ︙ | |||
648 649 650 651 652 653 654 | } /* ** Gather statistics on artifact types, counts, and sizes. ** ** Only populate the artstat.atype field if the bWithTypes parameter is true. */ | | | | | > | | | | 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 |
}
/*
** Gather statistics on artifact types, counts, and sizes.
**
** Only populate the artstat.atype field if the bWithTypes parameter is true.
*/
void gather_artifact_stats(int bWithTypes){
static const char zSql[] =
@ CREATE TEMP TABLE artstat(
@ id INTEGER PRIMARY KEY, -- Corresponds to BLOB.RID
@ atype TEXT, -- 'data', 'manifest', 'tag', 'wiki', etc.
@ isDelta BOOLEAN, -- true if stored as a delta
@ szExp, -- expanded, uncompressed size
@ szCmpr -- size as stored on disk
@ );
@ INSERT INTO artstat(id,atype,isDelta,szExp,szCmpr)
@ SELECT blob.rid, NULL,
@ delta.rid IS NOT NULL,
@ size, length(content)
@ FROM blob LEFT JOIN delta ON blob.rid=delta.rid
@ WHERE content IS NOT NULL;
;
static const char zSql2[] =
@ UPDATE artstat SET atype='file'
@ WHERE +id IN (SELECT fid FROM mlink);
@ UPDATE artstat SET atype='manifest'
@ WHERE id IN (SELECT objid FROM event WHERE type='ci') AND atype IS NULL;
@ UPDATE artstat SET atype='forum'
@ WHERE id IN (SELECT objid FROM event WHERE type='f') AND atype IS NULL;
@ UPDATE artstat SET atype='cluster'
@ WHERE atype IS NULL
@ AND id IN (SELECT rid FROM tagxref
@ WHERE tagid=(SELECT tagid FROM tag
@ WHERE tagname='cluster'));
@ UPDATE artstat SET atype='ticket'
@ WHERE atype IS NULL
|
| ︙ | ︙ | |||
753 754 755 756 757 758 759 | login_check_credentials(); /* These stats are expensive to compute. To disable them for ** user without check-in privileges, to prevent excessive usage by ** robots and random passers-by on the internet */ | | | > | 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 |
login_check_credentials();
/* These stats are expensive to compute. To disable them for
** user without check-in privileges, to prevent excessive usage by
** robots and random passers-by on the internet
*/
if( !g.perm.Write && !db_get_boolean("artifact_stats_enable",0) ){
login_needed(g.anon.Write);
return;
}
load_control();
style_header("Artifact Statistics");
style_submenu_element("Repository Stats", "stat");
style_submenu_element("Artifact List", "bloblist");
gather_artifact_stats(1);
db_prepare(&q,
|
| ︙ | ︙ | |||
834 835 836 837 838 839 840 |
r = db_double(0.0, "SELECT avg(szCmpr) FROM artstat WHERE NOT isDelta;");
med = db_int(0, "SELECT szCmpr FROM artstat WHERE NOT isDelta ORDER BY szCmpr"
" LIMIT 1 OFFSET %d", nFull/2);
@ <tr><th>Full-text artifact sizes:</th>
@ <td>largest: %,d(mxCmpr), average: %,d((int)r), median: %,d(med)</td>
@ </table>
| | | 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 |
r = db_double(0.0, "SELECT avg(szCmpr) FROM artstat WHERE NOT isDelta;");
med = db_int(0, "SELECT szCmpr FROM artstat WHERE NOT isDelta ORDER BY szCmpr"
" LIMIT 1 OFFSET %d", nFull/2);
@ <tr><th>Full-text artifact sizes:</th>
@ <td>largest: %,d(mxCmpr), average: %,d((int)r), median: %,d(med)</td>
@ </table>
@ <h1>Artifact Size Distribution Facts:</h1>
@ <ol>
@ <li><p>The largest %.2f(n50pct*100.0/nTotal)%% of artifacts
largest_n_artifacts(n50pct);
@ use 50%% of the total artifact space.
@ <li><p>The largest 1%% of artifacts
largest_n_artifacts((nTotal+99)/100);
@ use %lld(sz1pct*100/sumCmpr)%% of the total artifact space.
|
| ︙ | ︙ | |||
883 884 885 886 887 888 889 |
int nFull = nTotal - nDelta;
sqlite3_int64 szCmpr = db_column_int64(&q, 3);
sqlite3_int64 szExp = db_column_int64(&q, 4);
@ <tr><td>%h(zType)</td>
@ <td data-sortkey='%08x(nTotal)' align='right'>%,d(nTotal)</td>
@ <td data-sortkey='%08x(nFull)' align='right'>%,d(nFull)</td>
@ <td data-sortkey='%08x(nDelta)' align='right'>%,d(nDelta)</td>
| | | | 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 |
int nFull = nTotal - nDelta;
sqlite3_int64 szCmpr = db_column_int64(&q, 3);
sqlite3_int64 szExp = db_column_int64(&q, 4);
@ <tr><td>%h(zType)</td>
@ <td data-sortkey='%08x(nTotal)' align='right'>%,d(nTotal)</td>
@ <td data-sortkey='%08x(nFull)' align='right'>%,d(nFull)</td>
@ <td data-sortkey='%08x(nDelta)' align='right'>%,d(nDelta)</td>
@ <td data-sortkey='%016llx(szCmpr)' align='right'>%,lld(szCmpr)</td>
@ <td data-sortkey='%016llx(szExp)' align='right'>%,lld(szExp)</td>
}
@ </tbody></table>
db_finalize(&q);
if( db_exists("SELECT 1 FROM artstat WHERE atype='unused'") ){
@ <h1>Unused Artifacts:</h1>
db_prepare(&q,
|
| ︙ | ︙ |
| ︙ | ︙ | |||
48 49 50 51 52 53 54 | ** no 'y' is specified), "*" is assumed (that is also the default for ** invalid/unknown filter values). That 'y' filter is the one used for ** the event list. Note that a filter of "*" or "all" is equivalent to ** querying against the full event table. The view, however, adds an ** abstraction level to simplify the implementation code for the ** various /reports pages. ** | | > > > > > | 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 |
** no 'y' is specified), "*" is assumed (that is also the default for
** invalid/unknown filter values). That 'y' filter is the one used for
** the event list. Note that a filter of "*" or "all" is equivalent to
** querying against the full event table. The view, however, adds an
** abstraction level to simplify the implementation code for the
** various /reports pages.
**
** Returns one of: 'c', 'f', 'w', 'g', 't', 'e', representing the type of
** filter it applies, or '*' if no filter is applied (i.e. if "all" is
** used).
*/
static int stats_report_init_view(){
const char *zType = PD("type","*"); /* analog to /timeline?y=... */
const char *zRealType = NULL; /* normalized form of zType */
int rc = 0; /* result code */
char *zTimeSpan; /* Time span */
assert( !statsReportType && "Must not be called more than once." );
switch( (zType && *zType) ? *zType : 0 ){
case 'c':
case 'C':
zRealType = "ci";
rc = *zRealType;
break;
case 'e':
case 'E':
zRealType = "e";
rc = *zRealType;
break;
case 'f':
case 'F':
zRealType = "f";
rc = *zRealType;
break;
case 'g':
case 'G':
zRealType = "g";
rc = *zRealType;
break;
case 't':
case 'T':
|
| ︙ | ︙ | |||
122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
static const char *stats_report_label_for_type(){
assert( statsReportType && "Must call stats_report_init_view() first." );
switch( statsReportType ){
case 'c':
return "check-ins";
case 'e':
return "technotes";
case 'w':
return "wiki changes";
case 't':
return "ticket changes";
case 'g':
return "tag changes";
default:
| > > | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
static const char *stats_report_label_for_type(){
assert( statsReportType && "Must call stats_report_init_view() first." );
switch( statsReportType ){
case 'c':
return "check-ins";
case 'e':
return "technotes";
case 'f':
return "forum posts";
case 'w':
return "wiki changes";
case 't':
return "ticket changes";
case 'g':
return "tag changes";
default:
|
| ︙ | ︙ | |||
716 717 718 719 720 721 722 | ** Shows activity reports for the repository. ** ** Query Parameters: ** ** view=REPORT_NAME Valid values: bymonth, byyear, byuser ** user=NAME Restricts statistics to the given user ** type=TYPE Restricts the report to a specific event type: | | | 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 | ** Shows activity reports for the repository. ** ** Query Parameters: ** ** view=REPORT_NAME Valid values: bymonth, byyear, byuser ** user=NAME Restricts statistics to the given user ** type=TYPE Restricts the report to a specific event type: ** ci (check-in), f (forum), w (wiki), t (ticket), g (tag) ** Defaulting to all event types. ** ** The view-specific query parameters include: ** ** view=byweek: ** ** y=YYYY The year to report (default is the server's |
| ︙ | ︙ | |||
748 749 750 751 752 753 754 755 756 757 758 759 760 761 |
{ "By Week", "byweek", RPT_BYWEEK },
{ "By Weekday", "byweekday", RPT_BYWEEKDAY },
{ "By Year", "byyear", RPT_BYYEAR },
};
static const char *const azType[] = {
"a", "All Changes",
"ci", "Check-ins",
"g", "Tags",
"e", "Tech Notes",
"t", "Tickets",
"w", "Wiki"
};
login_check_credentials();
| > | 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 |
{ "By Week", "byweek", RPT_BYWEEK },
{ "By Weekday", "byweekday", RPT_BYWEEKDAY },
{ "By Year", "byyear", RPT_BYYEAR },
};
static const char *const azType[] = {
"a", "All Changes",
"ci", "Check-ins",
"f", "Forum Posts",
"g", "Tags",
"e", "Tech Notes",
"t", "Tickets",
"w", "Wiki"
};
login_check_credentials();
|
| ︙ | ︙ |
> > > > > > | 1 2 3 4 5 6 |
/* This file is just to demonstrate/test page-specific CSS. Using
the browser dev tools, select any link, "inspect" it, and edit
this style. */
a{
font-size: inherit;
}
|
| ︙ | ︙ | |||
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | static int sideboxUsed = 0; /* ** Ad-unit styles. */ static unsigned adUnitFlags = 0; /* ** Flags for various javascript files needed prior to </body> */ static int needHrefJs = 0; /* href.js */ static int needSortJs = 0; /* sorttable.js */ static int needGraphJs = 0; /* graph.js */ static int needCopyBtnJs = 0; /* copybtn.js */ /* ** Extra JS added to the end of the file. */ static Blob blobOnLoad = BLOB_INITIALIZER; /* | > > > > > > | 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 | static int sideboxUsed = 0; /* ** Ad-unit styles. */ static unsigned adUnitFlags = 0; /* ** Submenu disable flag */ static int submenuEnable = 1; /* ** Flags for various javascript files needed prior to </body> */ static int needHrefJs = 0; /* href.js */ static int needSortJs = 0; /* sorttable.js */ static int needGraphJs = 0; /* graph.js */ static int needCopyBtnJs = 0; /* copybtn.js */ static int needAccordionJs = 0; /* accordion.js */ /* ** Extra JS added to the end of the file. */ static Blob blobOnLoad = BLOB_INITIALIZER; /* |
| ︙ | ︙ | |||
140 141 142 143 144 145 146 |
char *xhref(const char *zExtra, const char *zFormat, ...){
char *zUrl;
va_list ap;
va_start(ap, zFormat);
zUrl = vmprintf(zFormat, ap);
va_end(ap);
if( g.perm.Hyperlink && !g.javascriptHyperlink ){
| > > | > > > > > > | | > | 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 |
char *xhref(const char *zExtra, const char *zFormat, ...){
char *zUrl;
va_list ap;
va_start(ap, zFormat);
zUrl = vmprintf(zFormat, ap);
va_end(ap);
if( g.perm.Hyperlink && !g.javascriptHyperlink ){
char *zHUrl;
if( zExtra ){
zHUrl = mprintf("<a %s href=\"%h\">", zExtra, zUrl);
}else{
zHUrl = mprintf("<a href=\"%h\">", zUrl);
}
fossil_free(zUrl);
return zHUrl;
}
needHrefJs = 1;
if( zExtra==0 ){
return mprintf("<a data-href='%z' href='%R/honeypot'>", zUrl);
}else{
return mprintf("<a %s data-href='%z' href='%R/honeypot'>",
zExtra, zUrl);
}
}
char *chref(const char *zExtra, const char *zFormat, ...){
char *zUrl;
va_list ap;
va_start(ap, zFormat);
zUrl = vmprintf(zFormat, ap);
va_end(ap);
|
| ︙ | ︙ | |||
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
aSubmenuCtrl[nSubmenuCtrl].azChoice = (const char *const *)az;
aSubmenuCtrl[nSubmenuCtrl].eVisible = STYLE_NORMAL;
aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI;
nSubmenuCtrl++;
}
}
/*
** 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 fossil_strcmp(A->zLabel, B->zLabel);
}
| > > > > > > > | | | | > > > > > > > > > > > > > > | > | | | | 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 |
aSubmenuCtrl[nSubmenuCtrl].azChoice = (const char *const *)az;
aSubmenuCtrl[nSubmenuCtrl].eVisible = STYLE_NORMAL;
aSubmenuCtrl[nSubmenuCtrl].eType = FF_MULTI;
nSubmenuCtrl++;
}
}
/*
** Disable or enable the submenu
*/
void style_submenu_enable(int onOff){
submenuEnable = onOff;
}
/*
** 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 fossil_strcmp(A->zLabel, B->zLabel);
}
/* Use this for the $current_page variable if it is not NULL. If it
** is NULL then use g.zPath.
*/
static char *local_zCurrentPage = 0;
/*
** Set the desired $current_page to something other than g.zPath
*/
void style_set_current_page(const char *zFormat, ...){
fossil_free(local_zCurrentPage);
if( zFormat==0 ){
local_zCurrentPage = 0;
}else{
va_list ap;
va_start(ap, zFormat);
local_zCurrentPage = vmprintf(zFormat, ap);
va_end(ap);
}
}
/*
** Create a TH1 variable containing the URL for the specified config
** resource. The resulting variable name will be of the form
** $[zVarPrefix]_url.
*/
static void url_var(
const char *zVarPrefix,
const char *zConfigName,
const char *zPageName
){
char *zVarName = mprintf("%s_url", zVarPrefix);
char *zUrl = 0; /* stylesheet URL */
int hasBuiltin = 0; /* true for built-in page-specific CSS */
if(0==strcmp("css",zConfigName)){
/* Account for page-specific CSS, appending a /{{g.zPath}} to the
** url only if we have a corresponding built-in page-specific CSS
** file. Do not append it to all pages because we would
** effectively cache-bust all pages which do not have
** page-specific CSS. */
char * zBuiltin = mprintf("style.%s.css", g.zPath);
hasBuiltin = builtin_file(zBuiltin,0)!=0;
fossil_free(zBuiltin);
}
zUrl = mprintf("%R/%s%s%s?id=%x", zPageName,
hasBuiltin ? "/" : "", hasBuiltin ? g.zPath : "",
skin_id(zConfigName));
Th_Store(zVarName, zUrl);
fossil_free(zUrl);
fossil_free(zVarName);
}
/*
** Create a TH1 variable containing the URL for the specified config image.
** The resulting variable name will be of the form $[zImageName]_image_url.
*/
static void image_url_var(const char *zImageName){
|
| ︙ | ︙ | |||
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 |
if( zNonce[0]==0 ){
unsigned char zSeed[24];
sqlite3_randomness(24, zSeed);
encode16(zSeed,(unsigned char*)zNonce,24);
}
return zNonce;
}
/*
** Default HTML page header text through <body>. If the repository-specific
** header template lacks a <body> tag, then all of the following is
** prepended.
*/
static char zDfltHeader[] =
@ <html>
@ <head>
@ <base href="$baseurl/$current_page" />
@ <meta http-equiv="Content-Security-Policy" content="$default_csp" />
@ <meta name="viewport" content="width=device-width, initial-scale=1.0">
@ <title>$<project_name>: $<title></title>
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed" \
@ href="$home/timeline.rss" />
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < > > > < < < < | | 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 |
if( zNonce[0]==0 ){
unsigned char zSeed[24];
sqlite3_randomness(24, zSeed);
encode16(zSeed,(unsigned char*)zNonce,24);
}
return zNonce;
}
/*
** Return the default Content Security Policy (CSP) string.
** If the toHeader argument is true, then also add the
** CSP to the HTTP reply header.
**
** The CSP comes from the "default-csp" setting if it exists and
** is non-empty. If that setting is an empty string, then the following
** default is used instead:
**
** default-src 'self' data:;
** script-src 'self' 'nonce-$nonce';
** style-src 'self' 'unsafe-inline';
**
** The text '$nonce' is replaced by style_nonce() if and whereever it
** occurs in the input string.
**
** The string returned is obtained from fossil_malloc() and
** should be released by the caller.
*/
char *style_csp(int toHeader){
static const char zBackupCSP[] =
"default-src 'self' data:; "
"script-src 'self' 'nonce-$nonce'; "
"style-src 'self' 'unsafe-inline'";
const char *zFormat = db_get("default-csp","");
Blob csp;
char *zNonce;
char *zCsp;
if( zFormat[0]==0 ){
zFormat = zBackupCSP;
}
blob_init(&csp, 0, 0);
while( zFormat[0] && (zNonce = strstr(zFormat,"$nonce"))!=0 ){
blob_append(&csp, zFormat, (int)(zNonce - zFormat));
blob_append(&csp, style_nonce(), -1);
zFormat = zNonce + 6;
}
blob_append(&csp, zFormat, -1);
zCsp = blob_str(&csp);
if( toHeader ){
cgi_printf_header("Content-Security-Policy: %s\r\n", zCsp);
}
return zCsp;
}
/*
** Default HTML page header text through <body>. If the repository-specific
** header template lacks a <body> tag, then all of the following is
** prepended.
*/
static char zDfltHeader[] =
@ <html>
@ <head>
@ <base href="$baseurl/$current_page" />
@ <meta http-equiv="Content-Security-Policy" content="$default_csp" />
@ <meta name="viewport" content="width=device-width, initial-scale=1.0">
@ <title>$<project_name>: $<title></title>
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed" \
@ href="$home/timeline.rss" />
@ <link rel="stylesheet" href="$stylesheet_url" type="text/css" />
@ </head>
@ <body>
;
/*
** Initialize all the default TH1 variables
*/
static void style_init_th1_vars(const char *zTitle){
const char *zNonce = style_nonce();
char *zDfltCsp;
zDfltCsp = style_csp(1);
/*
** Do not overwrite the TH1 variable "default_csp" if it exists, as this
** allows it to be properly overridden via the TH1 setup script (i.e. it
** is evaluated before the header is rendered).
*/
Th_MaybeStore("default_csp", zDfltCsp);
fossil_free(zDfltCsp);
Th_Store("nonce", zNonce);
Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
Th_Store("project_description", db_get("project-description",""));
if( zTitle ) Th_Store("title", zTitle);
Th_Store("baseurl", g.zBaseURL);
Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
Th_Store("home", g.zTop);
|
| ︙ | ︙ | |||
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 |
/*
** Indicate that the table-sorting javascript is needed.
*/
void style_table_sorter(void){
needSortJs = 1;
}
/*
** Indicate that the timeline graph javascript is needed.
*/
void style_graph_generator(void){
needGraphJs = 1;
}
/*
** Indicate that the copy button javascript is needed.
*/
void style_copybutton_control(void){
needCopyBtnJs = 1;
}
/*
** Generate code to load a single javascript file
*/
void style_load_one_js_file(const char *zFile){
| > > > > > > > | | 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 |
/*
** Indicate that the table-sorting javascript is needed.
*/
void style_table_sorter(void){
needSortJs = 1;
}
/*
** Indicate that the accordion javascript is needed.
*/
void style_accordion(void){
needAccordionJs = 1;
}
/*
** Indicate that the timeline graph javascript is needed.
*/
void style_graph_generator(void){
needGraphJs = 1;
}
/*
** Indicate that the copy button javascript is needed.
*/
void style_copybutton_control(void){
needCopyBtnJs = 1;
}
/*
** Generate code to load a single javascript file
*/
void style_load_one_js_file(const char *zFile){
@ <script src='%R/builtin/%s(zFile)?id=%S(fossil_exe_id())'></script>
}
/*
** All extra JS files to load.
*/
static const char *azJsToLoad[4];
static int nJsToLoad = 0;
|
| ︙ | ︙ | |||
684 685 686 687 688 689 690 691 692 693 694 695 696 697 |
}
if( needGraphJs ){
cgi_append_content(builtin_text("graph.js"),-1);
}
if( needCopyBtnJs ){
cgi_append_content(builtin_text("copybtn.js"),-1);
}
for(i=0; i<nJsToLoad; i++){
cgi_append_content(builtin_text(azJsToLoad[i]),-1);
}
if( blob_size(&blobOnLoad)>0 ){
@ window.onload = function(){
cgi_append_content(blob_buffer(&blobOnLoad), blob_size(&blobOnLoad));
cgi_append_content("\n}\n", -1);
| > > > | 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 |
}
if( needGraphJs ){
cgi_append_content(builtin_text("graph.js"),-1);
}
if( needCopyBtnJs ){
cgi_append_content(builtin_text("copybtn.js"),-1);
}
if( needAccordionJs ){
cgi_append_content(builtin_text("accordion.js"),-1);
}
for(i=0; i<nJsToLoad; i++){
cgi_append_content(builtin_text(azJsToLoad[i]),-1);
}
if( blob_size(&blobOnLoad)>0 ){
@ window.onload = function(){
cgi_append_content(blob_buffer(&blobOnLoad), blob_size(&blobOnLoad));
cgi_append_content("\n}\n", -1);
|
| ︙ | ︙ | |||
720 721 722 723 724 725 726 | if( !headerHasBeenGenerated ) return; /* Go back and put the submenu at the top of the page. We delay the ** creation of the submenu until the end so that we can add elements ** to the submenu while generating page text. */ cgi_destination(CGI_HEADER); | | | 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 |
if( !headerHasBeenGenerated ) return;
/* Go back and put the submenu at the top of the page. We delay the
** creation of the submenu until the end so that we can add elements
** to the submenu while generating page text.
*/
cgi_destination(CGI_HEADER);
if( submenuEnable && nSubmenu+nSubmenuCtrl>0 ){
int i;
if( nSubmenuCtrl ){
@ <form id='f01' method='GET' action='%R/%s(g.zPath)'>
@ <input type='hidden' name='udc' value='1'>
cgi_tag_query_parameter("udc");
}
@ <div class="submenu">
|
| ︙ | ︙ | |||
895 896 897 898 899 900 901 |
/* End the side-box
*/
void style_sidebox_end(void){
@ </div>
}
| < < < < < < < < < < < < < < < < < < < < < < < | 985 986 987 988 989 990 991 992 993 994 995 996 997 998 |
/* End the side-box
*/
void style_sidebox_end(void){
@ </div>
}
/*
** Search string zCss for zSelector.
**
** Return true if found. Return false if not found
*/
static int containsSelector(const char *zCss, const char *zSelector){
const char *z;
|
| ︙ | ︙ | |||
947 948 949 950 951 952 953 954 955 956 957 958 959 960 |
/*
** COMMAND: test-contains-selector
**
** Usage: %fossil test-contains-selector FILENAME SELECTOR
**
** Determine if the CSS stylesheet FILENAME contains SELECTOR.
*/
void contains_selector_cmd(void){
int found;
char *zSelector;
Blob css;
if( g.argc!=4 ) usage("FILENAME SELECTOR");
blob_read_from_file(&css, g.argv[2], ExtFILE);
| > > > > > | 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 |
/*
** COMMAND: test-contains-selector
**
** Usage: %fossil test-contains-selector FILENAME SELECTOR
**
** Determine if the CSS stylesheet FILENAME contains SELECTOR.
**
** Note that as of 2020-05-28, the default rules are always emitted,
** so the containsSelector() logic is no longer applied when emitting
** style.css. It is unclear whether this test command is now obsolete
** or whether it may still serve a purpose.
*/
void contains_selector_cmd(void){
int found;
char *zSelector;
Blob css;
if( g.argc!=4 ) usage("FILENAME SELECTOR");
blob_read_from_file(&css, g.argv[2], ExtFILE);
|
| ︙ | ︙ | |||
979 980 981 982 983 984 985 986 987 988 989 990 991 992 |
/* Default behavior is to return javascript */
cgi_set_content_type("application/javascript");
}
style_init_th1_vars(0);
Th_Render(zScript?zScript:"");
}
/*
** WEBPAGE: style.css
**
** Return the style sheet.
*/
void page_style_css(void){
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | < | > | < < < | | | < < < | | < < < < < < | | 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 |
/* Default behavior is to return javascript */
cgi_set_content_type("application/javascript");
}
style_init_th1_vars(0);
Th_Render(zScript?zScript:"");
}
/*
** If one of the "name" or "page" URL parameters (in that order)
** is set then this function looks for page/page group-specific
** CSS and (if found) appends it to pOut, else it is a no-op.
*/
static void page_style_css_append_page_style(Blob *pOut){
const char *zPage = PD("name",P("page"));
char * zFile;
int nFile = 0;
const char *zBuiltin;
if(zPage==0 || zPage[0]==0){
return;
}
zFile = mprintf("style.%s.css", zPage);
zBuiltin = (const char *)builtin_file(zFile, &nFile);
if(nFile>0){
blob_appendf(pOut,
"\n/***********************************************************\n"
"** Start of page-specific CSS for page %s...\n"
"***********************************************************/\n",
zPage);
blob_append(pOut, zBuiltin, nFile);
blob_appendf(pOut,
"\n/***********************************************************\n"
"** End of page-specific CSS for page %s.\n"
"***********************************************************/\n",
zPage);
fossil_free(zFile);
return;
}
/* Potential TODO: check for aliases/page groups. e.g. group all
** /forumXYZ CSS into one file, all /setupXYZ into another, etc. As
** of this writing, doing so would only shave a few kb from
** default.css. */
fossil_free(zFile);
}
/*
** WEBPAGE: style.css
**
** Return the style sheet.
*/
void page_style_css(void){
Blob css = empty_blob;
int i;
const char * zDefaults;
cgi_set_content_type("text/css");
/* Emit all default rules... */
zDefaults = (const char*)builtin_file("default.css", &i);
blob_append(&css, zDefaults, i);
/* Page-specific CSS, if any... */
page_style_css_append_page_style(&css);
blob_append(&css,
"\n/***********************************************************\n"
"** All CSS which follows is supplied by the repository \"skin\".\n"
"***********************************************************/\n",
-1);
blob_append(&css,skin_get("css"),-1);
/* Process through TH1 in order to give an opportunity to substitute
** variables such as $baseurl.
*/
Th_Store("baseurl", g.zBaseURL);
Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
Th_Store("home", g.zTop);
image_url_var("logo");
|
| ︙ | ︙ | |||
1129 1130 1131 1132 1133 1134 1135 |
** For administators, or if the test_env_enable setting is true, then
** details of the request environment are displayed. Otherwise, just
** the error message is shown.
**
** If zFormat is an empty string, then this is the /test_env page.
*/
void webpage_error(const char *zFormat, ...){
| < < < < < < < < < < < < < < < < < < | | 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 |
** For administators, or if the test_env_enable setting is true, then
** details of the request environment are displayed. Otherwise, just
** the error message is shown.
**
** If zFormat is an empty string, then this is the /test_env page.
*/
void webpage_error(const char *zFormat, ...){
int showAll;
char *zErr = 0;
int isAuth = 0;
char zCap[100];
login_check_credentials();
if( g.perm.Admin || g.perm.Setup || db_get_boolean("test_env_enable",0) ){
isAuth = 1;
}
cgi_load_environment();
if( zFormat[0] ){
va_list ap;
va_start(ap, zFormat);
zErr = vmprintf(zFormat, ap);
va_end(ap);
style_header("Bad Request");
@ <h1>/%h(g.zPath): %h(zErr)</h1>
|
| ︙ | ︙ | |||
1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 |
@ capabilities = %s(find_capabilities(zCap))<br />
if( zCap[0] ){
@ anonymous-adds = %s(find_anon_capabilities(zCap))<br />
}
@ g.zRepositoryName = %h(g.zRepositoryName)<br />
@ load_average() = %f(load_average())<br />
@ cgi_csrf_safe(0) = %d(cgi_csrf_safe(0))<br />
@ <hr />
P("HTTP_USER_AGENT");
cgi_print_all(showAll, 0);
if( showAll && blob_size(&g.httpHeader)>0 ){
@ <hr />
@ <pre>
@ %h(blob_str(&g.httpHeader))
| > | 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 |
@ capabilities = %s(find_capabilities(zCap))<br />
if( zCap[0] ){
@ anonymous-adds = %s(find_anon_capabilities(zCap))<br />
}
@ g.zRepositoryName = %h(g.zRepositoryName)<br />
@ load_average() = %f(load_average())<br />
@ cgi_csrf_safe(0) = %d(cgi_csrf_safe(0))<br />
@ fossil_exe_id() = %h(fossil_exe_id())<br />
@ <hr />
P("HTTP_USER_AGENT");
cgi_print_all(showAll, 0);
if( showAll && blob_size(&g.httpHeader)>0 ){
@ <hr />
@ <pre>
@ %h(blob_str(&g.httpHeader))
|
| ︙ | ︙ | |||
1236 1237 1238 1239 1240 1241 1242 |
cgi_reset_content();
webpage_error("assertion fault at %s:%d - %s", zFile, iLine, zExpr);
}
#if INTERFACE
# define webpage_assert(T) if(!(T)){webpage_assert_page(__FILE__,__LINE__,#T);}
#endif
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 |
cgi_reset_content();
webpage_error("assertion fault at %s:%d - %s", zFile, iLine, zExpr);
}
#if INTERFACE
# define webpage_assert(T) if(!(T)){webpage_assert_page(__FILE__,__LINE__,#T);}
#endif
/*
** Returns a pseudo-random input field ID, for use in associating an
** ID-less input field with a label. The memory is owned by the
** caller.
*/
static char * style_next_input_id(){
static int inputID = 0;
++inputID;
return mprintf("input-id-%d", inputID);
}
/*
** Outputs a labeled checkbox element. zWrapperId is an optional ID
** value for the containing element (see below). zFieldName is the
** form element name. zLabel is the label for the checkbox. zValue is
** the optional value for the checkbox. zTip is an optional tooltip,
** which gets set as the "title" attribute of the outermost
** element. If isChecked is true, the checkbox gets the "checked"
** attribute set, else it is not.
**
** Resulting structure:
**
** <span class='input-with-label' title={{zTip}} id={{zWrapperId}}>
** <input type='checkbox' name={{zFieldName}} value={{zValue}}
** id='A RANDOM VALUE'
** {{isChecked ? " checked : ""}}/>
** <label for='ID OF THE INPUT FIELD'>{{zLabel}}</label>
** </span>
**
** zLabel, and zValue are required. zFieldName, zWrapperId, and zTip
** are may be NULL or empty.
**
** Be sure that the input-with-label CSS class is defined sensibly, in
** particular, having its display:inline-block is useful for alignment
** purposes.
*/
void style_labeled_checkbox(const char * zWrapperId,
const char *zFieldName, const char * zLabel,
const char * zValue, int isChecked,
const char * zTip){
char * zLabelID = style_next_input_id();
CX("<span class='input-with-label'");
if(zTip && *zTip){
CX(" title='%h'", zTip);
}
if(zWrapperId && *zWrapperId){
CX(" id='%s'",zWrapperId);
}
CX("><input type='checkbox' id='%s' ", zLabelID);
if(zFieldName && *zFieldName){
CX("name='%s' ",zFieldName);
}
CX("value='%T'%s/>",
zValue ? zValue : "", isChecked ? " checked" : "");
CX("<label for='%s'>%h</label></span>", zLabelID, zLabel);
fossil_free(zLabelID);
}
/*
** Outputs a SELECT list from a compile-time list of integers.
** The vargs must be a list of (const char *, int) pairs, terminated
** with a single NULL. Each pair is interpreted as...
**
** If the (const char *) is NULL, it is the end of the list, else
** a new OPTION entry is created. If the string is empty, the
** label and value of the OPTION is the integer part of the pair.
** If the string is not empty, it becomes the label and the integer
** the value. If that value == selectedValue then that OPTION
** element gets the 'selected' attribute.
**
** Note that the pairs are not in (int, const char *) order because
** there is no well-known integer value which we can definitively use
** as a list terminator.
**
** zWrapperId is an optional ID value for the containing element (see
** below).
**
** zFieldName is the value of the form element's name attribute. Note
** that fossil prefers underscores over '-' for separators in form
** element names.
**
** zLabel is an optional string to use as a "label" for the element
** (see below).
**
** zTooltip is an optional value for the SELECT's title attribute.
**
** The structure of the emitted HTML is:
**
** <span class='input-with-label' title={{zToolTip}} id={{zWrapperId}}>
** <label for='SELECT ELEMENT ID'>{{zLabel}}</label>
** <select id='RANDOM ID' name={{zFieldName}}>...</select>
** </span>
**
** Example:
**
** style_select_list_int("my-grapes", "my_grapes", "Grapes",
** "Select the number of grapes",
** atoi(PD("my_field","0")),
** "", 1, "2", 2, "Three", 3,
** NULL);
**
*/
void style_select_list_int(const char * zWrapperId,
const char *zFieldName, const char * zLabel,
const char * zToolTip, int selectedVal,
... ){
char * zLabelID = style_next_input_id();
va_list vargs;
va_start(vargs,selectedVal);
CX("<span class='input-with-label'");
if(zToolTip && *zToolTip){
CX(" title='%h'",zToolTip);
}
if(zWrapperId && *zWrapperId){
CX(" id='%s'",zWrapperId);
}
CX(">");
if(zLabel && *zLabel){
CX("<label label='%s'>%h</label>", zLabelID, zLabel);
}
CX("<select name='%s' id='%s'>",zFieldName, zLabelID);
while(1){
const char * zOption = va_arg(vargs,char *);
int v;
if(NULL==zOption){
break;
}
v = va_arg(vargs,int);
CX("<option value='%d'%s>",
v, v==selectedVal ? " selected" : "");
if(*zOption){
CX("%s", zOption);
}else{
CX("%d",v);
}
CX("</option>\n");
}
CX("</select>\n");
CX("</span>\n");
va_end(vargs);
fossil_free(zLabelID);
}
/*
** The C-string counterpart of style_select_list_int(), this variant
** differs only in that its variadic arguments are C-strings in pairs
** of (optionLabel, optionValue). If a given optionLabel is an empty
** string, the corresponding optionValue is used as its label. If any
** given value matches zSelectedVal, that option gets preselected. If
** no options match zSelectedVal then the first entry is selected by
** default.
**
** Any of (zWrapperId, zTooltip, zSelectedVal) may be NULL or empty.
**
** Example:
**
** style_select_list_str("my-grapes", "my_grapes", "Grapes",
** "Select the number of grapes",
** P("my_field"),
** "1", "One", "2", "Two", "", "3",
** NULL);
*/
void style_select_list_str(const char * zWrapperId,
const char *zFieldName, const char * zLabel,
const char * zToolTip, char const * zSelectedVal,
... ){
char * zLabelID = style_next_input_id();
va_list vargs;
va_start(vargs,zSelectedVal);
if(!zSelectedVal){
zSelectedVal = __FILE__/*some string we'll never match*/;
}
CX("<span class='input-with-label'");
if(zToolTip && *zToolTip){
CX(" title='%h'",zToolTip);
}
if(zWrapperId && *zWrapperId){
CX(" id='%s'",zWrapperId);
}
CX(">");
if(zLabel && *zLabel){
CX("<label for='%s'>%h</label>", zLabelID, zLabel);
}
CX("<select name='%s' id='%s'>",zFieldName, zLabelID);
while(1){
const char * zLabel = va_arg(vargs,char *);
const char * zVal;
if(NULL==zLabel){
break;
}
zVal = va_arg(vargs,char *);
CX("<option value='%T'%s>",
zVal, 0==fossil_strcmp(zVal, zSelectedVal) ? " selected" : "");
if(*zLabel){
CX("%s", zLabel);
}else{
CX("%h",zVal);
}
CX("</option>\n");
}
CX("</select>\n");
CX("</span>\n");
va_end(vargs);
fossil_free(zLabelID);
}
/*
** The first time this is called, it emits code to install and
** bootstrap the window.fossil object, using the built-in file
** fossil.bootstrap.js (not to be confused with bootstrap.js).
**
** Subsequent calls are no-ops.
**
** If passed a true value, it emits the contents directly to the page
** output, else it emits a script tag with a src=builtin/... to load
** the script. It always outputs a small pre-bootstrap element in its
** own script tag to initialize parts which need C-runtime-level
** information, before loading the main fossil.bootstrap.js either
** inline or via a <script src=...>, as specified by the first
** argument.
*/
void style_emit_script_fossil_bootstrap(int asInline){
static int once = 0;
if(0==once++){
/* Set up the generic/app-agnostic parts of window.fossil
** which require C-level state... */
style_emit_script_tag(0,0);
CX("(function(){\n"
"if(!window.fossil) window.fossil={};\n"
"window.fossil.version = %!j;\n"
/* fossil.rootPath is the top-most CGI/server path,
** including a trailing slash. */
"window.fossil.rootPath = %!j+'/';\n",
get_version(), g.zTop);
/* fossil.config = {...various config-level options...} */
CX("window.fossil.config = {"
"hashDigits: %d, hashDigitsUrl: %d"
"};\n", hash_digits(0), hash_digits(1));
#if 0
/* Is it safe to emit the CSRF token here? Some pages add it
** as a hidden form field. */
if(g.zCsrfToken[0]!=0){
CX("window.fossil.csrfToken = %!j;\n",
g.zCsrfToken);
}
#endif
/*
** fossil.page holds info about the current page. This is also
** where the current page "should" store any of its own
** page-specific state, and it is reserved for that purpose.
*/
CX("window.fossil.page = {"
"name:\"%T\""
"};\n", g.zPath);
CX("})();\n");
/* The remaining fossil object bootstrap code is not dependent on
** C-runtime state... */
if(asInline){
CX("%s\n", builtin_text("fossil.bootstrap.js"));
}
style_emit_script_tag(1,0);
if(asInline==0){
style_emit_script_builtin(0, "fossil.bootstrap.js");
}
}
}
/*
** If passed 0 as its first argument, it emits a script opener tag
** with this request's nonce. If passed non-0 it emits a script
** closing tag. Mnemonic for remembering the order in which to pass 0
** or 1 as the first argument to this function: 0 comes before 1.
**
** If passed 0 as its first argument and a non-NULL/non-empty zSrc,
** then it instead emits:
**
** <script src='%R/{{zSrc}}'></script>
**
** zSrc is always assumed to be a repository-relative path without
** a leading slash, and has %R/ prepended to it.
**
** Meaning that no follow-up call to pass a non-0 first argument
** to close the tag. zSrc is ignored if the first argument is not
** 0.
*/
void style_emit_script_tag(int isCloser, const char * zSrc){
if(0==isCloser){
if(zSrc!=0 && zSrc[0]!=0){
CX("<script src='%R/%T'></script>\n", zSrc);
}else{
CX("<script nonce='%s'>", style_nonce());
}
}else{
CX("</script>\n");
}
}
/*
** Emits a script tag which uses content from a builtin script file.
**
** If asInline is true, it is emitted directly as an opening tag, the
** content of the zName builtin file, and a closing tag.
**
** If it is false, a script tag loading it via
** src=builtin/{{zName}}?cache=XYZ is emitted, where XYZ is a
** build-time-dependent cache-buster value.
*/
void style_emit_script_builtin(int asInline, char const * zName){
if(asInline){
style_emit_script_tag(0,0);
CX("%s", builtin_text(zName));
style_emit_script_tag(1,0);
}else{
char * zFullName = mprintf("builtin/%s",zName);
const char * zHash = fossil_exe_id();
CX("<script src='%R/%T?cache=%.8s'></script>\n",
zFullName, zHash);
fossil_free(zFullName);
}
}
/*
** The first time this is called it emits the JS code from the
** built-in file fossil.fossil.js. Subsequent calls are no-ops.
**
** If passed a true value, it emits the contents directly
** to the page output, else it emits a script tag with a
** src=builtin/... to load the script.
**
** Note that this code relies on that loaded via
** style_emit_script_fossil_bootstrap() but it does not call that
** routine.
*/
void style_emit_script_fetch(int asInline){
static int once = 0;
if(0==once++){
style_emit_script_builtin(asInline, "fossil.fetch.js");
}
}
/*
** The first time this is called it emits the JS code from the
** built-in file fossil.dom.js. Subsequent calls are no-ops.
**
** If passed a true value, it emits the contents directly
** to the page output, else it emits a script tag with a
** src=builtin/... to load the script.
**
** Note that this code relies on that loaded via
** style_emit_script_fossil_bootstrap(), but it does not call that
** routine.
*/
void style_emit_script_dom(int asInline){
static int once = 0;
if(0==once++){
style_emit_script_builtin(asInline, "fossil.dom.js");
}
}
/*
** The first time this is called, it calls style_emit_script_dom(),
** passing it the given asInline value, and emits the JS code from the
** built-in file fossil.tabs.js. Subsequent calls are no-ops.
**
** If passed a true value, it emits the contents directly
** to the page output, else it emits a script tag with a
** src=builtin/... to load the script.
*/
void style_emit_script_tabs(int asInline){
static int once = 0;
if(0==once++){
style_emit_script_dom(asInline);
style_emit_script_builtin(asInline, "fossil.tabs.js");
}
}
/*
** The first time this is called it emits the JS code from the
** built-in file fossil.confirmer.js. Subsequent calls are no-ops.
**
** If passed a true value, it emits the contents directly
** to the page output, else it emits a script tag with a
** src=builtin/... to load the script.
*/
void style_emit_script_confirmer(int asInline){
static int once = 0;
if(0==once++){
style_emit_script_builtin(asInline, "fossil.confirmer.js");
}
}
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
/** Styles specific to /fileedit... */
body.fileedit.waiting * {
/* Triggered during AJAX requests. */
cursor: wait;
}
body.fileedit .error {
padding: 0.25em;
}
body.fileedit .warning {
padding: 0.25em;
}
body.fileedit textarea {
font-family: monospace;
flex: 10 1 auto;
height: initial/*undo damage from some skins*/;
max-width: initial /* default.css pins it at 95% */;
}
body.fileedit textarea:focus,
body.fileedit input:focus{
/* The sudden appearance of a border (as in the Ardoise skin)
shifts the layout in unsightly ways */
border: initial;
}
body.fileedit fieldset {
margin: 0.5em 0 0.5em 0;
padding: 0.25em 0;
border-radius: 0.5em;
border-color: inherit;
border-width: 1px;
font-size: 90%;
overflow: auto;
}
body.fileedit fieldset > legend {
margin: 0 0 0 1em;
padding: 0 0.5em 0 0.5em;
}
body.fileedit fieldset > div {
margin: 0 0.25em 0 0.25em;
padding: 0;
overflow: auto;
}
body.fileedit fieldset > div > .input-with-label {
margin: 0.25em 0.5em;
}
body.fileedit fieldset > div > button {
margin: 0.25em 0.5em;
}
body.fileedit .fileedit-hint {
font-size: 80%;
opacity: 0.75;
}
body.fileedit .fileedit-error-report {
background: yellow;
color: darkred;
margin: 1em 0;
padding: 0.5em;
border-radius: 0.5em;
}
body.fileedit code.fileedit-manifest {
display: block;
height: 16em;
overflow: auto;
white-space: pre;
}
body.fileedit div.fileedit-preview {
margin: 0;
padding: 0;
}
body.fileedit #fileedit-tabs {
margin: 1em 0 0 0;
}
body.fileedit #fileedit-tab-preview-wrapper {
overflow: auto;
}
body.fileedit #fileedit-tab-fileselect > h1 {
margin: 0;
}
body.fileedit .fileedit-options.commit-message > div {
display: flex;
flex-direction: column;
align-items: stretch;
font-family: monospace;
}
body.fileedit .fileedit-options.commit-message > div > * {
margin: 0.25em;
}
body.fileedit #fileedit-commit-button-wrapper {
margin: 0.25em;
}
body.fileedit .tab-container > .tabs > .tab-panel > .fileedit-options {
margin-top: 0;
border: none;
border-radius: 0;
border-bottom-width: 1px;
border-bottom-style: dotted;
}
body.fileedit .tab-container > .tabs > .tab-panel > .fileedit-options > button {
vertical-align: middle;
margin: 0.5em;
}
body.fileedit .tab-container > .tabs > .tab-panel > .fileedit-options > input {
vertical-align: middle;
margin: 0.5em;
}
body.fileedit .tab-container > .tabs > .tab-panel > .fileedit-options > .input-with-label {
vertical-align: middle;
margin: 0.5em;
}
body.fileedit .fileedit-options > div > * {
margin: 0.25em;
}
body.fileedit .fileedit-options.flex-container.flex-row {
align-items: first baseline;
}
body.fileedit #fileedit-file-selector {
display: flex;
flex-direction: column;
align-content: flex-start;
border-color: inherit;
border-width: 1px;
border-style: inset;
border-radius: 0.5em;
padding: 0 0.25em;
margin: 0;
min-height: 12em;
}
body.fileedit #fileedit-file-selector select {
margin: 0 0 0.5em 0;
height: initial;
font-family: monospace;
}
body.fileedit select:focus {
border: none;
}
body.fileedit option:focus {
border: none;
}
body.fileedit #fileedit-file-selector > div {
padding: 0;
margin: 0;
}
body.fileedit #fileedit-file-selector > div > * {
margin: 0.25em 0.5em 0.25em 0;
}
body.fileedit #fileedit-stash-selector {
margin: 0.25em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: baseline;
}
body.fileedit #fileedit-stash-selector select {
margin: 0 1em;
height: initial;
font-family: monospace;
flex: 10 1 auto;
}
body.fileedit .tab-container > .tabs > .tab-panel {
display: flex;
flex-direction: column;
}
body.fileedit #fileedit-tab-diff-wrapper {
margin: 0;
padding: 0;
overflow: auto;
display: flex;
flex-direction: column;
align-items: stretch;
}
body.fileedit #fileedit-tab-diff-wrapper > div {
margin: 0.5em 0 0.5em 0;
}
body.fileedit table.sbsdiffcols {
/*width: initial;*/
}
body.fileedit #fileedit-tab-diff-wrapper > pre.udiff {
margin-top: 0;
}
body.fileedit .sbsdiffcols div.difftxtcol {
display: flex;
flex-direction: column;
align-items: stretch;
width: initial;
}
body.fileedit .sbsdiffcols div.difftxtcol pre {
max-width: 44em;
}
/**
Styles for fossil.tabs.js. As of this writing, currently
only used by /fileedit, but it is anticipated that these
will eventually need to migrate to default_css.txt for use
in the wiki and/or forum pages when implementing tabbed
ajax-based previews.
*/
.tab-container {
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
}
.tab-container > #fossil-status-bar {
margin-top: 0;
}
.tab-container > .tabs {
padding: 0.25em;
margin: 0;
display: flex;
flex-direction: column;
border-width: 1px;
border-style: outset;
border-color: inherit;
}
.tab-container > .tabs > .tab-panel {
align-self: stretch;
flex: 10 1 auto;
display: block;
}
.tab-container > .tab-bar {
display: flex;
flex-direction: row;
flex: 1 10 auto;
align-self: stretch;
flex-wrap: wrap;
}
.tab-container > .tab-bar > .tab-button {
display: inline-block;
border-radius: 0.5em 0.5em 0 0;
margin: 0 0.1em;
padding: 0.25em 0.75em;
align-self: baseline;
border-color: inherit;
border-width: 1px;
border-bottom: none;
border-top-style: inset;
border-left-style: inset;
border-right-style: inset;
cursor: pointer;
opacity: 0.6;
}
.tab-container > .tab-bar > .tab-button.selected {
text-decoration: underline;
opacity: 1.0;
border-top-style: outset;
border-left-style: outset;
border-right-style: outset;
}
/**
Styles developed for /fileedit but which have wider
applicability...
As of this writing, these are only used by /fileedit, but it is
anticipated that they will eventually need to be migrated over to
default_css.txt for use in other pages (specifically wiki and forum
page/post editors).
*/
.flex-container {
display: flex;
}
.flex-container.flex-row {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
.flex-container .flex-grow {
flex-grow: 10;
flex-shrink: 0;
}
.flex-container .flex-shrink {
flex-grow: 0;
flex-shrink: 10;
}
.flex-container.flex-row.stretch {
flex-wrap: wrap;
align-items: baseline;
justify-content: stretch;
margin: 0;
}
.flex-container.flex-column {
flex-direction: column;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
.flex-container.flex-column.stretch {
align-items: stretch;
margin: 0;
}
.flex-container.child-gap-small > * {
margin: 0.25em;
}
#fossil-status-bar {
display: block;
font-family: monospace;
border-width: 1px;
border-style: inset;
border-color: inherit;
min-height: 1.5em;
font-size: 1.2em;
padding: 0.2em;
margin: 0.25em 0;
flex: 0 0 auto;
}
.font-size-100 {
font-size: 100%;
}
.font-size-125 {
font-size: 125%;
}
.font-size-150 {
font-size: 150%;
}
.font-size-175 {
font-size: 175%;
}
.font-size-200 {
font-size: 200%;
}
/**
.input-with-label is intended to be a wrapper element which
contain both a LABEL tag and an INPUT or SELECT control.
The wrapper is "necessary", as opposed to placing the INPUT
in the LABEL, so that we can include multiple INPUT
elements (e.g. a set of radio buttons).
*/
.input-with-label {
border: 1px inset #808080;
border-radius: 0.5em;
padding: 0.25em 0.4em;
margin: 0 0.5em;
display: inline-block;
cursor: default;
}
.input-with-label > * {
vertical-align: middle;
}
.input-with-label > label {
display: inline; /* some skins set label display to block! */
}
.input-with-label > input {
margin: 0;
}
.input-with-label > button {
margin: 0;
}
.input-with-label > select {
margin: 0;
}
.input-with-label > input[type=text] {
margin: 0;
}
.input-with-label > textarea {
margin: 0;
}
.input-with-label > input[type=checkbox] {
vertical-align: sub;
}
.input-with-label > input[type=radio] {
vertical-align: sub;
}
.input-with-label > label {
font-weight: initial;
margin: 0 0.25em 0 0.25em;
vertical-align: middle;
}
|
| ︙ | ︙ | |||
19 20 21 22 23 24 25 | */ #include "config.h" #include "sync.h" #include <assert.h> /* ** If the repository is configured for autosyncing, then do an | | | > > > > > > > > > > > > < < < | < < < | | < < > | < < < < < < < < < < < < | | 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 |
*/
#include "config.h"
#include "sync.h"
#include <assert.h>
/*
** If the repository is configured for autosyncing, then do an
** autosync. Bits of the "flags" parameter determine details of behavior:
**
** SYNC_PULL Pull content from the server to the local repo
** SYNC_PUSH Push content from local up to the server
** SYNC_CKIN_LOCK Take a check-in lock on the current checkout.
** SYNC_VERBOSE Extra output
**
** Return the number of errors.
**
** The autosync setting can be a boolean or "pullonly". No autosync
** is attempted if the autosync setting is off, and only auto-pull is
** attempted if autosync is set to "pullonly". The check-in lock is
** not acquired unless autosync is set to "on".
**
** If dont-push setting is true, that is the same as having autosync
** set to pullonly.
*/
int autosync(int flags){
const char *zAutosync;
int rc;
int configSync = 0; /* configuration changes transferred */
if( g.fNoSync ){
return 0;
}
zAutosync = db_get("autosync", 0);
if( zAutosync==0 ) zAutosync = "on"; /* defend against misconfig */
if( is_false(zAutosync) ) return 0;
if( db_get_boolean("dont-push",0) || fossil_strncmp(zAutosync,"pull",4)==0 ){
flags &= ~SYNC_CKIN_LOCK;
if( flags & SYNC_PUSH ) return 0;
}
url_parse(0, URL_REMEMBER);
if( g.url.protocol==0 ) return 0;
if( g.url.user!=0 && g.url.passwd==0 ){
g.url.passwd = unobscure(db_get("last-sync-pw", 0));
g.url.flags |= URL_PROMPT_PW;
url_prompt_for_password();
}
g.zHttpAuth = get_httpauth();
url_remember();
if( find_option("verbose","v",0)!=0 ) flags |= SYNC_VERBOSE;
fossil_print("Autosync: %s\n", g.url.canonical);
url_enable_proxy("via proxy: ");
rc = client_sync(flags, configSync, 0, 0);
return rc;
}
/*
** This routine will try a number of times to perform autosync with a
** 0.5 second sleep between attempts.
**
|
| ︙ | ︙ | |||
119 120 121 122 123 124 125 | ** 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. */ static void process_sync_args( unsigned *pConfigFlags, /* Write configuration flags here */ unsigned *pSyncFlags, /* Write sync flags here */ | | > | 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
** 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.
*/
static void process_sync_args(
unsigned *pConfigFlags, /* Write configuration flags here */
unsigned *pSyncFlags, /* Write sync flags here */
int uvOnly, /* Special handling flags for UV sync */
unsigned urlOmitFlags /* Omit these URL flags */
){
const char *zUrl = 0;
const char *zHttpAuth = 0;
unsigned configSync = 0;
unsigned urlFlags = URL_REMEMBER | URL_PROMPT_PW;
int urlOptional = 0;
if( find_option("autourl",0,0)!=0 ){
|
| ︙ | ︙ | |||
155 156 157 158 159 160 161 |
*pSyncFlags |= SYNC_VERBOSE;
}
url_proxy_options();
clone_ssh_find_options();
if( !uvOnly ) db_find_and_open_repository(0, 0);
db_open_config(0, 1);
if( g.argc==2 ){
| | > | 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
*pSyncFlags |= SYNC_VERBOSE;
}
url_proxy_options();
clone_ssh_find_options();
if( !uvOnly ) db_find_and_open_repository(0, 0);
db_open_config(0, 1);
if( g.argc==2 ){
if( db_get_boolean("auto-shun",0) ) configSync = CONFIGSET_SHUN;
}else if( g.argc==3 ){
zUrl = g.argv[2];
}
if( ((*pSyncFlags) & (SYNC_PUSH|SYNC_PULL))==(SYNC_PUSH|SYNC_PULL)
&& db_get_boolean("uv-sync",0)
){
*pSyncFlags |= SYNC_UNVERSIONED;
}
urlFlags &= ~urlOmitFlags;
if( urlFlags & URL_REMEMBER ){
clone_ssh_db_set_options();
}
url_parse(zUrl, urlFlags);
remember_or_get_http_auth(zHttpAuth, urlFlags & URL_REMEMBER, zUrl);
url_remember();
if( g.url.protocol==0 ){
|
| ︙ | ︙ | |||
193 194 195 196 197 198 199 | } /* ** COMMAND: pull ** ** Usage: %fossil pull ?URL? ?options? ** | | | > | < > > > > > | | | | > | | | 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 |
}
/*
** COMMAND: pull
**
** Usage: %fossil pull ?URL? ?options?
**
** Pull all sharable changes from a remote repository into the local
** repository. Sharable changes include public check-ins, edits to
** wiki pages, tickets, and tech-notes, as well as forum content. Add
** the --private option to pull private branches. Use the
** "configuration pull" command to pull website configuration details.
**
** If URL is not specified, then the URL from the most recent clone, push,
** pull, remote-url, or sync command is used. See "fossil help clone" for
** details on the URL formats.
**
** Options:
**
** -B|--httpauth USER:PASS Credentials for the simple HTTP auth protocol,
** if required by the remote website
** --from-parent-project Pull content from the parent project
** --ipv4 Use only IPv4, not IPv6
** --once Do not remember URL for subsequent syncs
** --private Pull private branches too
** --project-code CODE Use CODE as the project code
** --proxy PROXY Use the specified HTTP proxy
** -R|--repository REPO Local repository to pull into
** --ssl-identity FILE Local SSL credentials, if requested by remote
** --ssh-command SSH Use SSH as the "ssh" command
** -v|--verbose Additional (debugging) output
** --verily Exchange extra information with the remote
** to ensure no content is overlooked
**
** See also: clone, config pull, push, remote-url, sync
*/
void pull_cmd(void){
unsigned configFlags = 0;
unsigned syncFlags = SYNC_PULL;
unsigned urlOmitFlags = 0;
const char *zAltPCode = find_option("project-code",0,1);
if( find_option("from-parent-project",0,0)!=0 ){
syncFlags |= SYNC_FROMPARENT;
}
if( zAltPCode ) urlOmitFlags = URL_REMEMBER;
process_sync_args(&configFlags, &syncFlags, 0, urlOmitFlags);
/* We should be done with options.. */
verify_all_options();
client_sync(syncFlags, configFlags, 0, zAltPCode);
}
/*
** COMMAND: push
**
** Usage: %fossil push ?URL? ?options?
**
** Push all sharable changes from the local repository to a remote
** repository. Sharable changes include public check-ins, edits to
** wiki pages, tickets, and tech-notes, as well as forum content. Use
** --private to also push private branches. Use the "configuration
** push" command to push website configuration details.
**
** If URL is not specified, then the URL from the most recent clone, push,
** pull, remote-url, or sync command is used. See "fossil help clone" for
** details on the URL formats.
**
** Options:
**
|
| ︙ | ︙ | |||
268 269 270 271 272 273 274 |
** to ensure no content is overlooked
**
** See also: clone, config push, pull, remote-url, sync
*/
void push_cmd(void){
unsigned configFlags = 0;
unsigned syncFlags = SYNC_PUSH;
| | | | 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 |
** to ensure no content is overlooked
**
** See also: clone, config push, pull, remote-url, sync
*/
void push_cmd(void){
unsigned configFlags = 0;
unsigned syncFlags = SYNC_PUSH;
process_sync_args(&configFlags, &syncFlags, 0, 0);
/* We should be done with options.. */
verify_all_options();
if( db_get_boolean("dont-push",0) ){
fossil_fatal("pushing is prohibited: the 'dont-push' option is set");
}
client_sync(syncFlags, 0, 0, 0);
}
/*
** COMMAND: sync
**
** Usage: %fossil sync ?URL? ?options?
|
| ︙ | ︙ | |||
317 318 319 320 321 322 323 |
*/
void sync_cmd(void){
unsigned configFlags = 0;
unsigned syncFlags = SYNC_PUSH|SYNC_PULL;
if( find_option("unversioned","u",0)!=0 ){
syncFlags |= SYNC_UNVERSIONED;
}
| | | | | | 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 |
*/
void sync_cmd(void){
unsigned configFlags = 0;
unsigned syncFlags = SYNC_PUSH|SYNC_PULL;
if( find_option("unversioned","u",0)!=0 ){
syncFlags |= SYNC_UNVERSIONED;
}
process_sync_args(&configFlags, &syncFlags, 0, 0);
/* We should be done with options.. */
verify_all_options();
if( db_get_boolean("dont-push",0) ) syncFlags &= ~SYNC_PUSH;
client_sync(syncFlags, configFlags, 0, 0);
if( (syncFlags & SYNC_PUSH)==0 ){
fossil_warning("pull only: the 'dont-push' option is set");
}
}
/*
** Handle the "fossil unversioned sync" and "fossil unversioned revert"
** commands.
*/
void sync_unversioned(unsigned syncFlags){
unsigned configFlags = 0;
(void)find_option("uv-noop",0,0);
process_sync_args(&configFlags, &syncFlags, 1, 0);
verify_all_options();
client_sync(syncFlags, 0, 0, 0);
}
/*
** COMMAND: remote-url
**
** Usage: %fossil remote-url ?URL|off?
**
|
| ︙ | ︙ |
| ︙ | ︙ | |||
218 219 220 221 222 223 224 |
}
}
if( zCol ){
db_multi_exec("UPDATE event SET \"%w\"=%Q WHERE objid=%d",
zCol, zValue, rid);
if( tagid==TAG_COMMENT ){
char *zCopy = mprintf("%s", zValue);
| | | 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
}
}
if( zCol ){
db_multi_exec("UPDATE event SET \"%w\"=%Q WHERE objid=%d",
zCol, zValue, rid);
if( tagid==TAG_COMMENT ){
char *zCopy = mprintf("%s", zValue);
backlink_extract(zCopy, 0, rid, BKLNK_COMMENT, mtime, 1);
free(zCopy);
}
}
if( tagid==TAG_DATE ){
db_multi_exec("UPDATE event "
" SET mtime=julianday(%Q),"
" omtime=coalesce(omtime,mtime)"
|
| ︙ | ︙ | |||
369 370 371 372 373 374 375 | /* ** COMMAND: tag ** ** Usage: %fossil tag SUBCOMMAND ... ** ** Run various subcommands to control tags and properties. ** | | | | | > | 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 | /* ** COMMAND: tag ** ** Usage: %fossil tag SUBCOMMAND ... ** ** Run various subcommands to control tags and properties. ** ** > fossil tag add ?OPTIONS? TAGNAME CHECK-IN ?VALUE? ** ** Add a new tag or property to CHECK-IN. The tag will ** be usable instead of a CHECK-IN in commands such as ** update and merge. If the --propagate flag is present, ** the tag value propagates to all descendants of CHECK-IN ** ** Options: ** --raw Raw tag name. ** --propagate Propagating tag. ** --date-override DATETIME Set date and time added. ** --user-override USER Name USER when adding the tag. ** --dryrun|-n Display the tag text, but do not ** actually insert it into the database. ** ** The --date-override and --user-override options support ** importing history from other SCM systems. DATETIME has ** the form 'YYYY-MMM-DD HH:MM:SS'. ** ** > fossil tag cancel ?--raw? TAGNAME CHECK-IN ** ** Remove the tag TAGNAME from CHECK-IN, and also remove ** the propagation of the tag to any descendants. Use the ** the --dryrun or -n options to see what would have happened. ** ** Options: ** --raw Raw tag name. ** --date-override DATETIME Set date and time deleted. ** --user-override USER Name USER when deleting the tag. ** --dryrun|-n Display the control artifact, but do ** not insert it into the database. ** ** > fossil tag find ?OPTIONS? TAGNAME ** ** List all objects that use TAGNAME. TYPE can be "ci" for ** check-ins or "e" for events. The limit option limits the number ** of results to the given value. ** ** Options: ** --raw Raw tag name. ** -t|--type TYPE One of "ci", or "e". ** -n|--limit N Limit to N results. ** ** > fossil tag list|ls ?OPTIONS? ?CHECK-IN? ** ** List all tags, or if CHECK-IN is supplied, list ** all tags and their values for CHECK-IN. The tagtype option ** takes one of: propagated, singleton, cancel. ** ** Options: ** --raw List tags raw names of tags ** --tagtype TYPE List only tags of type TYPE ** -v|--inverse Inverse the meaning of --tagtype TYPE. ** ** The option --raw allows the manipulation of all types of tags ** used for various internal purposes in fossil. It also shows ** "cancel" tags for the "find" and "list" subcommands. You should ** not use this option to make changes unless you are sure what ** you are doing. ** |
| ︙ | ︙ | |||
546 547 548 549 550 551 552 |
db_finalize(&q);
}
}
}else
if(( strncmp(g.argv[2],"list",n)==0 )||( strncmp(g.argv[2],"ls",n)==0 )){
Stmt q;
| | > | | | | | 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 |
db_finalize(&q);
}
}
}else
if(( strncmp(g.argv[2],"list",n)==0 )||( strncmp(g.argv[2],"ls",n)==0 )){
Stmt q;
const int fRaw = find_option("raw","",0)!=0;
const char *zTagType = find_option("tagtype","t",1);
const int fInverse = find_option("inverse","v",0)!=0;
int nTagType = fRaw ? -1 : 0;
if( zTagType!=0 ){
int l = strlen(zTagType);
if( strncmp(zTagType,"cancel",l)==0 ){
nTagType = 0;
}else if( strncmp(zTagType,"singleton",l)==0 ){
nTagType = 1;
}else if( strncmp(zTagType,"propagated",l)==0 ){
nTagType = 2;
}else{
fossil_fatal("unrecognized tag type");
}
}
if( g.argc==3 ){
db_prepare(&q,
"SELECT tagname FROM tag"
" WHERE EXISTS(SELECT 1 FROM tagxref"
" WHERE tagid=tag.tagid"
" AND tagtype%s%d)"
" ORDER BY tagname",
zTagType!=0 ? (fInverse!=0?"<>":"=") : ">"/*safe-for-%s*/,
nTagType
);
while( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q, 0);
if( fRaw ){
fossil_print("%s\n", zName);
}else if( strncmp(zName, "sym-", 4)==0 ){
fossil_print("%s\n", &zName[4]);
}
}
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%s%d"
" ORDER BY tagname",
rid,
zTagType!=0 ? (fInverse!=0?"<>":"=") : ">"/*safe-for-%s*/,
nTagType
);
while( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q, 0);
const char *zValue = db_column_text(&q, 1);
if( fRaw==0 ){
if( strncmp(zName, "sym-", 4)!=0 ) continue;
|
| ︙ | ︙ | |||
774 775 776 777 778 779 780 |
blob_reset(&sql);
/* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too
** many descenders to (off-screen) parents. */
tmFlags = TIMELINE_XMERGE | TIMELINE_FILLGAPS | TIMELINE_NOSCROLL;
if( PB("ng")==0 ) tmFlags |= TIMELINE_GRAPH;
if( PB("brbg")!=0 ) tmFlags |= TIMELINE_BRCOLOR;
if( PB("ubg")!=0 ) tmFlags |= TIMELINE_UCOLOR;
| | | 776 777 778 779 780 781 782 783 784 785 786 787 |
blob_reset(&sql);
/* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too
** many descenders to (off-screen) parents. */
tmFlags = TIMELINE_XMERGE | TIMELINE_FILLGAPS | TIMELINE_NOSCROLL;
if( PB("ng")==0 ) tmFlags |= TIMELINE_GRAPH;
if( PB("brbg")!=0 ) tmFlags |= TIMELINE_BRCOLOR;
if( PB("ubg")!=0 ) tmFlags |= TIMELINE_UCOLOR;
www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, 0);
db_finalize(&q);
@ <br />
style_footer();
}
|
| ︙ | ︙ | |||
441 442 443 444 445 446 447 |
}
sqlite3_open(":memory:", &g.db);
tar_begin(-1);
for(i=3; i<g.argc; i++){
Blob file;
blob_zero(&file);
blob_read_from_file(&file, g.argv[i], eFType);
| | | 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 |
}
sqlite3_open(":memory:", &g.db);
tar_begin(-1);
for(i=3; i<g.argc; i++){
Blob file;
blob_zero(&file);
blob_read_from_file(&file, g.argv[i], eFType);
tar_add_file(g.argv[i], &file, file_perm(0,eFType), file_mtime(0,eFType));
blob_reset(&file);
}
tar_finish(&zip);
blob_write_to_file(&zip, g.argv[2]);
}
/*
|
| ︙ | ︙ | |||
464 465 466 467 468 469 470 | ** If the RID object does not exist in the repository, then ** pTar is zeroed. ** ** zDir is a "synthetic" subdirectory which all files get ** added to as part of the tarball. It may be 0 or an empty string, in ** which case it is ignored. The intention is to create a tarball which ** politely expands into a subdir instead of filling your current dir | | | 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 | ** If the RID object does not exist in the repository, then ** pTar is zeroed. ** ** zDir is a "synthetic" subdirectory which all files get ** added to as part of the tarball. It may be 0 or an empty string, in ** which case it is ignored. The intention is to create a tarball which ** politely expands into a subdir instead of filling your current dir ** with source files. For example, pass an artifact hash or "ProjectName". ** */ void tarball_of_checkin( int rid, /* The RID of the checkin from which to form a tarball */ Blob *pTar, /* Write the tarball into this blob */ const char *zDir, /* Directory prefix for all file added to tarball */ Glob *pInclude, /* Only add files matching this pattern */ |
| ︙ | ︙ |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
/*
** Copyright (c) 2020 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
** 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.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to query terminal info
*/
#include "config.h"
#include "terminal.h"
#include <assert.h>
#ifdef _WIN32
# include <windows.h>
#else
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#endif
#if INTERFACE
/*
** Terminal size defined in terms of columns and lines.
*/
struct TerminalSize {
unsigned int nColumns; /* Number of characters on a single line */
unsigned int nLines; /* Number of lines */
};
#endif
/* Get the current terminal size by calling a system service.
**
** Return 1 on success. This sets the size parameters to the values retured by
** the system call, when such is supported; set the size to zero otherwise.
** Return 0 on the system service call failure.
**
** Under Linux/bash the size info is also available from env $LINES, $COLUMNS.
** Or it can be queried using tput `echo -e "lines\ncols"|tput -S`.
** Technically, this info could be cached, but then we'd need to handle
** SIGWINCH signal to requery the terminal on resize event.
*/
int terminal_get_size(TerminalSize *t){
memset(t, 0, sizeof(*t));
#if defined(TIOCGSIZE)
{
struct ttysize ts;
if( ioctl(STDIN_FILENO, TIOCGSIZE, &ts)!=-1 ){
t->nColumns = ts.ts_cols;
t->nLines = ts.ts_lines;
return 1;
}
return 0;
}
#elif defined(TIOCGWINSZ)
{
struct winsize ws;
if( ioctl(STDIN_FILENO, TIOCGWINSZ, &ws)!=-1 ){
t->nColumns = ws.ws_col;
t->nLines = ws.ws_row;
return 1;
}
return 0;
}
#elif defined(_WIN32)
{
CONSOLE_SCREEN_BUFFER_INFO csbi;
if( GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) ){
t->nColumns = csbi.srWindow.Right - csbi.srWindow.Left + 1;
t->nLines = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
return 1;
}
return 0;
}
#else
return 1;
#endif
}
/*
** Return the terminal's current width in columns when available, otherwise
** return the specified default value.
*/
unsigned int terminal_get_width(unsigned int nDefault){
TerminalSize ts;
if( terminal_get_size(&ts) ){
return ts.nColumns;
}
return nDefault;
}
/*
** Return the terminal's current height in lines when available, otherwise
** return the specified default value.
*/
unsigned int terminal_get_height(unsigned int nDefault){
TerminalSize ts;
if( terminal_get_size(&ts) ){
return ts.nLines;
}
return nDefault;
}
/*
** COMMAND: test-terminal-size
**
** Show the size of the terminal window from which the command is launched
** as two integers, the width in charaters and the height in lines.
**
** If the size cannot be determined, two zeros are shown.
*/
void test_terminal_size_cmd(void){
TerminalSize ts;
terminal_get_size(&ts);
fossil_print("%d %d\n", ts.nColumns, ts.nLines);
}
|
| ︙ | ︙ | |||
200 201 202 203 204 205 206 207 208 209 210 211 212 213 | int nBuf; int nBufAlloc; }; typedef struct Buffer Buffer; static int thBufferWrite(Th_Interp *interp, Buffer *, const char *, int); static void thBufferInit(Buffer *); static void thBufferFree(Th_Interp *interp, Buffer *); /* ** Append nAdd bytes of content copied from zAdd to the end of buffer ** pBuffer. If there is not enough space currently allocated, resize ** the allocation to make space. */ static int thBufferWrite( | > > > > > > > > | 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
int nBuf;
int nBufAlloc;
};
typedef struct Buffer Buffer;
static int thBufferWrite(Th_Interp *interp, Buffer *, const char *, int);
static void thBufferInit(Buffer *);
static void thBufferFree(Th_Interp *interp, Buffer *);
/*
** This version of memcpy() allows the first and second argument to
** be NULL as long as the number of bytes to copy is zero.
*/
static void *th_memcpy(void *dest, const void *src, size_t n){
return n>0 ? memcpy(dest,src,n) : dest;
}
/*
** Append nAdd bytes of content copied from zAdd to the end of buffer
** pBuffer. If there is not enough space currently allocated, resize
** the allocation to make space.
*/
static int thBufferWrite(
|
| ︙ | ︙ | |||
225 226 227 228 229 230 231 |
if( nReq>pBuffer->nBufAlloc ){
char *zNew;
int nNew;
nNew = nReq*2;
zNew = (char *)Th_Malloc(interp, nNew);
| | | | 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
if( nReq>pBuffer->nBufAlloc ){
char *zNew;
int nNew;
nNew = nReq*2;
zNew = (char *)Th_Malloc(interp, nNew);
th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf);
Th_Free(interp, pBuffer->zBuf);
pBuffer->nBufAlloc = nNew;
pBuffer->zBuf = zNew;
}
th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd);
pBuffer->nBuf += nAdd;
pBuffer->zBuf[pBuffer->nBuf] = '\0';
return TH_OK;
}
#define thBufferWrite(a,b,c,d) thBufferWrite(a,b,(const char *)c,d)
|
| ︙ | ︙ | |||
839 840 841 842 843 844 845 |
char **azElem = Th_Malloc(interp,
sizeof(char*) * nCount + /* azElem */
sizeof(int) * nCount + /* anElem */
strbuf.nBuf /* space for list element strings */
);
anElem = (int *)&azElem[nCount];
zElem = (char *)&anElem[nCount];
| | | | 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 |
char **azElem = Th_Malloc(interp,
sizeof(char*) * nCount + /* azElem */
sizeof(int) * nCount + /* anElem */
strbuf.nBuf /* space for list element strings */
);
anElem = (int *)&azElem[nCount];
zElem = (char *)&anElem[nCount];
th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf);
th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf);
for(i=0; i<nCount;i++){
azElem[i] = zElem;
zElem += (anElem[i] + 1);
}
*pazElem = azElem;
*panElem = anElem;
}
|
| ︙ | ︙ | |||
1291 1292 1293 1294 1295 1296 1297 |
Th_Free(interp, pValue->zData);
pValue->zData = 0;
}
assert(zValue || nValue==0);
pValue->zData = Th_Malloc(interp, nValue+1);
pValue->zData[nValue] = '\0';
| | | 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 |
Th_Free(interp, pValue->zData);
pValue->zData = 0;
}
assert(zValue || nValue==0);
pValue->zData = Th_Malloc(interp, nValue+1);
pValue->zData[nValue] = '\0';
th_memcpy(pValue->zData, zValue, nValue);
pValue->nData = nValue;
return TH_OK;
}
/*
** Create a variable link so that accessing variable (zLocal, nLocal) is
|
| ︙ | ︙ | |||
1410 1411 1412 1413 1414 1415 1416 |
*/
char *th_strdup(Th_Interp *interp, const char *z, int n){
char *zRes;
if( n<0 ){
n = th_strlen(z);
}
zRes = Th_Malloc(interp, n+1);
| | | 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 |
*/
char *th_strdup(Th_Interp *interp, const char *z, int n){
char *zRes;
if( n<0 ){
n = th_strlen(z);
}
zRes = Th_Malloc(interp, n+1);
th_memcpy(zRes, z, n);
zRes[n] = '\0';
return zRes;
}
/*
** Argument zPre must be a nul-terminated string. Set the interpreter
** result to a string containing the contents of zPre, followed by
|
| ︙ | ︙ | |||
1470 1471 1472 1473 1474 1475 1476 |
if( n<0 ){
n = th_strlen(z);
}
if( z && n>0 ){
char *zResult;
zResult = Th_Malloc(pInterp, n+1);
| | | 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 |
if( n<0 ){
n = th_strlen(z);
}
if( z && n>0 ){
char *zResult;
zResult = Th_Malloc(pInterp, n+1);
th_memcpy(zResult, z, n);
zResult[n] = '\0';
pInterp->zResult = zResult;
pInterp->nResult = n;
}
return TH_OK;
}
|
| ︙ | ︙ | |||
1773 1774 1775 1776 1777 1778 1779 |
if( nElem<0 ){
nElem = th_strlen(zElem);
}
nNew = *pnStr + nElem;
zNew = Th_Malloc(interp, nNew);
| | | | 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 |
if( nElem<0 ){
nElem = th_strlen(zElem);
}
nNew = *pnStr + nElem;
zNew = Th_Malloc(interp, nNew);
th_memcpy(zNew, *pzStr, *pnStr);
th_memcpy(&zNew[*pnStr], zElem, nElem);
Th_Free(interp, *pzStr);
*pzStr = zNew;
*pnStr = nNew;
return TH_OK;
}
|
| ︙ | ︙ | |||
2333 2334 2335 2336 2337 2338 2339 |
}
if( pNew->pOp || pNew->nValue ){
if( pNew->nValue ){
/* A terminal. Copy the string value. */
assert( !pNew->pOp );
pNew->zValue = Th_Malloc(interp, pNew->nValue);
| | | | 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 |
}
if( pNew->pOp || pNew->nValue ){
if( pNew->nValue ){
/* A terminal. Copy the string value. */
assert( !pNew->pOp );
pNew->zValue = Th_Malloc(interp, pNew->nValue);
th_memcpy(pNew->zValue, z, pNew->nValue);
i += pNew->nValue;
}
if( (nToken%16)==0 ){
/* Grow the apToken array. */
Expr **apTokenOld = apToken;
apToken = Th_Malloc(interp, sizeof(Expr *)*(nToken+16));
th_memcpy(apToken, apTokenOld, sizeof(Expr *)*nToken);
}
/* Put the new token at the end of the apToken array */
apToken[nToken] = pNew;
nToken++;
}else{
Th_Free(interp, pNew);
|
| ︙ | ︙ | |||
2511 2512 2513 2514 2515 2516 2517 |
pRet = 0;
}
if( op>0 && !pRet ){
pRet = (Th_HashEntry *)Th_Malloc(interp, sizeof(Th_HashEntry) + nKey);
pRet->zKey = (char *)&pRet[1];
pRet->nKey = nKey;
| | | 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 |
pRet = 0;
}
if( op>0 && !pRet ){
pRet = (Th_HashEntry *)Th_Malloc(interp, sizeof(Th_HashEntry) + nKey);
pRet->zKey = (char *)&pRet[1];
pRet->nKey = nKey;
th_memcpy(pRet->zKey, zKey, nKey);
pRet->pNext = pHash->a[iKey];
pHash->a[iKey] = pRet;
}
return pRet;
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
28 29 30 31 32 33 34 | ** interpreter creation and initialization process. */ #define TH_INIT_NONE ((u32)0x00000000) /* No flags. */ #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */ #define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */ #define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */ #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */ | > | | | | | 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 | ** interpreter creation and initialization process. */ #define TH_INIT_NONE ((u32)0x00000000) /* No flags. */ #define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */ #define TH_INIT_FORCE_TCL ((u32)0x00000002) /* Force Tcl to be enabled? */ #define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */ #define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */ #define TH_INIT_NO_REPO ((u32)0x00000010) /* Skip opening repository. */ #define TH_INIT_MASK ((u32)0x0000001F) /* All possible init flags. */ /* ** Useful and/or "well-known" combinations of flag values. */ #define TH_INIT_DEFAULT (TH_INIT_NONE) /* Default flags. */ #define TH_INIT_HOOK (TH_INIT_NEED_CONFIG | TH_INIT_FORCE_SETUP) #define TH_INIT_FORBID_MASK (TH_INIT_FORCE_TCL) /* Illegal from a script. */ #endif /* ** Flags set by functions in this file to keep track of integration state ** information. These flags should not be used outside of this file. */ #define TH_STATE_CONFIG ((u32)0x00000020) /* We opened the config. */ #define TH_STATE_REPOSITORY ((u32)0x00000040) /* We opened the repository. */ #define TH_STATE_MASK ((u32)0x00000060) /* All possible state flags. */ #ifdef FOSSIL_ENABLE_TH1_HOOKS /* ** These are the "well-known" TH1 error messages that occur when no hook is ** registered to be called prior to executing a command or processing a web ** page, respectively. If one of these errors is seen, it will not be sent ** or displayed to the remote user or local interactive user, respectively. |
| ︙ | ︙ | |||
125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
/*
** Checks if the TH1 trace log needs to be enabled. If so, prepares
** it for use.
*/
void Th_InitTraceLog(){
g.thTrace = find_option("th-trace", 0, 0)!=0;
if( g.thTrace ){
blob_zero(&g.thLog);
}
}
/*
** Prints the entire contents of the TH1 trace log to the standard
** output channel.
| > | 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
/*
** Checks if the TH1 trace log needs to be enabled. If so, prepares
** it for use.
*/
void Th_InitTraceLog(){
g.thTrace = find_option("th-trace", 0, 0)!=0;
if( g.thTrace ){
g.fAnyTrace = 1;
blob_zero(&g.thLog);
}
}
/*
** Prints the entire contents of the TH1 trace log to the standard
** output channel.
|
| ︙ | ︙ | |||
527 528 529 530 531 532 533 534 535 536 537 538 539 540 |
){
if( argc!=1 ){
return Th_WrongNumArgs(interp, "verifyCsrf");
}
login_verify_csrf_secret();
return TH_OK;
}
/*
** TH1 command: markdown STRING
**
** Renders the input string as markdown. The result is a two-element list.
** The first element is the text-only title string. The second element
** contains the body, rendered as HTML.
| > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
){
if( argc!=1 ){
return Th_WrongNumArgs(interp, "verifyCsrf");
}
login_verify_csrf_secret();
return TH_OK;
}
/*
** TH1 command: verifyLogin
**
** Returns non-zero if the specified user name and password represent a
** valid login for the repository.
*/
static int verifyLoginCmd(
Th_Interp *interp,
void *p,
int argc,
const char **argv,
int *argl
){
const char *zUser;
const char *zPass;
int uid;
if( argc!=3 ){
return Th_WrongNumArgs(interp, "verifyLogin userName password");
}
zUser = argv[1];
zPass = argv[2];
uid = login_search_uid(&zUser, zPass);
Th_SetResultInt(interp, uid!=0);
if( uid==0 ) sqlite3_sleep(100);
return TH_OK;
}
/*
** TH1 command: markdown STRING
**
** Renders the input string as markdown. The result is a two-element list.
** The first element is the text-only title string. The second element
** contains the body, rendered as HTML.
|
| ︙ | ︙ | |||
1471 1472 1473 1474 1475 1476 1477 |
int *argl
){
if( argc!=3 ){
return Th_WrongNumArgs(interp, "unversioned content FILENAME");
}
if( Th_IsRepositoryOpen() ){
Blob content;
| | | 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 |
int *argl
){
if( argc!=3 ){
return Th_WrongNumArgs(interp, "unversioned content FILENAME");
}
if( Th_IsRepositoryOpen() ){
Blob content;
if( unversioned_content(argv[2], &content)!=0 ){
Th_SetResult(interp, blob_str(&content), blob_size(&content));
blob_reset(&content);
return TH_OK;
}else{
return TH_ERROR;
}
}else{
|
| ︙ | ︙ | |||
1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 |
for(i=0; i<nCol; i++){
const char *zCol = sqlite3_column_name(pStmt, i);
int szCol = th_strlen(zCol);
const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
int szVal = sqlite3_column_bytes(pStmt, i);
Th_SetVar(interp, zCol, szCol, zVal, szVal);
}
res = Th_Eval(interp, 0, argv[2], argl[2]);
if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
}
rc = sqlite3_finalize(pStmt);
if( rc!=SQLITE_OK ){
if( noComplain ) return TH_OK;
Th_ErrorMessage(interp, "SQL error: ", sqlite3_errmsg(g.db), -1);
return TH_ERROR;
| > > > > > > > > > | 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 |
for(i=0; i<nCol; i++){
const char *zCol = sqlite3_column_name(pStmt, i);
int szCol = th_strlen(zCol);
const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
int szVal = sqlite3_column_bytes(pStmt, i);
Th_SetVar(interp, zCol, szCol, zVal, szVal);
}
if( g.thTrace ){
Th_Trace("query_eval {<pre>%#h</pre>}<br />\n", argl[2], argv[2]);
}
res = Th_Eval(interp, 0, argv[2], argl[2]);
if( g.thTrace ){
int nTrRes;
char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
Th_Trace("[query_eval] => %h {%#h}<br />\n",
Th_ReturnCodeName(res, 0), nTrRes, zTrRes);
}
if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
}
rc = sqlite3_finalize(pStmt);
if( rc!=SQLITE_OK ){
if( noComplain ) return TH_OK;
Th_ErrorMessage(interp, "SQL error: ", sqlite3_errmsg(g.db), -1);
return TH_ERROR;
|
| ︙ | ︙ | |||
2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 |
*/
void Th_FossilInit(u32 flags){
int wasInit = 0;
int needConfig = flags & TH_INIT_NEED_CONFIG;
int forceReset = flags & TH_INIT_FORCE_RESET;
int forceTcl = flags & TH_INIT_FORCE_TCL;
int forceSetup = flags & TH_INIT_FORCE_SETUP;
static unsigned int aFlags[] = { 0, 1, WIKI_LINKSONLY };
static int anonFlag = LOGIN_ANON;
static int zeroInt = 0;
static struct _Command {
const char *zName;
Th_CommandProc xProc;
void *pContext;
| > | 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 |
*/
void Th_FossilInit(u32 flags){
int wasInit = 0;
int needConfig = flags & TH_INIT_NEED_CONFIG;
int forceReset = flags & TH_INIT_FORCE_RESET;
int forceTcl = flags & TH_INIT_FORCE_TCL;
int forceSetup = flags & TH_INIT_FORCE_SETUP;
int noRepo = flags & TH_INIT_NO_REPO;
static unsigned int aFlags[] = { 0, 1, WIKI_LINKSONLY };
static int anonFlag = LOGIN_ANON;
static int zeroInt = 0;
static struct _Command {
const char *zName;
Th_CommandProc xProc;
void *pContext;
|
| ︙ | ︙ | |||
2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 |
{"styleScript", styleScriptCmd, 0},
{"tclReady", tclReadyCmd, 0},
{"trace", traceCmd, 0},
{"stime", stimeCmd, 0},
{"unversioned", unversionedCmd, 0},
{"utime", utimeCmd, 0},
{"verifyCsrf", verifyCsrfCmd, 0},
{"wiki", wikiCmd, (void*)&aFlags[0]},
{0, 0, 0}
};
if( g.thTrace ){
Th_Trace("th1-init 0x%x => 0x%x<br />\n", g.th1Flags, flags);
}
if( needConfig ){
/*
** This function uses several settings which may be defined in the
** repository and/or the global configuration. Since the caller
** passed a non-zero value for the needConfig parameter, make sure
** the necessary database connections are open prior to continuing.
*/
| > | | 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 |
{"styleScript", styleScriptCmd, 0},
{"tclReady", tclReadyCmd, 0},
{"trace", traceCmd, 0},
{"stime", stimeCmd, 0},
{"unversioned", unversionedCmd, 0},
{"utime", utimeCmd, 0},
{"verifyCsrf", verifyCsrfCmd, 0},
{"verifyLogin", verifyLoginCmd, 0},
{"wiki", wikiCmd, (void*)&aFlags[0]},
{0, 0, 0}
};
if( g.thTrace ){
Th_Trace("th1-init 0x%x => 0x%x<br />\n", g.th1Flags, flags);
}
if( needConfig ){
/*
** This function uses several settings which may be defined in the
** repository and/or the global configuration. Since the caller
** passed a non-zero value for the needConfig parameter, make sure
** the necessary database connections are open prior to continuing.
*/
Th_OpenConfig(!noRepo);
}
if( forceReset || forceTcl || g.interp==0 ){
int created = 0;
int i;
if( g.interp==0 ){
g.interp = Th_CreateInterp(&vtab);
created = 1;
|
| ︙ | ︙ | |||
2592 2593 2594 2595 2596 2597 2598 |
zResult = (char*)Th_GetResult(g.interp, &n);
sendText((char*)zResult, n, encode);
}else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
sendText(z, i, 0);
z += i+5;
for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
if( g.thTrace ){
| | > > > > > > | 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 |
zResult = (char*)Th_GetResult(g.interp, &n);
sendText((char*)zResult, n, encode);
}else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
sendText(z, i, 0);
z += i+5;
for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
if( g.thTrace ){
Th_Trace("render_eval {<pre>%#h</pre>}<br />\n", i, z);
}
rc = Th_Eval(g.interp, 0, (const char*)z, i);
if( g.thTrace ){
int nTrRes;
char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
Th_Trace("[render_eval] => %h {%#h}<br />\n",
Th_ReturnCodeName(rc, 0), nTrRes, zTrRes);
}
if( rc!=TH_OK ) break;
z += i;
if( z[0] ){ z += 6; }
i = 0;
}else{
i++;
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
39 40 41 42 43 44 45 |
/*
** Add an appropriate tag to the output if "rid" is unpublished (private)
*/
#define UNPUB_TAG "<em>(unpublished)</em>"
void tag_private_status(int rid){
if( content_is_private(rid) ){
| | | | | | 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 |
/*
** Add an appropriate tag to the output if "rid" is unpublished (private)
*/
#define UNPUB_TAG "<em>(unpublished)</em>"
void tag_private_status(int rid){
if( content_is_private(rid) ){
cgi_printf(" %s", UNPUB_TAG);
}
}
/*
** Generate a hyperlink to a version.
*/
void hyperlink_to_version(const char *zVerHash){
if( g.perm.Hyperlink ){
@ %z(chref("timelineHistLink","%R/info/%!S",zVerHash))[%S(zVerHash)]</a>
}else{
@ <span class="timelineHistDsp">[%S(zVerHash)]</span>
}
}
/*
** Generate a hyperlink to a date & time.
*/
void hyperlink_to_date(const char *zDate, const char *zSuffix){
|
| ︙ | ︙ | |||
99 100 101 102 103 104 105 | #define TIMELINE_GRAPH 0x0000008 /* Compute a graph */ #define TIMELINE_DISJOINT 0x0000010 /* Elements are not contiguous */ #define TIMELINE_FCHANGES 0x0000020 /* Detail file changes */ #define TIMELINE_BRCOLOR 0x0000040 /* Background color by branch name */ #define TIMELINE_UCOLOR 0x0000080 /* Background color by user */ #define TIMELINE_FRENAMES 0x0000100 /* Detail only file name changes */ #define TIMELINE_UNHIDE 0x0000200 /* Unhide check-ins with "hidden" tag */ | | > > > | 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 |
#define TIMELINE_GRAPH 0x0000008 /* Compute a graph */
#define TIMELINE_DISJOINT 0x0000010 /* Elements are not contiguous */
#define TIMELINE_FCHANGES 0x0000020 /* Detail file changes */
#define TIMELINE_BRCOLOR 0x0000040 /* Background color by branch name */
#define TIMELINE_UCOLOR 0x0000080 /* Background color by user */
#define TIMELINE_FRENAMES 0x0000100 /* Detail only file name changes */
#define TIMELINE_UNHIDE 0x0000200 /* Unhide check-ins with "hidden" tag */
#define TIMELINE_SHOWRID 0x0000400 /* Show RID values in addition to hashes */
#define TIMELINE_BISECT 0x0000800 /* Show supplimental bisect information */
#define TIMELINE_COMPACT 0x0001000 /* Use the "compact" view style */
#define TIMELINE_VERBOSE 0x0002000 /* Use the "detailed" view style */
#define TIMELINE_MODERN 0x0004000 /* Use the "modern" view style */
#define TIMELINE_COLUMNAR 0x0008000 /* Use the "columns" view style */
#define TIMELINE_CLASSIC 0x0010000 /* Use the "classic" view style */
#define TIMELINE_VIEWS 0x001f000 /* Mask for all of the view styles */
#define TIMELINE_NOSCROLL 0x0100000 /* Don't scroll to the selection */
#define TIMELINE_FILEDIFF 0x0200000 /* Show File differences, not ckin diffs */
#define TIMELINE_CHPICK 0x0400000 /* Show cherrypick merges */
#define TIMELINE_FILLGAPS 0x0800000 /* Dotted lines for missing nodes */
#define TIMELINE_XMERGE 0x1000000 /* Omit merges from off-graph nodes */
#define TIMELINE_NOTKT 0x2000000 /* Omit extra ticket classes */
#define TIMELINE_FORUMTXT 0x4000000 /* Render all forum messages */
#define TIMELINE_REFS 0x8000000 /* Output intended for References tab */
#endif
/*
** Hash a string and use the hash to determine a background color.
*/
char *hash_color(const char *z){
int i; /* Loop counter */
|
| ︙ | ︙ | |||
224 225 226 227 228 229 230 | } /* ** Output a timeline in the web format given a query. The query ** should return these columns: ** ** 0. rid | | | | > | 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 |
}
/*
** Output a timeline in the web format given a query. The query
** should return these columns:
**
** 0. rid
** 1. artifact hash
** 2. Date/Time
** 3. Comment string
** 4. User
** 5. True if is a leaf
** 6. background color
** 7. type ("ci", "w", "t", "e", "g", "f", "div")
** 8. list of symbolic tags.
** 9. tagid for ticket or wiki or event
** 10. Short comment to user for repeated tickets and wiki
*/
void www_print_timeline(
Stmt *pQuery, /* Query to implement the timeline */
int tmFlags, /* Flags controlling display behavior */
const char *zThisUser, /* Suppress links to this user */
const char *zThisTag, /* Suppress links to this tag */
const char *zLeftBranch, /* Strive to put this branch on the left margin */
int selectedRid, /* Highlight the line with this RID value or zero */
int secondRid, /* Secondary highlight (or zero) */
void (*xExtra)(int) /* Routine to call on each line of display */
){
int mxWikiLen;
Blob comment;
int prevTagid = 0;
int suppressCnt = 0;
char zPrevDate[20];
|
| ︙ | ︙ | |||
321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
const char *zBr = 0; /* Branch */
int commentColumn = 3; /* Column containing comment text */
int modPending; /* Pending moderation */
char *zDateLink; /* URL for the link on the timestamp */
int drawDetailEllipsis; /* True to show ellipsis in place of detail */
int gidx = 0; /* Graph row identifier */
int isSelectedOrCurrent = 0; /* True if current row is selected */
char zTime[20];
if( zDate==0 ){
zDate = "YYYY-MM-DD HH:MM:SS"; /* Something wrong with the repo */
}
modPending = moderation_pending(rid);
if( tagid ){
| > | 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
const char *zBr = 0; /* Branch */
int commentColumn = 3; /* Column containing comment text */
int modPending; /* Pending moderation */
char *zDateLink; /* URL for the link on the timestamp */
int drawDetailEllipsis; /* True to show ellipsis in place of detail */
int gidx = 0; /* Graph row identifier */
int isSelectedOrCurrent = 0; /* True if current row is selected */
const char *zExtraClass = "";
char zTime[20];
if( zDate==0 ){
zDate = "YYYY-MM-DD HH:MM:SS"; /* Something wrong with the repo */
}
modPending = moderation_pending(rid);
if( tagid ){
|
| ︙ | ︙ | |||
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 |
}else{
zTime[0] = 0;
}
pendingEndTr = 1;
if( rid==selectedRid ){
@ <tr class="timelineSelected">
isSelectedOrCurrent = 1;
}else if( rid==vid ){
@ <tr class="timelineCurrent">
isSelectedOrCurrent = 1;
}else {
@ <tr>
}
if( zType[0]=='e' && tagid ){
if( bTimestampLinksToInfo ){
char *zId;
zId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d",
tagid);
zDateLink = href("%R/technote/%s",zId);
free(zId);
}else{
| > > > > > > > > > > > > > > > > | | | 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 |
}else{
zTime[0] = 0;
}
pendingEndTr = 1;
if( rid==selectedRid ){
@ <tr class="timelineSelected">
isSelectedOrCurrent = 1;
}else if( rid==secondRid ){
@ <tr class="timelineSelected timelineSecondary">
isSelectedOrCurrent = 1;
}else if( rid==vid ){
@ <tr class="timelineCurrent">
isSelectedOrCurrent = 1;
}else {
@ <tr>
}
if( zType[0]=='t' && tagid && (tmFlags & TIMELINE_NOTKT)==0 ){
char *zTktid = db_text(0, "SELECT substr(tagname,5) FROM tag"
" WHERE tagid=%d", tagid);
if( zTktid ){
int isClosed = 0;
if( is_ticket(zTktid, &isClosed) && isClosed ){
zExtraClass = " tktTlClosed";
}else{
zExtraClass = " tktTlOpen";
}
fossil_free(zTktid);
}
}
if( zType[0]=='e' && tagid ){
if( bTimestampLinksToInfo ){
char *zId;
zId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d",
tagid);
zDateLink = href("%R/technote/%s",zId);
free(zId);
}else{
zDateLink = href("%R/timeline?c=%t&y=a",zDate);
}
}else if( zUuid ){
if( bTimestampLinksToInfo ){
zDateLink = chref("timelineHistLink", "%R/info/%!S", zUuid);
}else{
zDateLink = chref("timelineHistLink", "%R/timeline?c=%!S&y=a", zUuid);
}
}else{
zDateLink = mprintf("<a>");
}
@ <td class="timelineTime">%z(zDateLink)%s(zTime)</a></td>
@ <td class="timelineGraph">
if( tmFlags & TIMELINE_UCOLOR ) zBgClr = zUser ? hash_color(zUser) : 0;
|
| ︙ | ︙ | |||
480 481 482 483 484 485 486 |
** not actually draw anything on the graph, but it will set the
** background color of the timeline entry */
gidx = graph_add_row(pGraph, rid, -1, 0, 0, zBr, zBgClr, zUuid, 0);
@ <div id="m%d(gidx)" class="tl-nodemark"></div>
}
@</td>
if( !isSelectedOrCurrent ){
| | | | 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 |
** not actually draw anything on the graph, but it will set the
** background color of the timeline entry */
gidx = graph_add_row(pGraph, rid, -1, 0, 0, zBr, zBgClr, zUuid, 0);
@ <div id="m%d(gidx)" class="tl-nodemark"></div>
}
@</td>
if( !isSelectedOrCurrent ){
@ <td class="timeline%s(zStyle)Cell%s(zExtraClass)" id='mc%d(gidx)'>
}else{
@ <td class="timeline%s(zStyle)Cell%s(zExtraClass)">
}
if( pGraph && zType[0]!='c' ){
@ •
}
if( modPending ){
@ <span class="modpending">(Awaiting Moderator Approval)</span>
}
|
| ︙ | ︙ | |||
510 511 512 513 514 515 516 |
if( tmFlags & TIMELINE_COMPACT ){
@ <span class='timelineCompactComment' data-id='%d(rid)'>
}else{
@ <span class='timeline%s(zStyle)Comment'>
}
if( (tmFlags & TIMELINE_CLASSIC)!=0 ){
if( zType[0]=='c' ){
| | | | > > > > > > > > > > > > > > > | > | > > > | 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 |
if( tmFlags & TIMELINE_COMPACT ){
@ <span class='timelineCompactComment' data-id='%d(rid)'>
}else{
@ <span class='timeline%s(zStyle)Comment'>
}
if( (tmFlags & TIMELINE_CLASSIC)!=0 ){
if( zType[0]=='c' ){
hyperlink_to_version(zUuid);
if( isLeaf ){
if( db_exists("SELECT 1 FROM tagxref"
" WHERE rid=%d AND tagid=%d AND tagtype>0",
rid, TAG_CLOSED) ){
@ <span class="timelineLeaf">Closed-Leaf:</span>
}else{
@ <span class="timelineLeaf">Leaf:</span>
}
}
}else if( zType[0]=='e' && tagid ){
hyperlink_to_event_tagid(tagid<0?-tagid:tagid);
}else if( (tmFlags & TIMELINE_ARTID)!=0 ){
hyperlink_to_version(zUuid);
}
if( tmFlags & TIMELINE_SHOWRID ){
int srcId = delta_source_rid(rid);
if( srcId ){
@ (%d(rid)←%d(srcId))
}else{
@ (%d(rid))
}
}
}
if( zType[0]!='c' ){
/* Comments for anything other than a check-in are generated by
** "fossil rebuild" and expect to be rendered as text/x-fossil-wiki */
if( zType[0]=='w' ){
const char *zCom = blob_str(&comment);
char *zWiki;
wiki_hyperlink_override(zUuid);
if( (tmFlags & TIMELINE_REFS)!=0
&& (zWiki = strstr(zCom,"wiki"))!=0
){
/* The TIMELINE_REFS flag causes timeline comments of the
** form "Changes to wiki..." or "Added wiki" to be changed
** into just "Wiki..." */
Blob rcom;
blob_init(&rcom, 0, 0);
blob_appendf(&rcom, "W%s", zWiki+1);
wiki_convert(&rcom, 0, WIKI_INLINE);
blob_reset(&rcom);
}else{
wiki_convert(&comment, 0, WIKI_INLINE);
}
wiki_hyperlink_override(0);
}else{
wiki_convert(&comment, 0, WIKI_INLINE);
}
}else{
if( bCommentGitStyle ){
/* Truncate comment at first blank line */
int ii, jj;
int n = blob_size(&comment);
char *z = blob_str(&comment);
for(ii=0; ii<n; ii++){
|
| ︙ | ︙ | |||
578 579 580 581 582 583 584 |
*/
if( drawDetailEllipsis ){
@ <span class='timelineEllipsis' id='ellipsis-%d(rid)' \
@ data-id='%d(rid)'>...</span>
}
if( tmFlags & TIMELINE_COLUMNAR ){
if( !isSelectedOrCurrent ){
| | | | 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 |
*/
if( drawDetailEllipsis ){
@ <span class='timelineEllipsis' id='ellipsis-%d(rid)' \
@ data-id='%d(rid)'>...</span>
}
if( tmFlags & TIMELINE_COLUMNAR ){
if( !isSelectedOrCurrent ){
@ <td class="timelineDetailCell%s(zExtraClass)" id='md%d(gidx)'>
}else{
@ <td class="timelineDetailCell%s(zExtraClass)">
}
}
if( tmFlags & TIMELINE_COMPACT ){
cgi_printf("<span class='clutter' id='detail-%d'>",rid);
}
cgi_printf("<span class='timeline%sDetail'>", zStyle);
if( (tmFlags & (TIMELINE_CLASSIC|TIMELINE_VERBOSE|TIMELINE_COMPACT))!=0 ){
|
| ︙ | ︙ | |||
609 610 611 612 613 614 615 |
cgi_printf("check-in: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
}else if( zType[0]=='e' && tagid ){
cgi_printf("technote: ");
hyperlink_to_event_tagid(tagid<0?-tagid:tagid);
}else{
cgi_printf("artifact: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
}
| | > > > | > > > | 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 |
cgi_printf("check-in: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
}else if( zType[0]=='e' && tagid ){
cgi_printf("technote: ");
hyperlink_to_event_tagid(tagid<0?-tagid:tagid);
}else{
cgi_printf("artifact: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
}
}else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t'
|| zType[0]=='n' || zType[0]=='f'){
cgi_printf("artifact: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
}
if( g.perm.Hyperlink && fossil_strcmp(zDispUser, zThisUser)!=0 ){
char *zLink;
if( zType[0]!='f' || (tmFlags & TIMELINE_FORUMTXT)==0 ){
zLink = mprintf("%R/timeline?u=%h&c=%t&y=a", zDispUser, zDate);
}else{
zLink = mprintf("%R/timeline?u=%h&c=%t&y=a&vfx", zDispUser, zDate);
}
cgi_printf("user: %z%h</a>", href("%z",zLink), zDispUser);
}else{
cgi_printf("user: %h", zDispUser);
}
/* Generate the "tags: TAGLIST" at the end of the comment, together
** with hyperlinks to the tag list.
|
| ︙ | ︙ | |||
759 760 761 762 763 764 765 766 767 768 769 770 771 772 |
fossil_free(zA);
}
db_reset(&fchngQuery);
if( inUl ){
@ </ul>
}
}
}
if( suppressCnt ){
@ <span class="timelineDisabled">... %d(suppressCnt) similar
@ event%s(suppressCnt>1?"s":"") omitted.</span>
suppressCnt = 0;
}
if( pendingEndTr ){
| > > > > > > > > > > > > > > > > | 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 |
fossil_free(zA);
}
db_reset(&fchngQuery);
if( inUl ){
@ </ul>
}
}
/* Show the complete text of forum messages */
if( (tmFlags & (TIMELINE_FORUMTXT))!=0
&& zType[0]=='f' && g.perm.Hyperlink
&& (!content_is_private(rid) || g.perm.ModForum)
){
Manifest *pPost = manifest_get(rid, CFTYPE_FORUM, 0);
if( pPost ){
const char *zClass = "forumTimeline";
if( forum_rid_has_been_edited(rid) ){
zClass = "forumTimeline forumObs";
}
forum_render(0, pPost->zMimetype, pPost->zWiki, zClass, 1);
manifest_destroy(pPost);
}
}
}
if( suppressCnt ){
@ <span class="timelineDisabled">... %d(suppressCnt) similar
@ event%s(suppressCnt>1?"s":"") omitted.</span>
suppressCnt = 0;
}
if( pendingEndTr ){
|
| ︙ | ︙ | |||
927 928 929 930 931 932 933 |
* br: The branch to which the artifact belongs
*/
aiMap = pGraph->aiRailMap;
for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
int k = 0;
cgi_printf("{\"id\":%d,", pRow->idx);
cgi_printf("\"bg\":\"%s\",", pRow->zBgClr);
| | | | 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 |
* br: The branch to which the artifact belongs
*/
aiMap = pGraph->aiRailMap;
for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
int k = 0;
cgi_printf("{\"id\":%d,", pRow->idx);
cgi_printf("\"bg\":\"%s\",", pRow->zBgClr);
cgi_printf("\"r\":%d,", pRow->iRail>=0 ? aiMap[pRow->iRail] : -1);
if( pRow->bDescender ){
cgi_printf("\"d\":%d,", pRow->bDescender);
}
if( pRow->mergeOut>=0 ){
cgi_printf("\"mo\":%d,", aiMap[pRow->mergeOut]);
if( pRow->mergeUpto==0 ) pRow->mergeUpto = pRow->idx;
cgi_printf("\"mu\":%d,", pRow->mergeUpto);
if( pRow->cherrypickUpto>0 && pRow->cherrypickUpto<=pRow->mergeUpto ){
cgi_printf("\"cu\":%d,", pRow->cherrypickUpto);
}
}
if( pRow->isStepParent ){
cgi_printf("\"sb\":%d,", pRow->aiRiser[pRow->iRail]);
}else{
cgi_printf("\"u\":%d,", pRow->aiRiser[pRow->iRail]);
|
| ︙ | ︙ | |||
1070 1071 1072 1073 1074 1075 1076 |
const char *zDate;
if( z==0 ) return -1.0;
if( fossil_isdate(z) ){
mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", z);
if( mtime>0.0 ) return mtime;
}
zDate = fossil_expand_datetime(z, 1);
| | | > | | | > | 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 |
const char *zDate;
if( z==0 ) return -1.0;
if( fossil_isdate(z) ){
mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", z);
if( mtime>0.0 ) return mtime;
}
zDate = fossil_expand_datetime(z, 1);
if( zDate!=0 ){
mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())",
fossil_roundup_date(zDate));
if( mtime>0.0 ){
if( pzDisplay ) *pzDisplay = fossil_strdup(zDate);
return mtime;
}
}
rid = symbolic_name_to_rid(z, "*");
if( rid ){
mtime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
}else{
mtime = db_double(-1.0,
"SELECT max(event.mtime) FROM event, tag, tagxref"
|
| ︙ | ︙ | |||
1139 1140 1141 1142 1143 1144 1145 |
/*
** Add the select/option box to the timeline submenu that is used to
** set the y= parameter that determines which elements to display
** on the timeline.
*/
static void timeline_y_submenu(int isDisabled){
static int i = 0;
| | > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > < < < < < < < | | > | | | 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 |
/*
** Add the select/option box to the timeline submenu that is used to
** set the y= parameter that determines which elements to display
** on the timeline.
*/
static void timeline_y_submenu(int isDisabled){
static int i = 0;
static const char *az[16];
if( i==0 ){
az[0] = "all";
az[1] = "Any Type";
i = 2;
if( g.perm.Read ){
az[i++] = "ci";
az[i++] = "Check-ins";
az[i++] = "g";
az[i++] = "Tags";
}
if( g.perm.RdWiki ){
az[i++] = "e";
az[i++] = "Tech Notes";
}
if( g.perm.RdTkt ){
az[i++] = "t";
az[i++] = "Tickets";
az[i++] = "n";
az[i++] = "New Tickets";
}
if( g.perm.RdWiki ){
az[i++] = "w";
az[i++] = "Wiki";
}
if( g.perm.RdForum ){
az[i++] = "f";
az[i++] = "Forum";
}
assert( i<=count(az) );
}
if( i>2 ){
style_submenu_multichoice("y", i/2, az, isDisabled);
}
}
/*
** Return the default value for the "ss" cookie or query parameter.
** The "ss" cookie determines the graph style. See the
** timeline_view_styles[] global constant for a list of choices.
*/
const char *timeline_default_ss(void){
static const char *zSs = 0;
if( zSs==0 ) zSs = db_get("timeline-default-style","m");
return zSs;
}
/*
** Convert the current "ss" display preferences cookie into an
** appropriate TIMELINE_* flag
*/
int timeline_ss_cookie(void){
int tmFlags;
const char *v = cookie_value("ss",0);
if( v==0 ) v = timeline_default_ss();
switch( v[0] ){
case 'c': tmFlags = TIMELINE_COMPACT; break;
case 'v': tmFlags = TIMELINE_VERBOSE; break;
case 'j': tmFlags = TIMELINE_COLUMNAR; break;
case 'x': tmFlags = TIMELINE_CLASSIC; break;
default: tmFlags = TIMELINE_MODERN; break;
}
return tmFlags;
}
/* Available timeline display styles, together with their y= query
** parameter names.
*/
const char *const timeline_view_styles[] = {
"m", "Modern View",
"j", "Columnar View",
"c", "Compact View",
"v", "Verbose View",
"x", "Classic View",
};
#if INTERFACE
# define N_TIMELINE_VIEW_STYLE 5
#endif
/*
** Add the select/option box to the timeline submenu that is used to
** set the ss= parameter that determines the viewing mode.
**
** Return the TIMELINE_* value appropriate for the view-style.
*/
int timeline_ss_submenu(void){
cookie_link_parameter("ss","ss",timeline_default_ss());
style_submenu_multichoice("ss",
N_TIMELINE_VIEW_STYLE,
timeline_view_styles, 0);
return timeline_ss_cookie();
}
/*
** If the zChng string is not NULL, then it should be a comma-separated
** list of glob patterns for filenames. Add an term to the WHERE clause
** for the SQL statement under construction that excludes any check-in that
** does not modify one or more files matching the globs.
*/
static void addFileGlobExclusion(
const char *zChng, /* The filename GLOB list */
Blob *pSql /* The SELECT statement under construction */
){
if( zChng==0 || zChng[0]==0 ) return;
blob_append_sql(pSql," AND event.objid IN ("
"SELECT mlink.mid FROM mlink, filename"
" WHERE mlink.fnid=filename.fnid AND %s)",
glob_expr("filename.name", mprintf("\"%s\"", zChng)));
}
static void addFileGlobDescription(
const char *zChng, /* The filename GLOB list */
Blob *pDescription /* Result description */
){
if( zChng==0 || zChng[0]==0 ) return;
blob_appendf(pDescription, " that include changes to files matching '%h'",
|
| ︙ | ︙ | |||
1494 1495 1496 1497 1498 1499 1500 | /* ** WEBPAGE: timeline ** ** Query parameters: ** | | | | > | | | > | | > > | | | | | 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 | /* ** WEBPAGE: timeline ** ** Query parameters: ** ** a=TIMEORTAG Show events after TIMEORTAG ** b=TIMEORTAG Show events before TIMEORTAG ** c=TIMEORTAG Show events that happen "circa" TIMEORTAG ** cf=FILEHASH Show events around the time of the first use of ** the file with FILEHASH ** m=TIMEORTAG Highlight the event at TIMEORTAG ** n=COUNT Maximum number of events. "all" for no limit ** p=CHECKIN Parents and ancestors of CHECKIN ** bt=PRIOR ... going back to PRIOR ** d=CHECKIN Children and descendants of CHECKIN ** dp=CHECKIN The same as 'd=CHECKIN&p=CHECKIN' ** t=TAG Show only check-ins with the given TAG ** r=TAG Show check-ins related to TAG, equivalent to t=TAG&rel ** rel Show related check-ins as well as those matching t=TAG ** mionly Limit rel to show ancestors but not descendants ** nowiki Do not show wiki associated with branch or tag ** ms=MATCHSTYLE Set tag match style to EXACT, GLOB, LIKE, REGEXP ** u=USER Only show items associated with USER ** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', or 'all'. ** ss=VIEWSTYLE c: "Compact" v: "Verbose" m: "Modern" j: "Columnar" ** advm Use the "Advanced" or "Busy" menu design. ** ng No Graph. ** ncp Omit cherrypick merges ** nd Do not highlight the focus check-in ** nsm Omit the submenu ** v Show details of files changed ** vfx Show complete text of forum messages ** f=CHECKIN Show family (immediate parents and children) of CHECKIN ** from=CHECKIN Path from... ** to=CHECKIN ... to this ** shortest ... show only the shortest path ** rel ... also show related checkins ** uf=FILE_HASH Show only check-ins that contain the given file version ** chng=GLOBLIST Show only check-ins that involve changes to a file whose ** name matches one of the comma-separate GLOBLIST ** brbg Background color from branch name ** ubg Background color from user ** namechng Show only check-ins that have filename changes ** forks Show only forks and their children ** cherrypicks Show all cherrypicks ** ym=YYYY-MM Show only events for the given year/month ** yw=YYYY-WW Show only events for the given week of the given year |
| ︙ | ︙ | |||
1599 1600 1601 1602 1603 1604 1605 |
int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */
int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */
int pd_rid;
double rBefore, rAfter, rCirca; /* Boundary times */
const char *z;
char *zOlderButton = 0; /* URL for Older button at the bottom */
char *zNewerButton = 0; /* URL for Newer button at the top */
| | > | 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 |
int me_rid = name_to_typed_rid(P("me"),"ci"); /* me= for common ancestory */
int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */
int pd_rid;
double rBefore, rAfter, rCirca; /* Boundary times */
const char *z;
char *zOlderButton = 0; /* URL for Older button at the bottom */
char *zNewerButton = 0; /* URL for Newer button at the top */
int selectedRid = 0; /* Show a highlight on this RID */
int secondaryRid = 0; /* Show secondary highlight */
int disableY = 0; /* Disable type selector on submenu */
int advancedMenu = 0; /* Use the advanced menu design */
char *zPlural; /* Ending for plural forms */
int showCherrypicks = 1; /* True to show cherrypick merges */
/* Set number of rows to display */
cookie_read_parameter("n","n");
|
| ︙ | ︙ | |||
1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 |
nEntry = 10;
}
}
}else{
z = "50";
nEntry = 50;
}
cgi_replace_query_parameter("n",z);
cookie_write_parameter("n","n",0);
tmFlags |= timeline_ss_submenu();
cookie_link_parameter("advm","advm","0");
advancedMenu = atoi(PD("advm","0"));
/* Omit all cherry-pick merge lines if the "ncp" query parameter is
| > > | 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 |
nEntry = 10;
}
}
}else{
z = "50";
nEntry = 50;
}
secondaryRid = name_to_typed_rid(P("sel2"),"ci");
selectedRid = name_to_typed_rid(P("sel1"),"ci");
cgi_replace_query_parameter("n",z);
cookie_write_parameter("n","n",0);
tmFlags |= timeline_ss_submenu();
cookie_link_parameter("advm","advm","0");
advancedMenu = atoi(PD("advm","0"));
/* Omit all cherry-pick merge lines if the "ncp" query parameter is
|
| ︙ | ︙ | |||
1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 |
login_check_credentials();
if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum)
|| (bisectLocal && !g.perm.Setup)
){
login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
return;
}
cookie_read_parameter("y","y");
zType = P("y");
if( zType==0 ){
zType = g.perm.Read ? "ci" : "all";
cgi_set_parameter("y", zType);
}
if( zType[0]=='a' || zType[0]=='c' ){
| > | 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 |
login_check_credentials();
if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum)
|| (bisectLocal && !g.perm.Setup)
){
login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
return;
}
etag_check(ETAG_QUERY|ETAG_COOKIE|ETAG_DATA|ETAG_CONFIG, 0);
cookie_read_parameter("y","y");
zType = P("y");
if( zType==0 ){
zType = g.perm.Read ? "ci" : "all";
cgi_set_parameter("y", zType);
}
if( zType[0]=='a' || zType[0]=='c' ){
|
| ︙ | ︙ | |||
1673 1674 1675 1676 1677 1678 1679 |
" WHERE mlink.fid=(SELECT rid FROM blob WHERE uuid LIKE '%q%%')"
" AND event.objid=mlink.mid"
" ORDER BY event.mtime LIMIT 1",
P("cf")
);
}
| | > > > | 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 |
" WHERE mlink.fid=(SELECT rid FROM blob WHERE uuid LIKE '%q%%')"
" AND event.objid=mlink.mid"
" ORDER BY event.mtime LIMIT 1",
P("cf")
);
}
/* Convert r=TAG to t=TAG&rel in order to populate the UI style widgets. */
if( zBrName && !related ){
cgi_delete_query_parameter("r");
cgi_set_query_parameter("t", zBrName);
cgi_set_query_parameter("rel", "1");
zTagName = zBrName;
related = 1;
zType = "ci";
}
/* Ignore empty tag query strings. */
if( zTagName && !*zTagName ){
|
| ︙ | ︙ | |||
1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 |
}else if( fossil_stricmp(zMatchStyle, "like")==0 ){
matchStyle = MS_LIKE;
}else if( fossil_stricmp(zMatchStyle, "regexp")==0 ){
matchStyle = MS_REGEXP;
}else{
/* For exact maching, inhibit links to the selected tag. */
zThisTag = zTagName;
}
/* Display a checkbox to enable/disable display of related check-ins. */
if( advancedMenu ){
style_submenu_checkbox("rel", "Related", 0, 0);
}
| > | 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 |
}else if( fossil_stricmp(zMatchStyle, "like")==0 ){
matchStyle = MS_LIKE;
}else if( fossil_stricmp(zMatchStyle, "regexp")==0 ){
matchStyle = MS_REGEXP;
}else{
/* For exact maching, inhibit links to the selected tag. */
zThisTag = zTagName;
Th_Store("current_checkin", zTagName);
}
/* Display a checkbox to enable/disable display of related check-ins. */
if( advancedMenu ){
style_submenu_checkbox("rel", "Related", 0, 0);
}
|
| ︙ | ︙ | |||
1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 |
}
if( PB("ncp") ){
tmFlags &= ~TIMELINE_CHPICK;
}
if( PB("ng") || zSearch!=0 ){
tmFlags &= ~(TIMELINE_GRAPH|TIMELINE_CHPICK);
}
if( PB("brbg") ){
tmFlags |= TIMELINE_BRCOLOR;
}
if( PB("unhide") ){
tmFlags |= TIMELINE_UNHIDE;
}
if( PB("ubg") ){
| > > > | 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 |
}
if( PB("ncp") ){
tmFlags &= ~TIMELINE_CHPICK;
}
if( PB("ng") || zSearch!=0 ){
tmFlags &= ~(TIMELINE_GRAPH|TIMELINE_CHPICK);
}
if( PB("nsm") ){
style_submenu_enable(0);
}
if( PB("brbg") ){
tmFlags |= TIMELINE_BRCOLOR;
}
if( PB("unhide") ){
tmFlags |= TIMELINE_UNHIDE;
}
if( PB("ubg") ){
|
| ︙ | ︙ | |||
1777 1778 1779 1780 1781 1782 1783 |
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
" GROUP BY pid"
" HAVING count(*)>1;\n"
"INSERT OR IGNORE INTO rnfork(rid)"
" SELECT cid FROM plink\n"
" WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
| > > > > > > > | > > > > > > > | 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 |
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
" GROUP BY pid"
" HAVING count(*)>1;\n"
"INSERT OR IGNORE INTO rnfork(rid)"
" SELECT cid FROM plink\n"
" WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
" GROUP BY cid"
" HAVING count(*)>1;\n",
TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH
);
db_multi_exec(
"INSERT OR IGNORE INTO rnfork(rid)\n"
" SELECT cid FROM plink\n"
" WHERE pid IN rnfork"
" AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
" UNION "
" SELECT pid FROM plink\n"
" WHERE cid IN rnfork"
" AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n",
TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH
);
tmFlags |= TIMELINE_UNHIDE;
zType = "ci";
disableY = 1;
}
if( bisectLocal
|
| ︙ | ︙ | |||
1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 |
blob_zero(&sql);
blob_zero(&desc);
blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1);
blob_append(&sql, timeline_query_for_www(), -1);
if( PB("fc") || PB("v") || PB("detail") ){
tmFlags |= TIMELINE_FCHANGES;
}
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
blob_append_sql(&sql,
" AND NOT EXISTS(SELECT 1 FROM tagxref"
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n",
TAG_HIDDEN
);
}
| > > > | 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 |
blob_zero(&sql);
blob_zero(&desc);
blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1);
blob_append(&sql, timeline_query_for_www(), -1);
if( PB("fc") || PB("v") || PB("detail") ){
tmFlags |= TIMELINE_FCHANGES;
}
if( PB("vfx") ){
tmFlags |= TIMELINE_FORUMTXT;
}
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
blob_append_sql(&sql,
" AND NOT EXISTS(SELECT 1 FROM tagxref"
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n",
TAG_HIDDEN
);
}
|
| ︙ | ︙ | |||
1854 1855 1856 1857 1858 1859 1860 |
db_multi_exec(
"CREATE TABLE IF NOT EXISTS temp.pathnode(x INTEGER PRIMARY KEY);"
);
if( p ){
blob_init(&ins, 0, 0);
blob_append_sql(&ins, "INSERT INTO pathnode(x) VALUES(%d)", p->rid);
p = p->u.pTo;
| < < | | 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 |
db_multi_exec(
"CREATE TABLE IF NOT EXISTS temp.pathnode(x INTEGER PRIMARY KEY);"
);
if( p ){
blob_init(&ins, 0, 0);
blob_append_sql(&ins, "INSERT INTO pathnode(x) VALUES(%d)", p->rid);
p = p->u.pTo;
while( p ){
blob_append_sql(&ins, ",(%d)", p->rid);
p = p->u.pTo;
}
}
path_reset();
db_multi_exec("%s", blob_str(&ins)/*safe-for-%s*/);
blob_reset(&ins);
if( related || P("mionly") ){
db_multi_exec(
"CREATE TABLE IF NOT EXISTS temp.related(x INTEGER PRIMARY KEY);"
"INSERT OR IGNORE INTO related(x)"
" SELECT pid FROM plink WHERE cid IN pathnode AND NOT isprim;"
);
if( P("mionly")==0 ){
db_multi_exec(
|
| ︙ | ︙ | |||
1891 1892 1893 1894 1895 1896 1897 |
" SELECT childid FROM cherrypick WHERE parentid IN pathnode;"
);
}
}
db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related");
}
blob_append_sql(&sql, " AND event.objid IN pathnode");
| | > > > > > > > > | | > | | > > > > > > | > > > | | | > | > > > > > > | > > > > > > | 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 |
" SELECT childid FROM cherrypick WHERE parentid IN pathnode;"
);
}
}
db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related");
}
blob_append_sql(&sql, " AND event.objid IN pathnode");
if( zChng && zChng[0] ){
db_multi_exec(
"DELETE FROM pathnode "
" WHERE NOT EXISTS(SELECT 1 FROM mlink, filename"
" WHERE mlink.mid=x"
" AND mlink.fnid=filename.fnid AND %s)",
glob_expr("filename.name", zChng)
);
}
// tmFlags |= TIMELINE_DISJOINT;
tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
db_multi_exec("%s", blob_sql_text(&sql));
if( advancedMenu ){
style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
}
nNodeOnPath = db_int(0, "SELECT count(*) FROM temp.pathnode");
blob_appendf(&desc, "%d check-ins going from ", nNodeOnPath);
blob_appendf(&desc, "%z%h</a>", href("%R/info/%h", zFrom), zFrom);
blob_append(&desc, " to ", -1);
blob_appendf(&desc, "%z%h</a>", href("%R/info/%h",zTo), zTo);
if( related ){
int nRelated = db_int(0, "SELECT count(*) FROM timeline") - nNodeOnPath;
if( nRelated>0 ){
blob_appendf(&desc, " and %d related check-in%s", nRelated,
nRelated>1 ? "s" : "");
}
}
addFileGlobDescription(zChng, &desc);
}else if( (p_rid || d_rid) && g.perm.Read && zTagSql==0 ){
/* If p= or d= is present, ignore all other parameters other than n= */
char *zUuid;
const char *zCiName;
int np, nd;
const char *zBackTo = 0;
int ridBackTo = 0;
tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
if( p_rid && d_rid ){
if( p_rid!=d_rid ) p_rid = d_rid;
if( P("n")==0 ) nEntry = 10;
}
db_multi_exec(
"CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
);
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d",
p_rid ? p_rid : d_rid);
zCiName = pd_rid ? P("pd") : p_rid ? P("p") : P("d");
if( zCiName==0 ) zCiName = zUuid;
blob_append_sql(&sql, " AND event.objid IN ok");
nd = 0;
if( d_rid ){
compute_descendants(d_rid, nEntry==0 ? 0 : nEntry+1);
nd = db_int(0, "SELECT count(*)-1 FROM ok");
if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql));
if( nd>0 || p_rid==0 ){
blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s");
}
if( useDividers ) selectedRid = d_rid;
db_multi_exec("DELETE FROM ok");
}
if( p_rid ){
zBackTo = P("bt");
ridBackTo = zBackTo ? name_to_typed_rid(zBackTo,"ci") : 0;
compute_ancestors(p_rid, nEntry==0 ? 0 : nEntry+1, 0, ridBackTo);
np = db_int(0, "SELECT count(*)-1 FROM ok");
if( np>0 || nd==0 ){
if( nd>0 ) blob_appendf(&desc, " and ");
blob_appendf(&desc, "%d ancestor%s", np, (1==np)?"":"s");
db_multi_exec("%s", blob_sql_text(&sql));
}
if( useDividers ) selectedRid = p_rid;
}
blob_appendf(&desc, " of %z%h</a>",
href("%R/info?name=%h", zCiName), zCiName);
if( ridBackTo ){
if( np==0 ){
blob_reset(&desc);
blob_appendf(&desc,
"Check-in %z%h</a> only (%z%h</a> is not an ancestor)",
href("%R/info?name=%h",zCiName), zCiName,
href("%R/info?name=%h",zBackTo), zBackTo);
}else{
blob_appendf(&desc, " back to %z%h</a>",
href("%R/info?name=%h",zBackTo), zBackTo);
}
}
if( d_rid ){
if( p_rid ){
/* If both p= and d= are set, we don't have the uuid of d yet. */
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid);
}
}
if( advancedMenu ){
|
| ︙ | ︙ | |||
2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 |
if( zTagSql ){
db_multi_exec(
"CREATE TEMP TABLE selected_nodes(rid INTEGER PRIMARY KEY);"
"INSERT OR IGNORE INTO selected_nodes"
" SELECT tagxref.rid FROM tagxref NATURAL JOIN tag"
" WHERE %s AND tagtype>0", zTagSql/*safe-for-%s*/
);
if( !related ){
blob_append_sql(&cond, " AND blob.rid IN selected_nodes");
}else{
db_multi_exec(
"CREATE TEMP TABLE related_nodes(rid INTEGER PRIMARY KEY);"
"INSERT INTO related_nodes SELECT rid FROM selected_nodes;"
);
| > > > > > > > | 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 |
if( zTagSql ){
db_multi_exec(
"CREATE TEMP TABLE selected_nodes(rid INTEGER PRIMARY KEY);"
"INSERT OR IGNORE INTO selected_nodes"
" SELECT tagxref.rid FROM tagxref NATURAL JOIN tag"
" WHERE %s AND tagtype>0", zTagSql/*safe-for-%s*/
);
if( zMark ){
/* If the t=release option is used with m=UUID, then also
** include the UUID check-in in the display list */
int ridMark = name_to_rid(zMark);
db_multi_exec(
"INSERT OR IGNORE INTO selected_nodes(rid) VALUES(%d)", ridMark);
}
if( !related ){
blob_append_sql(&cond, " AND blob.rid IN selected_nodes");
}else{
db_multi_exec(
"CREATE TEMP TABLE related_nodes(rid INTEGER PRIMARY KEY);"
"INSERT INTO related_nodes SELECT rid FROM selected_nodes;"
);
|
| ︙ | ︙ | |||
2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 |
TAG_HIDDEN
);
}
}
}
if( (zType[0]=='w' && !g.perm.RdWiki)
|| (zType[0]=='t' && !g.perm.RdTkt)
|| (zType[0]=='e' && !g.perm.RdWiki)
|| (zType[0]=='c' && !g.perm.Read)
|| (zType[0]=='g' && !g.perm.Read)
|| (zType[0]=='f' && !g.perm.RdForum)
){
zType = "all";
}
| > | 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 |
TAG_HIDDEN
);
}
}
}
if( (zType[0]=='w' && !g.perm.RdWiki)
|| (zType[0]=='t' && !g.perm.RdTkt)
|| (zType[0]=='n' && !g.perm.RdTkt)
|| (zType[0]=='e' && !g.perm.RdWiki)
|| (zType[0]=='c' && !g.perm.Read)
|| (zType[0]=='g' && !g.perm.Read)
|| (zType[0]=='f' && !g.perm.RdForum)
){
zType = "all";
}
|
| ︙ | ︙ | |||
2158 2159 2160 2161 2162 2163 2164 |
if( g.perm.RdForum ){
blob_append_sql(&cond, "%c'f'", cSep);
cSep = ',';
}
blob_append_sql(&cond, ")");
}
}else{ /* zType!="all" */
| > > > > | > > > | 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 |
if( g.perm.RdForum ){
blob_append_sql(&cond, "%c'f'", cSep);
cSep = ',';
}
blob_append_sql(&cond, ")");
}
}else{ /* zType!="all" */
if( zType[0]=='n' ){
blob_append_sql(&cond,
" AND event.type='t' AND event.comment GLOB 'New ticket*'");
}else{
blob_append_sql(&cond, " AND event.type=%Q", zType);
}
if( zType[0]=='c' ){
zEType = "check-in";
}else if( zType[0]=='w' ){
zEType = "wiki";
}else if( zType[0]=='t' ){
zEType = "ticket change";
}else if( zType[0]=='n' ){
zEType = "new tickets";
}else if( zType[0]=='e' ){
zEType = "technical note";
}else if( zType[0]=='g' ){
zEType = "tag";
}else if( zType[0]=='f' ){
zEType = "forum post";
}
|
| ︙ | ︙ | |||
2216 2217 2218 2219 2220 2221 2222 |
rBefore+ONE_SECOND);
zCirca = 0;
url_add_parameter(&url, "c", 0);
}else if( rCirca>0.0 ){
Blob sql2;
blob_init(&sql2, blob_sql_text(&sql), -1);
blob_append_sql(&sql2,
| | < > > > | | 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 |
rBefore+ONE_SECOND);
zCirca = 0;
url_add_parameter(&url, "c", 0);
}else if( rCirca>0.0 ){
Blob sql2;
blob_init(&sql2, blob_sql_text(&sql), -1);
blob_append_sql(&sql2,
" AND event.mtime>=%f ORDER BY event.mtime ASC", rCirca);
if( nEntry>0 ){
blob_append_sql(&sql2," LIMIT %d", (nEntry+1)/2);
}
if( PB("showsql") ){
@ <pre>%h(blob_sql_text(&sql2))</pre>
}
db_multi_exec("%s", blob_sql_text(&sql2));
if( nEntry>0 ){
nEntry -= db_int(0,"select count(*) from timeline");
}
blob_reset(&sql2);
blob_append_sql(&sql,
" AND event.mtime<=%f ORDER BY event.mtime DESC",
rCirca
);
if( zMark==0 ) zMark = zCirca;
}else{
blob_append_sql(&sql, " ORDER BY event.mtime DESC");
}
if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry);
|
| ︙ | ︙ | |||
2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 |
}else{
if( related ){
blob_appendf(&desc, " related to tags matching %h", zMatchDesc);
}else{
blob_appendf(&desc, " with tags matching %h", zMatchDesc);
}
}
tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
}
addFileGlobDescription(zChng, &desc);
if( rAfter>0.0 ){
if( rBefore>0.0 ){
blob_appendf(&desc, " occurring between %h and %h.<br />",
zAfter, zBefore);
| > > > | 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 |
}else{
if( related ){
blob_appendf(&desc, " related to tags matching %h", zMatchDesc);
}else{
blob_appendf(&desc, " with tags matching %h", zMatchDesc);
}
}
if( zMark ){
blob_appendf(&desc," plus check-in \"%h\"", zMark);
}
tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
}
addFileGlobDescription(zChng, &desc);
if( rAfter>0.0 ){
if( rBefore>0.0 ){
blob_appendf(&desc, " occurring between %h and %h.<br />",
zAfter, zBefore);
|
| ︙ | ︙ | |||
2385 2386 2387 2388 2389 2390 2391 |
if( r>0.0 ) selectedRid = timeline_add_divider(r);
}
blob_zero(&sql);
db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/");
if( fossil_islower(desc.aData[0]) ){
desc.aData[0] = fossil_toupper(desc.aData[0]);
}
| | | | | | > > > > | 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 |
if( r>0.0 ) selectedRid = timeline_add_divider(r);
}
blob_zero(&sql);
db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/");
if( fossil_islower(desc.aData[0]) ){
desc.aData[0] = fossil_toupper(desc.aData[0]);
}
if( zBrName ){
if( !PB("nowiki")
&& wiki_render_associated("branch", zBrName, WIKIASSOC_ALL)
){
@ <div class="section">%b(&desc)</div>
} else{
@ <h2>%b(&desc)</h2>
}
style_submenu_element("Diff", "%R/vdiff?branch=%T", zBrName);
}else
if( zTagName
&& matchStyle==MS_EXACT
&& zBrName==0
&& !PB("nowiki")
&& wiki_render_associated("tag", zTagName, WIKIASSOC_ALL)
){
|
| ︙ | ︙ | |||
2412 2413 2414 2415 2416 2417 2418 |
@ <p class="generalError">%h(zError)</p>
}
if( zNewerButton ){
@ %z(chref("button","%z",zNewerButton))More ↑</a>
}
www_print_timeline(&q, tmFlags, zThisUser, zThisTag, zBrName,
| | | 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 |
@ <p class="generalError">%h(zError)</p>
}
if( zNewerButton ){
@ %z(chref("button","%z",zNewerButton))More ↑</a>
}
www_print_timeline(&q, tmFlags, zThisUser, zThisTag, zBrName,
selectedRid, secondaryRid, 0);
db_finalize(&q);
if( zOlderButton ){
@ %z(chref("button","%z",zOlderButton))More ↓</a>
}
style_footer();
}
|
| ︙ | ︙ | |||
2796 2797 2798 2799 2800 2801 2802 |
/* When zFilePattern is specified, compute complete ancestry;
* limit later at print_timeline() */
if( mode==TIMELINE_MODE_CHILDREN || mode==TIMELINE_MODE_PARENTS ){
db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)");
if( mode==TIMELINE_MODE_CHILDREN ){
compute_descendants(objid, (zFilePattern ? 0 : n));
}else{
| | | 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 |
/* When zFilePattern is specified, compute complete ancestry;
* limit later at print_timeline() */
if( mode==TIMELINE_MODE_CHILDREN || mode==TIMELINE_MODE_PARENTS ){
db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)");
if( mode==TIMELINE_MODE_CHILDREN ){
compute_descendants(objid, (zFilePattern ? 0 : n));
}else{
compute_ancestors(objid, (zFilePattern ? 0 : n), 0, 0);
}
blob_append_sql(&sql, "\n AND blob.rid IN ok");
}
if( zType && (zType[0]!='a') ){
blob_append_sql(&sql, "\n AND event.type=%Q ", zType);
}
if( zFilePattern ){
|
| ︙ | ︙ | |||
2876 2877 2878 2879 2880 2881 2882 |
}
@ <h1>This Day In History For %h(zToday)</h1>
z = db_text(0, "SELECT date(%Q,'-1 day')", zToday);
style_submenu_element("Yesterday", "%R/thisdayinhistory?today=%t", z);
z = db_text(0, "SELECT date(%Q,'+1 day')", zToday);
style_submenu_element("Tomorrow", "%R/thisdayinhistory?today=%t", z);
zStartOfProject = db_text(0,
| | | 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 |
}
@ <h1>This Day In History For %h(zToday)</h1>
z = db_text(0, "SELECT date(%Q,'-1 day')", zToday);
style_submenu_element("Yesterday", "%R/thisdayinhistory?today=%t", z);
z = db_text(0, "SELECT date(%Q,'+1 day')", zToday);
style_submenu_element("Tomorrow", "%R/thisdayinhistory?today=%t", z);
zStartOfProject = db_text(0,
"SELECT datetime(min(mtime),toLocal(),'startofday') FROM event;"
);
timeline_temp_table();
db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/");
for(i=0; i<sizeof(aYearsAgo)/sizeof(aYearsAgo[0]); i++){
int iAgo = aYearsAgo[i];
char *zThis = db_text(0, "SELECT date(%Q,'-%d years')", zToday, iAgo);
Blob sql;
|
| ︙ | ︙ | |||
2905 2906 2907 2908 2909 2910 2911 |
continue;
}
zId = db_text(0, "SELECT timestamp FROM timeline"
" ORDER BY sortby DESC LIMIT 1");
@ <h2>%d(iAgo) Year%s(iAgo>1?"s":"") Ago
@ <small>%z(href("%R/timeline?c=%t",zId))(more context)</a>\
@ </small></h2>
| | | 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 |
continue;
}
zId = db_text(0, "SELECT timestamp FROM timeline"
" ORDER BY sortby DESC LIMIT 1");
@ <h2>%d(iAgo) Year%s(iAgo>1?"s":"") Ago
@ <small>%z(href("%R/timeline?c=%t",zId))(more context)</a>\
@ </small></h2>
www_print_timeline(&q, TIMELINE_GRAPH, 0, 0, 0, 0, 0, 0);
}
db_finalize(&q);
style_footer();
}
/*
|
| ︙ | ︙ |
| ︙ | ︙ | |||
192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
** Return the new rowid of the TICKET table entry.
*/
static int ticket_insert(const Manifest *p, int rid, int tktid){
Blob sql1, sql2, sql3;
Stmt q;
int i, j;
char *aUsed;
if( tktid==0 ){
db_multi_exec("INSERT INTO ticket(tkt_uuid, tkt_mtime) "
"VALUES(%Q, 0)", p->zTicketUuid);
tktid = db_last_insert_rowid();
}
blob_zero(&sql1);
| > | 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
** Return the new rowid of the TICKET table entry.
*/
static int ticket_insert(const Manifest *p, int rid, int tktid){
Blob sql1, sql2, sql3;
Stmt q;
int i, j;
char *aUsed;
const char *zMimetype = 0;
if( tktid==0 ){
db_multi_exec("INSERT INTO ticket(tkt_uuid, tkt_mtime) "
"VALUES(%Q, 0)", p->zTicketUuid);
tktid = db_last_insert_rowid();
}
blob_zero(&sql1);
|
| ︙ | ︙ | |||
231 232 233 234 235 236 237 |
const char *zUsedByName = zName;
if( zUsedByName[0]=='+' ){
zUsedByName++;
}
blob_append_sql(&sql2, ",\"%w\"", zUsedByName);
blob_append_sql(&sql3, ",%Q", p->aField[i].zValue);
}
| > > > > | > > > > > > | | 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 |
const char *zUsedByName = zName;
if( zUsedByName[0]=='+' ){
zUsedByName++;
}
blob_append_sql(&sql2, ",\"%w\"", zUsedByName);
blob_append_sql(&sql3, ",%Q", p->aField[i].zValue);
}
if( strcmp(zBaseName,"mimetype")==0 ){
zMimetype = p->aField[i].zValue;
}
}
if( rid>0 ){
for(i=0; i<p->nField; i++){
const char *zName = p->aField[i].zName;
const char *zBaseName = zName[0]=='+' ? zName+1 : zName;
j = fieldId(zBaseName);
if( j<0 ) continue;
backlink_extract(p->aField[i].zValue, zMimetype, rid, BKLNK_TICKET,
p->rDate, i==0);
}
}
blob_append_sql(&sql1, " WHERE tkt_id=%d", tktid);
db_prepare(&q, "%s", blob_sql_text(&sql1));
db_bind_double(&q, ":mtime", p->rDate);
db_step(&q);
db_finalize(&q);
|
| ︙ | ︙ | |||
362 363 364 365 366 367 368 |
return Th_Eval(g.interp, 0, zConfig, -1);
}
/*
** Recreate the TICKET and TICKETCHNG tables.
*/
void ticket_create_table(int separateConnection){
| | > | 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 |
return Th_Eval(g.interp, 0, zConfig, -1);
}
/*
** Recreate the TICKET and TICKETCHNG tables.
*/
void ticket_create_table(int separateConnection){
char *zSql;
db_multi_exec(
"DROP TABLE IF EXISTS ticket;"
"DROP TABLE IF EXISTS ticketchng;"
);
zSql = ticket_table_schema();
if( separateConnection ){
if( db_transaction_nesting_depth() ) db_end_transaction(0);
db_init_database(g.zRepositoryName, zSql, 0);
}else{
db_multi_exec("%s", zSql/*safe-for-%s*/);
}
fossil_free(zSql);
}
/*
** Repopulate the TICKET and TICKETCHNG tables from scratch using all
** available ticket artifacts.
*/
void ticket_rebuild(void){
|
| ︙ | ︙ | |||
442 443 444 445 446 447 448 |
@ mUsed = %d(aField[i].mUsed);
}
@ </ul></div>
}
/*
** WEBPAGE: tktview
| | > > > > < > > > > > > > > > > > > > > | 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 |
@ mUsed = %d(aField[i].mUsed);
}
@ </ul></div>
}
/*
** WEBPAGE: tktview
** URL: tktview?name=HASH
**
** View a ticket identified by the name= query parameter.
** Other query parameters:
**
** tl Show a timeline of the ticket above the status
*/
void tktview_page(void){
const char *zScript;
char *zFullName;
const char *zUuid = PD("name","");
int showTimeline = P("tl")!=0;
login_check_credentials();
if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
if( g.anon.WrTkt || g.anon.ApndTkt ){
style_submenu_element("Edit", "%s/tktedit?name=%T", g.zTop, PD("name",""));
}
if( g.perm.Hyperlink ){
style_submenu_element("History", "%s/tkthistory/%T", g.zTop, zUuid);
style_submenu_element("Check-ins", "%s/tkttimeline/%T?y=ci", g.zTop, zUuid);
}
if( g.anon.NewTkt ){
style_submenu_element("New Ticket", "%s/tktnew", g.zTop);
}
if( g.anon.ApndTkt && g.anon.Attach ){
style_submenu_element("Attach", "%s/attachadd?tkt=%T&from=%s/tktview/%t",
g.zTop, zUuid, g.zTop, zUuid);
}
if( P("plaintext") ){
style_submenu_element("Formatted", "%R/tktview/%s", zUuid);
}else{
style_submenu_element("Plaintext", "%R/tktview/%s?plaintext", zUuid);
}
style_header("View Ticket");
if( showTimeline ){
int tagid = db_int(0,"SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",
zUuid);
if( tagid ){
tkt_draw_timeline(tagid, "a");
@ <hr>
}else{
showTimeline = 0;
}
}
if( !showTimeline && g.perm.Hyperlink ){
style_submenu_element("Timeline", "%s/info/%T", g.zTop, zUuid);
}
if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1);
ticket_init();
initializeVariablesFromCGI();
getAllTicketFields();
initializeVariablesFromDb();
zScript = ticket_viewpage_code();
if( P("showfields")!=0 ) showAllFields();
if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1);
safe_html_context(DOCSRC_TICKET);
Th_Render(zScript);
if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1);
zFullName = db_text(0,
"SELECT tkt_uuid FROM ticket"
" WHERE tkt_uuid GLOB '%q*'", zUuid);
if( zFullName ){
|
| ︙ | ︙ | |||
822 823 824 825 826 827 828 829 830 831 |
"table containing all required fields");
}
}
sqlite3_close(db);
}
return zErr;
}
/*
** WEBPAGE: tkttimeline
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > < < < | 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 |
"table containing all required fields");
}
}
sqlite3_close(db);
}
return zErr;
}
/*
** Draw a timeline for a ticket with tag.tagid given by the tagid
** parameter.
**
** If zType[0]=='c' then only show check-ins associated with the
** ticket. For any other value of zType, show all events associated
** with the ticket.
*/
void tkt_draw_timeline(int tagid, const char *zType){
Stmt q;
char *zFullUuid;
char *zSQL;
zFullUuid = db_text(0, "SELECT substr(tagname, 5) FROM tag WHERE tagid=%d",
tagid);
if( zType[0]=='c' ){
zSQL = mprintf(
"%s AND event.objid IN "
" (SELECT srcid FROM backlink WHERE target GLOB '%.4s*' "
"AND srctype=0 "
"AND '%s' GLOB (target||'*')) "
"ORDER BY mtime DESC",
timeline_query_for_www(), zFullUuid, zFullUuid
);
}else{
zSQL = mprintf(
"%s AND event.objid IN "
" (SELECT rid FROM tagxref WHERE tagid=%d"
" UNION"
" SELECT CASE srctype WHEN 2 THEN"
" (SELECT rid FROM tagxref WHERE tagid=backlink.srcid"
" ORDER BY mtime DESC LIMIT 1)"
" ELSE srcid END"
" FROM backlink"
" WHERE target GLOB '%.4s*'"
" AND '%s' GLOB (target||'*')"
" UNION SELECT attachid FROM attachment"
" WHERE target=%Q) "
"ORDER BY mtime DESC",
timeline_query_for_www(), tagid, zFullUuid, zFullUuid, zFullUuid
);
}
db_prepare(&q, "%z", zSQL/*safe-for-%s*/);
www_print_timeline(&q,
TIMELINE_ARTID | TIMELINE_DISJOINT | TIMELINE_GRAPH | TIMELINE_NOTKT |
TIMELINE_REFS,
0, 0, 0, 0, 0, 0);
db_finalize(&q);
fossil_free(zFullUuid);
}
/*
** WEBPAGE: tkttimeline
** URL: /tkttimeline/TICKETUUID
**
** Show the change history for a single ticket in timeline format.
**
** Query parameters:
**
** y=ci Show only check-ins associated with the ticket
*/
void tkttimeline_page(void){
char *zTitle;
const char *zUuid;
int tagid;
char zGlobPattern[50];
const char *zType;
login_check_credentials();
if( !g.perm.Hyperlink || !g.perm.RdTkt ){
login_needed(g.anon.Hyperlink && g.anon.RdTkt);
|
| ︙ | ︙ | |||
869 870 871 872 873 874 875 |
canonical16(zGlobPattern, strlen(zGlobPattern));
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
if( tagid==0 ){
@ No such ticket: %h(zUuid)
style_footer();
return;
}
| < | < < < < < < < < < < < < < < < < < < < < < < < < < | > > > > > > > | | | | > > > > > | < | 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 |
canonical16(zGlobPattern, strlen(zGlobPattern));
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
if( tagid==0 ){
@ No such ticket: %h(zUuid)
style_footer();
return;
}
tkt_draw_timeline(tagid, zType);
style_footer();
}
/*
** WEBPAGE: tkthistory
** URL: /tkthistory?name=TICKETUUID
**
** Show the complete change history for a single ticket. Or (to put it
** another way) show a list of artifacts associated with a single ticket.
**
** By default, the artifacts are decoded and formatted. Text fields
** are formatted as text/plain, since in the general case Fossil does
** not have knowledge of the encoding. If the "raw" query parameter
** is present, then the* undecoded and unformatted text of each artifact
** is displayed.
*/
void tkthistory_page(void){
Stmt q;
char *zTitle;
const char *zUuid;
int tagid;
int nChng = 0;
login_check_credentials();
if( !g.perm.Hyperlink || !g.perm.RdTkt ){
login_needed(g.anon.Hyperlink && g.anon.RdTkt);
return;
}
zUuid = PD("name","");
zTitle = mprintf("History Of Ticket %h", zUuid);
style_submenu_element("Status", "%s/info/%s", g.zTop, zUuid);
style_submenu_element("Check-ins", "%s/tkttimeline?name=%s&y=ci",
g.zTop, zUuid);
style_submenu_element("Timeline", "%s/tkttimeline?name=%s", g.zTop, zUuid);
if( P("raw")!=0 ){
style_submenu_element("Decoded", "%R/tkthistory/%s", zUuid);
}else if( g.perm.Admin ){
style_submenu_element("Raw", "%R/tkthistory/%s?raw", zUuid);
}
style_header("%z", zTitle);
tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
if( tagid==0 ){
@ No such ticket: %h(zUuid)
style_footer();
return;
}
if( P("raw")!=0 ){
@ <h2>Raw Artifacts Associated With Ticket %h(zUuid)</h2>
}else{
@ <h2>Artifacts Associated With Ticket %h(zUuid)</h2>
}
db_prepare(&q,
"SELECT datetime(mtime,toLocal()), objid, uuid, NULL, NULL, NULL"
" FROM event, blob"
" WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
" AND blob.rid=event.objid"
" UNION "
"SELECT datetime(mtime,toLocal()), attachid, uuid, src, filename, user"
" FROM attachment, blob"
" WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
" AND blob.rid=attachid"
" ORDER BY 1",
tagid, tagid
);
for(nChng=0; db_step(&q)==SQLITE_ROW; nChng++){
Manifest *pTicket;
const char *zDate = db_column_text(&q, 0);
int rid = db_column_int(&q, 1);
const char *zChngUuid = db_column_text(&q, 2);
const char *zFile = db_column_text(&q, 4);
if( nChng==0 ){
@ <ol>
}
if( zFile!=0 ){
const char *zSrc = db_column_text(&q, 3);
const char *zUser = db_column_text(&q, 5);
if( zSrc==0 || zSrc[0]==0 ){
@
@ <li><p>Delete attachment "%h(zFile)"
}else{
|
| ︙ | ︙ | |||
984 985 986 987 988 989 990 |
@
@ <li><p>Ticket change
@ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>]
@ (rid %d(rid)) by
hyperlink_to_user(pTicket->zUser,zDate," on");
hyperlink_to_date(zDate, ":");
@ </p>
| > > > > > > > > | > | 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 |
@
@ <li><p>Ticket change
@ [%z(href("%R/artifact/%!S",zChngUuid))%S(zChngUuid)</a>]
@ (rid %d(rid)) by
hyperlink_to_user(pTicket->zUser,zDate," on");
hyperlink_to_date(zDate, ":");
@ </p>
if( P("raw")!=0 ){
Blob c;
content_get(rid, &c);
@ <blockquote><pre>
@ %h(blob_str(&c))
@ </pre></blockquote>
blob_reset(&c);
}else{
ticket_output_change_artifact(pTicket, "a", nChng);
}
}
manifest_destroy(pTicket);
}
}
db_finalize(&q);
if( nChng ){
@ </ol>
|
| ︙ | ︙ | |||
1012 1013 1014 1015 1016 1017 1018 | return 0; } /* ** The pTkt object is a ticket change artifact. Output a detailed ** description of this object. */ | | > > > > < < < < < < < < > | > | > > > > > > > > > | < > | > | | | | | | | 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 |
return 0;
}
/*
** The pTkt object is a ticket change artifact. Output a detailed
** description of this object.
*/
void ticket_output_change_artifact(
Manifest *pTkt, /* Parsed artifact for the ticket change */
const char *zListType, /* Which type of list */
int n /* Which ticket change is this */
){
int i;
if( zListType==0 ) zListType = "1";
getAllTicketFields();
@ <ol type="%s(zListType)">
for(i=0; i<pTkt->nField; i++){
Blob val;
const char *z, *zX;
int id;
z = pTkt->aField[i].zName;
blob_set(&val, pTkt->aField[i].zValue);
zX = z[0]=='+' ? z+1 : z;
id = fieldId(zX);
@ <li>\
if( id<0 ){
@ Untracked field %h(zX):
}else if( aField[id].mUsed==USEDBY_TICKETCHNG ){
@ %h(zX):
}else if( n==0 ){
@ %h(zX) initialized to:
}else if( z[0]=='+' && (aField[id].mUsed&USEDBY_TICKET)!=0 ){
@ Appended to %h(zX):
}else{
@ %h(zX) changed to:
}
if( blob_size(&val)>50 || contains_newline(&val) ){
@ <blockquote><pre class='verbatim'>
@ %h(blob_str(&val))
@ </pre></blockquote></li>
}else{
@ "%h(blob_str(&val))"</li>
}
blob_reset(&val);
}
@ </ol>
}
/*
** COMMAND: ticket*
**
** Usage: %fossil ticket SUBCOMMAND ...
**
** Run various subcommands to control tickets
**
** > fossil ticket show (REPORTTITLE|REPORTNR) ?TICKETFILTER? ?OPTIONS?
**
** Options:
** -l|--limit LIMITCHAR
** -q|--quote
** -R|--repository FILE
**
** Run the ticket report, identified by the report format title
|
| ︙ | ︙ | |||
1081 1082 1083 1084 1085 1086 1087 | ** Otherwise, the simplified encoding as on the show report raw page ** in the GUI is used. This has no effect in JSON mode. ** ** Instead of the report title it's possible to use the report ** number; the special report number 0 lists all columns defined in ** the ticket table. ** | | | | | | | | | | 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 |
** Otherwise, the simplified encoding as on the show report raw page
** in the GUI is used. This has no effect in JSON mode.
**
** Instead of the report title it's possible to use the report
** number; the special report number 0 lists all columns defined in
** the ticket table.
**
** > fossil ticket list fields
** > fossil ticket ls fields
**
** List all fields defined for ticket in the fossil repository.
**
** > fossil ticket list reports
** > fossil ticket ls reports
**
** List all ticket reports defined in the fossil repository.
**
** > fossil ticket set TICKETUUID (FIELD VALUE)+ ?-q|--quote?
** > fossil ticket change TICKETUUID (FIELD VALUE)+ ?-q|--quote?
**
** Change ticket identified by TICKETUUID to set the values of
** each field FIELD to VALUE.
**
** Field names as defined in the TICKET table. By default, these
** names include: type, status, subsystem, priority, severity, foundin,
** resolution, title, and comment, but other field names can be added
** or substituted in customized installations.
**
** If you use +FIELD, the VALUE is appended to the field FIELD. You
** can use more than one field/value pair on the commandline. Using
** --quote enables the special character decoding as in "ticket
** show", which allows setting multiline text or text with special
** characters.
**
** > fossil ticket add FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote?
**
** Like set, but create a new ticket with the given values.
**
** > fossil ticket history TICKETUUID
**
** Show the complete change history for the ticket
**
** Note that the values in set|add are not validated against the
** definitions given in "Ticket Common Script".
*/
void ticket_cmd(void){
|
| ︙ | ︙ |
| ︙ | ︙ | |||
94 95 96 97 98 99 100 | @ mimetype TEXT, @ icomment TEXT @ ); @ CREATE INDEX ticketchng_idx1 ON ticketchng(tkt_id, tkt_mtime); ; /* | | > | | 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
@ mimetype TEXT,
@ icomment TEXT
@ );
@ CREATE INDEX ticketchng_idx1 ON ticketchng(tkt_id, tkt_mtime);
;
/*
** Return the ticket table definition in heap-allocated
** memory owned by the caller.
*/
char *ticket_table_schema(void){
return db_get("ticket-table", zDefaultTicketTable);
}
/*
** Common implementation for the ticket setup editor pages.
*/
static void tktsetup_generic(
|
| ︙ | ︙ | |||
296 297 298 299 300 301 302 |
0,
30
);
}
static const char zDefaultNew[] =
@ <th1>
| | > > | 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 |
0,
30
);
}
static const char zDefaultNew[] =
@ <th1>
@ if {![info exists mutype]} {set mutype Markdown}
@ if {[info exists submit]} {
@ set status Open
@ if {$mutype eq "HTML"} {
@ set mimetype "text/html"
@ } elseif {$mutype eq "Wiki"} {
@ set mimetype "text/x-fossil-wiki"
@ } elseif {$mutype eq "Markdown"} {
@ set mimetype text/x-markdown
@ } elseif {$mutype eq {[links only]}} {
@ set mimetype "text/x-fossil-plain"
@ } else {
@ set mimetype "text/plain"
@ }
@ submit_ticket
@ set preview 1
|
| ︙ | ︙ | |||
359 360 361 362 363 364 365 | @ @ <tr> @ <td colspan="3"> @ 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. Format: | | > > | 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 |
@
@ <tr>
@ <td colspan="3">
@ 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. Format:
@ <th1>combobox mutype {HTML {[links only]} Markdown {Plain Text} Wiki}} 1</th1>
@ <br />
@ <th1>set nline [linecount $comment 50 10]</th1>
@ <textarea name="icomment" cols="80" rows="$nline"
@ wrap="virtual" class="wikiedit">$<icomment></textarea><br />
@ </tr>
@
@ <th1>enable_output [info exists preview]</th1>
@ <tr><td colspan="3">
@ Description Preview:<br /><hr />
@ <th1>
@ if {$mutype eq "Wiki"} {
@ wiki $icomment
@ } elseif {$mutype eq "Plain Text"} {
@ set r [randhex]
@ wiki "<verbatim-$r>[string trimright $icomment]\n</verbatim-$r>"
@ } elseif {$mutype eq "Markdown"} {
@ html [lindex [markdown "$icomment\n"] 1]
@ } elseif {$mutype eq {[links only]}} {
@ set r [randhex]
@ wiki "<verbatim-$r links>[string trimright $icomment]\n</verbatim-$r>"
@ } else {
@ wiki "<nowiki>$icomment\n</nowiki>"
@ }
@ </th1>
|
| ︙ | ︙ | |||
441 442 443 444 445 446 447 |
0,
40
);
}
static const char zDefaultView[] =
@ <table cellpadding="5">
| | | 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 |
0,
40
);
}
static const char zDefaultView[] =
@ <table cellpadding="5">
@ <tr><td class="tktDspLabel">Ticket Hash:</td>
@ <th1>
@ if {[info exists tkt_uuid]} {
@ html "<td class='tktDspValue' colspan='3'>"
@ copybtn hash-tk 0 $tkt_uuid 2
@ if {[hascap s]} {
@ html " ($tkt_id)"
@ }
|
| ︙ | ︙ | |||
539 540 541 542 543 544 545 546 547 548 549 550 551 552 |
@ html " added on $xdate:\n"
@ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
@ set r [randhex]
@ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"}
@ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
@ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
@ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
@ } elseif {$xmimetype eq "text/html"} {
@ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n"
@ } else {
@ set r [randhex]
@ wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n"
@ }
@ }
| > > | 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 |
@ html " added on $xdate:\n"
@ if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
@ set r [randhex]
@ if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"}
@ wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
@ } elseif {$xmimetype eq "text/x-fossil-wiki"} {
@ wiki "<p>\n[string trimright $xcomment]\n</p>\n"
@ } elseif {$xmimetype eq "text/x-markdown"} {
@ html [lindex [markdown $xcomment] 1]
@ } elseif {$xmimetype eq "text/html"} {
@ wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n"
@ } else {
@ set r [randhex]
@ wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n"
@ }
@ }
|
| ︙ | ︙ | |||
581 582 583 584 585 586 587 |
0,
40
);
}
static const char zDefaultEdit[] =
@ <th1>
| | > > | 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 |
0,
40
);
}
static const char zDefaultEdit[] =
@ <th1>
@ if {![info exists mutype]} {set mutype Markdown}
@ if {![info exists icomment]} {set icomment {}}
@ if {![info exists username]} {set username $login}
@ if {[info exists submit]} {
@ if {$mutype eq "Wiki"} {
@ set mimetype text/x-fossil-wiki
@ } elseif {$mutype eq "Markdown"} {
@ set mimetype text/x-markdown
@ } elseif {$mutype eq "HTML"} {
@ set mimetype text/html
@ } elseif {$mutype eq {[links only]}} {
@ set mimetype text/x-fossil-plain
@ } else {
@ set mimetype text/plain
@ }
|
| ︙ | ︙ | |||
640 641 642 643 644 645 646 | @ @ <tr><td class="tktDspLabel">Version Found In:</td><td> @ <input type="text" name="foundin" size="50" value="$<foundin>" /> @ </td></tr> @ @ <tr><td colspan="2"> @ Append Remark with format | | > > | 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 |
@
@ <tr><td class="tktDspLabel">Version Found In:</td><td>
@ <input type="text" name="foundin" size="50" value="$<foundin>" />
@ </td></tr>
@
@ <tr><td colspan="2">
@ Append Remark with format
@ <th1>combobox mutype {HTML {[links only]} Markdown {Plain Text} Wiki} 1</th1>
@ from
@ <input type="text" name="username" value="$<username>" size="30" />:<br />
@ <textarea name="icomment" cols="80" rows="15"
@ wrap="virtual" class="wikiedit">$<icomment></textarea>
@ </td></tr>
@
@ <th1>enable_output [info exists preview]</th1>
@ <tr><td colspan="2">
@ Description Preview:<br /><hr />
@ <th1>
@ if {$mutype eq "Wiki"} {
@ wiki $icomment
@ } elseif {$mutype eq "Plain Text"} {
@ set r [randhex]
@ wiki "<verbatim-$r>\n[string trimright $icomment]\n</verbatim-$r>"
@ } elseif {$mutype eq "Markdown"} {
@ html [lindex [markdown "$icomment\n"] 1]
@ } elseif {$mutype eq {[links only]}} {
@ set r [randhex]
@ wiki "<verbatim-$r links>\n[string trimright $icomment]</verbatim-$r>"
@ } else {
@ wiki "<nowiki>\n[string trimright $icomment]\n</nowiki>"
@ }
@ </th1>
|
| ︙ | ︙ | |||
897 898 899 900 901 902 903 |
@ <form action="%s(g.zTop)/tktsetup_timeline" method="post"><div>
login_insert_csrf_secret();
@ <hr />
entry_attribute("Ticket Title", 40, "ticket-title-expr", "t",
"title", 0);
@ <p>An SQL expression in a query against the TICKET table that will
| | > | > | > | 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 |
@ <form action="%s(g.zTop)/tktsetup_timeline" method="post"><div>
login_insert_csrf_secret();
@ <hr />
entry_attribute("Ticket Title", 40, "ticket-title-expr", "t",
"title", 0);
@ <p>An SQL expression in a query against the TICKET table that will
@ return the title of the ticket for display purposes.
@ (Property: ticket-title-expr)</p>
@ <hr />
entry_attribute("Ticket Status", 40, "ticket-status-column", "s",
"status", 0);
@ <p>The name of the column in the TICKET table that contains the ticket
@ status in human-readable form. Case sensitive.
@ (Property: ticket-status-column)</p>
@ <hr />
entry_attribute("Ticket Closed", 40, "ticket-closed-expr", "c",
"status='Closed'", 0);
@ <p>An SQL expression that evaluates to true in a TICKET table query if
@ the ticket is closed.
@ (Property: ticket-closed-expr)</p>
@ <hr />
@ <p>
@ <input type="submit" name="submit" value="Apply Changes" />
@ <input type="submit" name="setup" value="Cancel" />
@ </p>
@ </div></form>
db_end_transaction(0);
style_footer();
}
|
| ︙ | ︙ | |||
176 177 178 179 180 181 182 |
static const char zSql[] =
@ DROP TABLE IF EXISTS undo;
@ DROP TABLE IF EXISTS undo_vfile;
@ DROP TABLE IF EXISTS undo_vmerge;
@ DROP TABLE IF EXISTS undo_stash;
@ DROP TABLE IF EXISTS undo_stashfile;
;
| | | 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
static const char zSql[] =
@ DROP TABLE IF EXISTS undo;
@ DROP TABLE IF EXISTS undo_vfile;
@ DROP TABLE IF EXISTS undo_vmerge;
@ DROP TABLE IF EXISTS undo_stash;
@ DROP TABLE IF EXISTS undo_stashfile;
;
db_exec_sql(zSql);
db_lset_int("undo_available", 0);
db_lset_int("undo_checkout", 0);
}
/*
** The following variable stores the original command-line of the
** command that is a candidate to be undone.
|
| ︙ | ︙ | |||
233 234 235 236 237 238 239 |
@ content BLOB -- Saved content
@ );
@ CREATE TABLE localdb.undo_vfile AS SELECT * FROM vfile;
@ CREATE TABLE localdb.undo_vmerge AS SELECT * FROM vmerge;
;
if( undoDisable ) return;
undo_reset();
| | | 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
@ content BLOB -- Saved content
@ );
@ CREATE TABLE localdb.undo_vfile AS SELECT * FROM vfile;
@ CREATE TABLE localdb.undo_vmerge AS SELECT * FROM vmerge;
;
if( undoDisable ) return;
undo_reset();
db_exec_sql(zSql);
cid = db_lget_int("checkout", 0);
db_lset_int("undo_checkout", cid);
db_lset_int("undo_available", 1);
db_lset("undo_cmdline", undoCmd);
undoActive = 1;
}
|
| ︙ | ︙ | |||
421 422 423 424 425 426 427 | /* ** COMMAND: undo ** COMMAND: redo* ** ** Usage: %fossil undo ?OPTIONS? ?FILENAME...? ** or: %fossil redo ?OPTIONS? ?FILENAME...? ** | | | | > > > | | | | | | > > > > > > > | > > > > | 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 |
/*
** COMMAND: undo
** COMMAND: redo*
**
** Usage: %fossil undo ?OPTIONS? ?FILENAME...?
** or: %fossil redo ?OPTIONS? ?FILENAME...?
**
** The undo command reverts the changes caused by the previous command
** if the previous command is one of the following:
** * fossil update
** * fossil merge
** * fossil revert
** * fossil stash pop
** * fossil stash apply
** * fossil stash drop
** * fossil stash goto
** * fossil clean (*see note below*)
**
** Note: The "fossil clean" command only saves state for files less than
** 10MiB in size and so if fossil clean deleted files larger than that,
** then "fossil undo" will not recover the larger files.
**
** If FILENAME is specified then restore the content of the named
** file(s) but otherwise leave the update or merge or revert in effect.
** The redo command undoes the effect of the most recent undo.
**
** If the -n|--dry-run option is present, no changes are made and instead
** the undo or redo command explains what actions the undo or redo would
** have done had the -n|--dry-run been omitted.
**
** If the most recent command is not one of those listed as undoable,
** then the undo command might try to restore the state to be what it was
** prior to the last undoable command, or it might be a no-op. If in
** doubt about what the undo command will do, first run it with the -n
** option.
**
** A single level of undo/redo is supported. The undo/redo stack
** is cleared by the commit and checkout commands. Other commands may
** or may not clear the undo stack.
**
** Future versions of Fossil might add new commands to the set of commands
** that are undoable.
**
** Options:
** -n|--dry-run do not make changes but show what would be done
**
** See also: commit, status
*/
void undo_cmd(void){
|
| ︙ | ︙ |
| ︙ | ︙ | |||
54 55 56 57 58 59 60 |
0x00217801, 0x00234C31, 0x0024E803, 0x0024F812, 0x00254407,
0x00258804, 0x0025C001, 0x00260403, 0x0026F001, 0x0026F807,
0x00271C02, 0x00272C03, 0x00275C01, 0x00278802, 0x0027C802,
0x0027E802, 0x0027F402, 0x00280403, 0x0028F001, 0x0028F805,
0x00291C02, 0x00292C03, 0x00294401, 0x0029C002, 0x0029D402,
0x002A0403, 0x002AF001, 0x002AF808, 0x002B1C03, 0x002B2C03,
0x002B8802, 0x002BC002, 0x002BE806, 0x002C0403, 0x002CF001,
| | | | | | | | | | | | | | | | | | | | | | | | | | | > | | | | | | | > | | | | | | | | | | | | < | > | | 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 |
0x00217801, 0x00234C31, 0x0024E803, 0x0024F812, 0x00254407,
0x00258804, 0x0025C001, 0x00260403, 0x0026F001, 0x0026F807,
0x00271C02, 0x00272C03, 0x00275C01, 0x00278802, 0x0027C802,
0x0027E802, 0x0027F402, 0x00280403, 0x0028F001, 0x0028F805,
0x00291C02, 0x00292C03, 0x00294401, 0x0029C002, 0x0029D402,
0x002A0403, 0x002AF001, 0x002AF808, 0x002B1C03, 0x002B2C03,
0x002B8802, 0x002BC002, 0x002BE806, 0x002C0403, 0x002CF001,
0x002CF807, 0x002D1C02, 0x002D2C03, 0x002D5403, 0x002D8802,
0x002DC001, 0x002E0801, 0x002EF805, 0x002F1803, 0x002F2804,
0x002F5C01, 0x002FCC08, 0x00300005, 0x0030F807, 0x00311803,
0x00312804, 0x00315402, 0x00318802, 0x0031DC01, 0x0031FC01,
0x00320404, 0x0032F001, 0x0032F807, 0x00331803, 0x00332804,
0x00335402, 0x00338802, 0x00340004, 0x0034EC02, 0x0034F807,
0x00351803, 0x00352804, 0x00353C01, 0x00355C01, 0x00358802,
0x0035E401, 0x00360403, 0x00372801, 0x00373C06, 0x00375801,
0x00376008, 0x0037C803, 0x0038C401, 0x0038D007, 0x0038FC01,
0x00391C09, 0x00396802, 0x003AC401, 0x003AD009, 0x003B2006,
0x003C041F, 0x003CD00C, 0x003DC417, 0x003E340B, 0x003E6424,
0x003EF80F, 0x003F380D, 0x0040AC14, 0x00412806, 0x00415804,
0x00417803, 0x00418803, 0x00419C07, 0x0041C404, 0x0042080C,
0x00423C01, 0x00426806, 0x0043EC01, 0x004D740C, 0x004E400A,
0x00500001, 0x0059B402, 0x005A0001, 0x005A6C02, 0x005BAC03,
0x005C4803, 0x005CC805, 0x005D4802, 0x005DC802, 0x005ED023,
0x005F6004, 0x005F7401, 0x0060000F, 0x00621402, 0x0062A401,
0x0064800C, 0x0064C00C, 0x00650001, 0x00651002, 0x00677822,
0x00685C05, 0x00687802, 0x0069540A, 0x0069801D, 0x0069FC01,
0x006A8007, 0x006AA006, 0x006AC011, 0x006C0005, 0x006CD011,
0x006D6823, 0x006E0003, 0x006E840D, 0x006F980E, 0x006FF004,
0x00709014, 0x0070EC05, 0x0071F802, 0x00730008, 0x00734019,
0x0073B401, 0x0073D001, 0x0073DC03, 0x0077003A, 0x0077EC05,
0x007EF401, 0x007EFC03, 0x007F3403, 0x007F7403, 0x007FB403,
0x007FF402, 0x00800065, 0x0081980A, 0x0081E805, 0x00822805,
0x00828020, 0x00834021, 0x00840002, 0x00840C04, 0x00842002,
0x00845001, 0x00845803, 0x00847806, 0x00849401, 0x00849C01,
0x0084A401, 0x0084B801, 0x0084E802, 0x00850005, 0x00852804,
0x00853C01, 0x00862802, 0x00864297, 0x0091000B, 0x0092704E,
0x00940276, 0x009E53E0, 0x00ADD820, 0x00AE5C69, 0x00B39406,
0x00B3BC03, 0x00B3E404, 0x00B3F802, 0x00B5C001, 0x00B5FC01,
0x00B7804F, 0x00B8C023, 0x00BA001A, 0x00BA6C59, 0x00BC00D6,
0x00BFC00C, 0x00C00005, 0x00C02019, 0x00C0A807, 0x00C0D802,
0x00C0F403, 0x00C26404, 0x00C28001, 0x00C3EC01, 0x00C64002,
0x00C6580A, 0x00C70024, 0x00C8001F, 0x00C8A81E, 0x00C94001,
0x00C98020, 0x00CA2827, 0x00CB0140, 0x01370040, 0x02924037,
0x0293F802, 0x02983403, 0x0299BC10, 0x029A7802, 0x029BC008,
0x029C0017, 0x029C8002, 0x029E2402, 0x02A00801, 0x02A01801,
0x02A02C01, 0x02A08C0A, 0x02A0D804, 0x02A1D004, 0x02A20002,
0x02A2D012, 0x02A33802, 0x02A38012, 0x02A3E003, 0x02A3F001,
0x02A3FC01, 0x02A4980A, 0x02A51C0D, 0x02A57C01, 0x02A60004,
0x02A6CC1B, 0x02A77802, 0x02A79401, 0x02A8A40E, 0x02A90C01,
0x02A93002, 0x02A97004, 0x02A9DC03, 0x02A9EC03, 0x02AAC001,
0x02AAC803, 0x02AADC02, 0x02AAF802, 0x02AB0401, 0x02AB7802,
0x02ABAC07, 0x02ABD402, 0x02AD6C01, 0x02ADA802, 0x02AF8C0B,
0x03600001, 0x036DFC02, 0x036FFC02, 0x037FFC01, 0x03EC7801,
0x03ECA401, 0x03EEC810, 0x03F4F802, 0x03F7F002, 0x03F8001A,
0x03F88033, 0x03F95013, 0x03F9A004, 0x03FBFC01, 0x03FC040F,
0x03FC6807, 0x03FCEC06, 0x03FD6C0B, 0x03FF8007, 0x03FFA007,
0x03FFE405, 0x04040003, 0x0404DC09, 0x0405E411, 0x04063003,
0x0406400D, 0x04068001, 0x0407402E, 0x040B8001, 0x040DD805,
0x040E7C01, 0x040F4001, 0x0415BC01, 0x04215C01, 0x0421DC02,
0x04247C01, 0x0424FC01, 0x04280403, 0x04281402, 0x04283004,
0x0428E003, 0x0428FC01, 0x04294009, 0x0429FC01, 0x042B2001,
0x042B9402, 0x042BC007, 0x042CE407, 0x042E6404, 0x04349004,
0x043AAC03, 0x043D180B, 0x043D5405, 0x04400003, 0x0440E016,
0x0441FC04, 0x0442C012, 0x04433401, 0x04440003, 0x04449C0E,
0x04450004, 0x04451402, 0x0445CC03, 0x04460003, 0x0446CC0E,
0x0447140B, 0x04476C01, 0x04477403, 0x0448B013, 0x044AA401,
0x044B7C0C, 0x044C0004, 0x044CEC02, 0x044CF807, 0x044D1C02,
0x044D2C03, 0x044D5C01, 0x044D8802, 0x044D9807, 0x044DC005,
0x0450D412, 0x04512C05, 0x04516802, 0x04517402, 0x0452C014,
0x04531801, 0x0456BC07, 0x0456E020, 0x04577002, 0x0458C014,
0x0459800D, 0x045AAC0D, 0x045C740F, 0x045CF004, 0x0460B010,
0x0464C006, 0x0464DC02, 0x0464EC04, 0x04650001, 0x04650805,
0x04674407, 0x04676807, 0x04678801, 0x04679001, 0x0468040A,
0x0468CC07, 0x0468EC0D, 0x0469440B, 0x046A2813, 0x046A7805,
0x0470BC08, 0x0470E008, 0x04710405, 0x0471C002, 0x04724816,
0x0472A40E, 0x0474C406, 0x0474E801, 0x0474F002, 0x0474FC07,
0x04751C01, 0x04762805, 0x04764002, 0x04764C05, 0x047BCC06,
0x047F541D, 0x047FFC01, 0x0491C005, 0x04D0C009, 0x05A9B802,
0x05ABC006, 0x05ACC010, 0x05AD1002, 0x05BA5C04, 0x05BD3C01,
0x05BD4437, 0x05BE3C04, 0x05BF8801, 0x05BF9001, 0x05BFC002,
0x06F27008, 0x074000F6, 0x07440027, 0x0744A4C0, 0x07480046,
0x074C0057, 0x075B0401, 0x075B6C01, 0x075BEC01, 0x075C5401,
0x075CD401, 0x075D3C01, 0x075DBC01, 0x075E2401, 0x075EA401,
0x075F0C01, 0x0760028C, 0x076A6C05, 0x076A840F, 0x07800007,
0x07802011, 0x07806C07, 0x07808C02, 0x07809805, 0x0784C007,
0x07853C01, 0x078BB004, 0x078BFC01, 0x07A34007, 0x07A51007,
0x07A57802, 0x07B2B001, 0x07B2C001, 0x07B4B801, 0x07BBC002,
0x07C0002C, 0x07C0C064, 0x07C2800F, 0x07C2C40F, 0x07C3040F,
0x07C34425, 0x07C434A1, 0x07C7981D, 0x07C8402C, 0x07C90009,
0x07C94002, 0x07C98006, 0x07CC03D8, 0x07DB800D, 0x07DBC00D,
0x07DC0074, 0x07DE0059, 0x07DF800C, 0x07E0000C, 0x07E04038,
0x07E1400A, 0x07E18028, 0x07E2401E, 0x07E2C002, 0x07E40079,
0x07E5E852, 0x07E73487, 0x07E9800E, 0x07E9C005, 0x07E9E003,
0x07EA0007, 0x07EA4019, 0x07EAC007, 0x07EB0003, 0x07EB4007,
0x07EC0093, 0x07EE5037, 0x38000401, 0x38008060, 0x380400F0,
};
static const unsigned int aAscii[4] = {
0xFFFFFFFF, 0xFC00FFFF, 0xF8000001, 0xF8000001,
};
if( (unsigned int)c<128 ){
return ( (aAscii[c >> 5] & ((unsigned int)1 << (c & 0x001F)))==0 );
|
| ︙ | ︙ | |||
348 349 350 351 352 353 354 |
{42802, 1, 62}, {42873, 1, 4}, {42877, 98, 1},
{42878, 1, 10}, {42891, 0, 1}, {42893, 88, 1},
{42896, 1, 4}, {42902, 1, 20}, {42922, 80, 1},
{42923, 76, 1}, {42924, 78, 1}, {42925, 84, 1},
{42926, 80, 1}, {42928, 92, 1}, {42929, 86, 1},
{42930, 90, 1}, {42931, 68, 1}, {42932, 1, 12},
{42946, 0, 1}, {42948, 178, 1}, {42949, 82, 1},
| > | | 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
{42802, 1, 62}, {42873, 1, 4}, {42877, 98, 1},
{42878, 1, 10}, {42891, 0, 1}, {42893, 88, 1},
{42896, 1, 4}, {42902, 1, 20}, {42922, 80, 1},
{42923, 76, 1}, {42924, 78, 1}, {42925, 84, 1},
{42926, 80, 1}, {42928, 92, 1}, {42929, 86, 1},
{42930, 90, 1}, {42931, 68, 1}, {42932, 1, 12},
{42946, 0, 1}, {42948, 178, 1}, {42949, 82, 1},
{42950, 96, 1}, {42951, 1, 4}, {42997, 0, 1},
{43888, 94, 80}, {65313, 14, 26},
};
static const unsigned short aiOff[] = {
1, 2, 8, 15, 16, 26, 28, 32,
34, 37, 38, 40, 48, 63, 64, 69,
71, 79, 80, 116, 202, 203, 205, 206,
207, 209, 210, 211, 213, 214, 217, 218,
219, 775, 928, 7264, 10792, 10795, 23217, 23221,
|
| ︙ | ︙ |
| ︙ | ︙ | |||
83 84 85 86 87 88 89 | } return zHash; } /* ** Initialize pContent to be the content of an unversioned file zName. ** | | > > | | > | > > > > > > > > > > > > | | 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 |
}
return zHash;
}
/*
** Initialize pContent to be the content of an unversioned file zName.
**
** Return 0 on failures.
** Return 1 if the file is found by name.
** Return 2 if the file is found by hash.
*/
int unversioned_content(const char *zName, Blob *pContent){
Stmt q;
int rc = 0;
blob_init(pContent, 0, 0);
db_prepare(&q, "SELECT encoding, content FROM unversioned WHERE name=%Q",
zName);
if( db_step(&q)==SQLITE_ROW ){
db_column_blob(&q, 1, pContent);
if( db_column_int(&q, 0)==1 ){
blob_uncompress(pContent, pContent);
}
rc = 1;
}
db_finalize(&q);
if( rc==0 && validate16(zName,-1) ){
db_prepare(&q, "SELECT encoding, content FROM unversioned WHERE hash=%Q",
zName);
if( db_step(&q)==SQLITE_ROW ){
db_column_blob(&q, 1, pContent);
if( db_column_int(&q, 0)==1 ){
blob_uncompress(pContent, pContent);
}
rc = 2;
}
db_finalize(&q);
}
return rc;
}
/*
** Write unversioned content into the database.
*/
static void unversioned_write(
const char *zUVFile, /* Name of the unversioned file */
Blob *pContent, /* File content */
sqlite3_int64 mtime /* Modification time */
){
Stmt ins;
Blob compressed;
Blob hash;
db_prepare(&ins,
"REPLACE INTO unversioned(name,rcvid,mtime,hash,sz,encoding,content)"
" VALUES(:name,:rcvid,:mtime,:hash,:sz,:encoding,:content)"
);
hname_hash(pContent, 0, &hash);
blob_compress(pContent, &compressed);
db_bind_text(&ins, ":name", zUVFile);
db_bind_int(&ins, ":rcvid", g.rcvid);
db_bind_int64(&ins, ":mtime", mtime);
db_bind_text(&ins, ":hash", blob_str(&hash));
db_bind_int(&ins, ":sz", blob_size(pContent));
if( blob_size(&compressed) <= 0.8*blob_size(pContent) ){
|
| ︙ | ︙ | |||
141 142 143 144 145 146 147 |
db_finalize(&ins);
db_unset("uv-hash", 0);
}
/*
** Check the status of unversioned file zName. "mtime" and "zHash" are the
| | | 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
db_finalize(&ins);
db_unset("uv-hash", 0);
}
/*
** Check the status of unversioned file zName. "mtime" and "zHash" are the
** time of last change and hash of a copy of this file on a remote
** server. Return an integer status code as follows:
**
** 0: zName does not exist in the unversioned table.
** 1: zName exists and should be replaced by the mtime/zHash remote.
** 2: zName exists and is the same as zHash but has a older mtime
** 3: zName exists and is identical to mtime/zHash in all respects.
** 4: zName exists and is the same as zHash but has a newer mtime.
|
| ︙ | ︙ | |||
235 236 237 238 239 240 241 | ** cat FILE ... Concatenate the content of FILEs to stdout. ** ** edit FILE Bring up FILE in a text editor for modification. ** ** export FILE OUTPUT Write the content of FILE into OUTPUT on disk ** ** list | ls Show all unversioned files held in the local | | > > > | > > > > | 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 |
** cat FILE ... Concatenate the content of FILEs to stdout.
**
** edit FILE Bring up FILE in a text editor for modification.
**
** export FILE OUTPUT Write the content of FILE into OUTPUT on disk
**
** list | ls Show all unversioned files held in the local
** repository. Options:
**
** --glob PATTERN Show only files that match
** --like PATTERN Show only files that match
**
** revert ?URL? Restore the state of all unversioned files in the
** local repository to match the remote repository
** URL.
**
** Options:
** -v|--verbose Extra diagnostic output
** -n|--dryrun Show what would have happened
**
** remove|rm|delete FILE ...
** Remove unversioned files from the local repository.
** Changes are not pushed to other repositories until
** the next sync. Options:
**
** --glob PATTERN Remove files that match
** --like PATTERN Remove files that match
**
** sync ?URL? Synchronize the state of all unversioned files with
** the remote repository URL. The most recent version
** of each file is propagated to all repositories and
** all prior versions are permanently forgotten.
**
** Options:
** -v|--verbose Extra diagnostic output
** -n|--dryrun Show what would have happened
**
** touch FILE ... Update the TIMESTAMP on all of the listed files
**
** Options:
**
** --mtime TIMESTAMP Use TIMESTAMP instead of "now" for the "add",
** "edit", "remove", and "touch" subcommands.
** -R|--repository FILE Use FILE as the repository
*/
void unversioned_cmd(void){
const char *zCmd;
int nCmd;
const char *zMtime = find_option("mtime", 0, 1);
sqlite3_int64 mtime;
db_find_and_open_repository(0, 0);
|
| ︙ | ︙ | |||
289 290 291 292 293 294 295 |
const char *zError = 0;
const char *zIn;
const char *zAs;
Blob file;
int i;
zAs = find_option("as",0,1);
| < > | 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
const char *zError = 0;
const char *zIn;
const char *zAs;
Blob file;
int i;
zAs = find_option("as",0,1);
verify_all_options();
if( zAs && g.argc!=4 ) usage("add DISKFILE --as UVFILE");
db_begin_transaction();
content_rcvid_init("#!fossil unversioned add");
for(i=3; i<g.argc; i++){
zIn = zAs ? zAs : g.argv[i];
if( zIn[0]==0 ){
zError = "be empty string";
}else if( zIn[0]=='/' ){
|
| ︙ | ︙ | |||
319 320 321 322 323 324 325 |
db_end_transaction(0);
}else if( memcmp(zCmd, "cat", nCmd)==0 ){
int i;
verify_all_options();
db_begin_transaction();
for(i=3; i<g.argc; i++){
Blob content;
| | > | > | | | > > > > > > > > > > > > | > | > | | | | 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 |
db_end_transaction(0);
}else if( memcmp(zCmd, "cat", nCmd)==0 ){
int i;
verify_all_options();
db_begin_transaction();
for(i=3; i<g.argc; i++){
Blob content;
if( unversioned_content(g.argv[i], &content)!=0 ){
blob_write_to_file(&content, "-");
}
blob_reset(&content);
}
db_end_transaction(0);
}else if( memcmp(zCmd, "edit", nCmd)==0 ){
const char *zEditor; /* Name of the text-editor command */
const char *zTFile; /* Temporary file */
const char *zUVFile; /* Name of the unversioned file */
char *zCmd; /* Command to run the text editor */
Blob content; /* Content of the unversioned file */
verify_all_options();
if( g.argc!=4) usage("edit UVFILE");
zUVFile = g.argv[3];
zEditor = fossil_text_editor();
if( zEditor==0 ){
fossil_fatal("no text editor - set the VISUAL env variable");
}
zTFile = fossil_temp_filename();
if( zTFile==0 ) fossil_fatal("cannot find a temporary filename");
db_begin_transaction();
content_rcvid_init("#!fossil unversioned edit");
if( unversioned_content(zUVFile, &content)==0 ){
fossil_fatal("no such uv-file: %Q", zUVFile);
}
if( looks_like_binary(&content) ){
fossil_fatal("cannot edit binary content");
}
#if defined(_WIN32) || defined(__CYGWIN__)
blob_add_cr(&content);
#endif
blob_write_to_file(&content, zTFile);
zCmd = mprintf("%s %$", zEditor, zTFile);
if( fossil_system(zCmd) ){
fossil_fatal("editor aborted: %Q", zCmd);
}
fossil_free(zCmd);
blob_reset(&content);
blob_read_from_file(&content, zTFile, ExtFILE);
#if defined(_WIN32) || defined(__CYGWIN__)
blob_to_lf_only(&content);
#endif
file_delete(zTFile);
if( zMtime==0 ) mtime = time(0);
unversioned_write(zUVFile, &content, mtime);
db_end_transaction(0);
blob_reset(&content);
}else if( memcmp(zCmd, "export", nCmd)==0 ){
Blob content;
verify_all_options();
if( g.argc!=5 ) usage("export UVFILE OUTPUT");
if( unversioned_content(g.argv[3], &content)==0 ){
fossil_fatal("no such uv-file: %Q", g.argv[3]);
}
blob_write_to_file(&content, g.argv[4]);
blob_reset(&content);
}else if( memcmp(zCmd, "hash", nCmd)==0 ){ /* undocumented */
/* Show the hash value used during uv sync */
int debugFlag = find_option("debug",0,0)!=0;
fossil_print("%s\n", unversioned_content_hash(debugFlag));
}else if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){
Stmt q;
int allFlag = find_option("all","a",0)!=0;
int longFlag = find_option("l",0,0)!=0 || (nCmd>1 && zCmd[1]=='i');
char *zPattern = sqlite3_mprintf("true");
const char *zGlob;
zGlob = find_option("glob",0,1);
if( zGlob ){
sqlite3_free(zPattern);
zPattern = sqlite3_mprintf("(name GLOB %Q)", zGlob);
}
zGlob = find_option("like",0,1);
if( zGlob ){
sqlite3_free(zPattern);
zPattern = sqlite3_mprintf("(name LIKE %Q)", zGlob);
}
verify_all_options();
if( !longFlag ){
if( allFlag ){
db_prepare(&q, "SELECT name FROM unversioned WHERE %s ORDER BY name",
zPattern/*safe-for-%s*/);
}else{
db_prepare(&q, "SELECT name FROM unversioned"
" WHERE %s AND hash IS NOT NULL"
" ORDER BY name", zPattern/*safe-for-%s*/);
}
while( db_step(&q)==SQLITE_ROW ){
fossil_print("%s\n", db_column_text(&q,0));
}
}else{
db_prepare(&q,
"SELECT hash, datetime(mtime,'unixepoch'), sz, length(content), name"
" FROM unversioned WHERE %s"
" ORDER BY name;", zPattern/*safe-for-%s*/
);
while( db_step(&q)==SQLITE_ROW ){
const char *zHash = db_column_text(&q, 0);
const char *zNoContent = "";
if( zHash==0 ){
if( !allFlag ) continue;
zHash = "(deleted)";
|
| ︙ | ︙ | |||
420 421 422 423 424 425 426 427 |
db_column_int(&q,3),
db_column_text(&q,4),
zNoContent
);
}
}
db_finalize(&q);
}else if( memcmp(zCmd, "revert", nCmd)==0 ){
| > > | | > > > > > > > > > > > > > > > | 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 |
db_column_int(&q,3),
db_column_text(&q,4),
zNoContent
);
}
}
db_finalize(&q);
sqlite3_free(zPattern);
}else if( memcmp(zCmd, "revert", nCmd)==0 ){
unsigned syncFlags =
unversioned_sync_flags(SYNC_UNVERSIONED|SYNC_UV_REVERT);
g.argv[1] = "sync";
g.argv[2] = "--uv-noop";
sync_unversioned(syncFlags);
}else if( memcmp(zCmd, "remove", nCmd)==0 || memcmp(zCmd, "rm", nCmd)==0
|| memcmp(zCmd, "delete", nCmd)==0 ){
int i;
const char *zGlob;
db_begin_transaction();
while( (zGlob = find_option("glob",0,1))!=0 ){
db_multi_exec(
"UPDATE unversioned"
" SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name GLOB %Q",
mtime, zGlob
);
}
while( (zGlob = find_option("like",0,1))!=0 ){
db_multi_exec(
"UPDATE unversioned"
" SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name LIKE %Q",
mtime, zGlob
);
}
verify_all_options();
for(i=3; i<g.argc; i++){
db_multi_exec(
"UPDATE unversioned"
" SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name=%Q",
mtime, g.argv[i]
);
}
|
| ︙ | ︙ | |||
482 483 484 485 486 487 488 489 490 491 492 493 494 495 |
int n = 0;
const char *zOrderBy = "name";
int showDel = 0;
char zSzName[100];
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
style_header("Unversioned Files");
if( !db_table_exists("repository","unversioned") ){
@ No unversioned files on this server
style_footer();
return;
}
if( PB("byage") ) zOrderBy = "mtime DESC";
| > | 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 |
int n = 0;
const char *zOrderBy = "name";
int showDel = 0;
char zSzName[100];
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
etag_check(ETAG_DATA,0);
style_header("Unversioned Files");
if( !db_table_exists("repository","unversioned") ){
@ No unversioned files on this server
style_footer();
return;
}
if( PB("byage") ) zOrderBy = "mtime DESC";
|
| ︙ | ︙ | |||
524 525 526 527 528 529 530 |
@ <table cellpadding="2" cellspacing="0" border="1" class='sortable' \
@ data-column-types='tkKttn' data-init-sort='1'>
@ <thead><tr>
@ <th> Name
@ <th> Age
@ <th> Size
@ <th> User
| | | 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 |
@ <table cellpadding="2" cellspacing="0" border="1" class='sortable' \
@ data-column-types='tkKttn' data-init-sort='1'>
@ <thead><tr>
@ <th> Name
@ <th> Age
@ <th> Size
@ <th> User
@ <th> Hash
if( g.perm.Admin ){
@ <th> rcvid
}
@ </tr></thead>
@ <tbody>
}
@ <tr>
|
| ︙ | ︙ | |||
594 595 596 597 598 599 600 601 602 603 604 605 606 607 |
Stmt q;
char *zSep = "[";
Blob json;
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
cgi_set_content_type("text/json");
if( !db_table_exists("repository","unversioned") ){
blob_init(&json, "[]", -1);
cgi_set_content(&json);
return;
}
blob_init(&json, 0, 0);
db_prepare(&q,
| > | 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 |
Stmt q;
char *zSep = "[";
Blob json;
login_check_credentials();
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
cgi_set_content_type("text/json");
etag_check(ETAG_DATA,0);
if( !db_table_exists("repository","unversioned") ){
blob_init(&json, "[]", -1);
cgi_set_content(&json);
return;
}
blob_init(&json, 0, 0);
db_prepare(&q,
|
| ︙ | ︙ |
| ︙ | ︙ | |||
89 90 91 92 93 94 95 | ** It prints out what would have happened but does not actually make ** any changes to the current checkout or the repository. ** ** The -v or --verbose option prints status information about ** unchanged files in addition to those file that actually do change. ** ** Options: | | | | > | | | | > | | | > > > | > | 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 |
** It prints out what would have happened but does not actually make
** any changes to the current checkout or the repository.
**
** The -v or --verbose option prints status information about
** unchanged files in addition to those file that actually do change.
**
** Options:
** --case-sensitive BOOL Override case-sensitive setting
** --debug Print debug information on stdout
** --latest Acceptable in place of VERSION, update to
** latest version
** --force-missing Force update if missing content after sync
** -n|--dry-run If given, display instead of run actions
** -v|--verbose Print status information about all files
** -W|--width WIDTH Width of lines (default is to auto-detect).
** Must be more than 20 or 0 (= no limit,
** resulting in a single line per entry).
** --setmtime Set timestamps of all files to match their
** SCM-side times (the timestamp of the last
** checkin which modified them).
** -K|--keep-merge-files On merge conflict, retain the temporary files
** used for merging, named *-baseline, *-original,
** and *-merge.
**
** See also: revert
*/
void update_cmd(void){
int vid; /* Current version */
int tid=0; /* Target version - version we are changing to */
Stmt q;
int latestFlag; /* --latest. Pick the latest version if true */
int dryRunFlag; /* -n or --dry-run. Do a dry run */
int verboseFlag; /* -v or --verbose. Output extra information */
int forceMissingFlag; /* --force-missing. Continue if missing content */
int debugFlag; /* --debug option */
int setmtimeFlag; /* --setmtime. Set mtimes on files */
int keepMergeFlag; /* True if --keep-merge-files is present */
int nChng; /* Number of file renames */
int *aChng; /* Array of file renames */
int i; /* Loop counter */
int nConflict = 0; /* Number of merge conflicts */
int nOverwrite = 0; /* Number of unmanaged files overwritten */
int nUpdate = 0; /* Number of changes of any kind */
int width; /* Width of printed comment lines */
|
| ︙ | ︙ | |||
145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
if( !dryRunFlag ){
dryRunFlag = find_option("nochange",0,0)!=0; /* deprecated */
}
verboseFlag = find_option("verbose","v",0)!=0;
forceMissingFlag = find_option("force-missing",0,0)!=0;
debugFlag = find_option("debug",0,0)!=0;
setmtimeFlag = find_option("setmtime",0,0)!=0;
/* We should be done with options.. */
verify_all_options();
db_must_be_within_tree();
vid = db_lget_int("checkout", 0);
user_select();
| > | 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
if( !dryRunFlag ){
dryRunFlag = find_option("nochange",0,0)!=0; /* deprecated */
}
verboseFlag = find_option("verbose","v",0)!=0;
forceMissingFlag = find_option("force-missing",0,0)!=0;
debugFlag = find_option("debug",0,0)!=0;
setmtimeFlag = find_option("setmtime",0,0)!=0;
keepMergeFlag = find_option("keep-merge-files", "K",0)!=0;
/* We should be done with options.. */
verify_all_options();
db_must_be_within_tree();
vid = db_lget_int("checkout", 0);
user_select();
|
| ︙ | ︙ | |||
488 489 490 491 492 493 494 495 496 497 498 499 500 501 |
fossil_print("MERGE %s\n", zName);
}
if( islinkv || islinkt ){
fossil_print("***** Cannot merge symlink %s\n", zNewName);
nConflict++;
}else{
unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0;
if( !dryRunFlag && !internalUpdate ) undo_save(zName);
content_get(ridt, &t);
content_get(ridv, &v);
rc = merge_3way(&v, zFullPath, &t, &r, mergeFlags);
if( rc>=0 ){
if( !dryRunFlag ){
blob_write_to_file(&r, zFullNewPath);
| > | 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 |
fossil_print("MERGE %s\n", zName);
}
if( islinkv || islinkt ){
fossil_print("***** Cannot merge symlink %s\n", zNewName);
nConflict++;
}else{
unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0;
if(keepMergeFlag!=0) mergeFlags |= MERGE_KEEP_FILES;
if( !dryRunFlag && !internalUpdate ) undo_save(zName);
content_get(ridt, &t);
content_get(ridv, &v);
rc = merge_3way(&v, zFullPath, &t, &r, mergeFlags);
if( rc>=0 ){
if( !dryRunFlag ){
blob_write_to_file(&r, zFullNewPath);
|
| ︙ | ︙ | |||
675 676 677 678 679 680 681 |
int vid;
Manifest *pManifest;
/* Determine the check-in manifest artifact ID. Panic on failure. */
if( zRevision ){
vid = name_to_typed_rid(zRevision, "ci");
}else if( !g.localOpen ){
| | > | 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 |
int vid;
Manifest *pManifest;
/* Determine the check-in manifest artifact ID. Panic on failure. */
if( zRevision ){
vid = name_to_typed_rid(zRevision, "ci");
}else if( !g.localOpen ){
vid = name_to_typed_rid(db_get("main-branch", 0), "ci");
}else{
vid = db_lget_int("checkout", 0);
if( !is_a_version(vid) ){
if( vid==0 ) return 0;
zRevision = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
if( zRevision ){
fossil_fatal("checkout artifact is not a check-in: %s", zRevision);
}else{
fossil_fatal("invalid checkout artifact ID: %d", vid);
}
}
|
| ︙ | ︙ | |||
755 756 757 758 759 760 761 | /* Return 1 on success and (assuming fatal is not set) 0 if not found. */ return result; } /* ** COMMAND: revert ** | | | < > > > | > | > > | > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > | | | | | | | > > > > > > > > > > > | | 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 |
/* Return 1 on success and (assuming fatal is not set) 0 if not found. */
return result;
}
/*
** COMMAND: revert
**
** Usage: %fossil revert ?OPTIONS? ?FILE ...?
**
** Revert to the current repository version of FILE, or to
** the baseline VERSION specified with -r flag.
**
** If FILE was part of a rename operation, both the original file
** and the renamed file are reverted.
**
** Using a directory name for any of the FILE arguments is the same
** as using every subdirectory and file beneath that directory.
**
** Revert all files if no file name is provided.
**
** If a file is reverted accidentally, it can be restored using
** the "fossil undo" command.
**
** Options:
** -r|--revision VERSION Revert given FILE(s) back to given
** VERSION
**
** See also: redo, undo, checkout, update
*/
void revert_cmd(void){
Manifest *pCoManifest; /* Manifest of current checkout */
Manifest *pRvManifest; /* Manifest of selected revert version */
ManifestFile *pCoFile; /* File within current checkout manifest */
ManifestFile *pRvFile; /* File within revert version manifest */
const char *zFile; /* Filename relative to checkout root */
const char *zRevision; /* Selected revert version, NULL if current */
Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */
int i;
Stmt q;
int revertAll = 0;
int revisionOptNotSupported = 0;
undo_capture_command_line();
zRevision = find_option("revision", "r", 1);
verify_all_options();
if( g.argc<2 ){
usage("?OPTIONS? [FILE] ...");
}
if( zRevision && g.argc<3 ){
fossil_fatal("directories or the entire tree can only be reverted"
" back to current version");
}
db_must_be_within_tree();
/* Get manifests of revert version and (if different) current checkout. */
pRvManifest = historical_manifest(zRevision);
pCoManifest = zRevision ? historical_manifest(0) : 0;
db_begin_transaction();
undo_begin();
db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);");
if( g.argc>2 ){
for(i=2; i<g.argc; i++){
Blob fname;
zFile = mprintf("%/", g.argv[i]);
blob_zero(&fname);
file_tree_name(zFile, &fname, 0, 1);
if( blob_eq(&fname, ".") ){
if( zRevision ){
revisionOptNotSupported = 1;
break;
}
revertAll = 1;
break;
}else if( db_exists(
"SELECT pathname"
" FROM vfile"
" WHERE (substr(pathname,1,length('%q/'))='%q/'"
" OR substr(origname,1,length('%q/'))='%q/');",
blob_str(&fname), blob_str(&fname),
blob_str(&fname), blob_str(&fname)) ){
int vid;
vid = db_lget_int("checkout", 0);
vfile_check_signature(vid, 0);
if( zRevision ){
revisionOptNotSupported = 1;
break;
}
db_multi_exec(
"INSERT OR IGNORE INTO torevert"
" SELECT pathname"
" FROM vfile"
" WHERE (substr(pathname,1,length('%q/'))='%q/'"
" OR substr(origname,1,length('%q/'))='%q/')"
" AND (chnged OR deleted OR rid=0 OR pathname!=origname);",
blob_str(&fname), blob_str(&fname),
blob_str(&fname), blob_str(&fname)
);
}else{
db_multi_exec(
"REPLACE INTO torevert VALUES(%B);"
"INSERT OR IGNORE INTO torevert"
" SELECT pathname"
" FROM vfile"
" WHERE origname=%B;",
&fname, &fname
);
}
blob_reset(&fname);
}
}else{
revertAll = 1;
}
if( revisionOptNotSupported ){
fossil_fatal("directories or the entire tree can only be reverted"
" back to current version");
}
if ( revertAll ){
int vid;
vid = db_lget_int("checkout", 0);
vfile_check_signature(vid, 0);
db_multi_exec(
"DELETE FROM vmerge;"
"INSERT OR IGNORE INTO torevert "
" SELECT pathname"
" FROM vfile "
" WHERE chnged OR deleted OR rid=0 OR pathname!=origname;"
);
}
db_multi_exec(
"INSERT OR IGNORE INTO torevert"
" SELECT origname"
" FROM vfile"
" WHERE origname!=pathname AND pathname IN (SELECT name FROM torevert);"
);
blob_zero(&record);
db_prepare(&q, "SELECT name FROM torevert");
if( zRevision==0 ){
int vid = db_lget_int("checkout", 0);
zRevision = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
}
while( db_step(&q)==SQLITE_ROW ){
char *zFull;
zFile = db_column_text(&q, 0);
zFull = mprintf("%/%/", g.zLocalRoot, zFile);
pRvFile = pRvManifest? manifest_file_find(pRvManifest, zFile) : 0;
if( !pRvFile ){
if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q",
zFile, zFile)==0 ){
fossil_print("UNMANAGE %s\n", zFile);
}else{
undo_save(zFile);
file_delete(zFull);
|
| ︙ | ︙ |
| ︙ | ︙ | |||
47 48 49 50 51 52 53 |
*/
struct UrlData {
int isFile; /* True if a "file:" url */
int isHttps; /* True if a "https:" url */
int isSsh; /* True if an "ssh:" url */
char *name; /* Hostname for http: or filename for file: */
char *hostname; /* The HOST: parameter on http headers */
| | < < < < < < < < < < | 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 |
*/
struct UrlData {
int isFile; /* True if a "file:" url */
int isHttps; /* True if a "https:" url */
int isSsh; /* True if an "ssh:" url */
char *name; /* Hostname for http: or filename for file: */
char *hostname; /* The HOST: parameter on http headers */
const char *protocol; /* "http" or "https" or "ssh" */
int port; /* TCP port number for http: or https: */
int dfltPort; /* The default port for the given protocol */
char *path; /* Pathname for http: */
char *user; /* User id for http: */
char *passwd; /* Password for http: */
char *canonical; /* Canonical representation of the URL */
char *proxyAuth; /* Proxy-Authorizer: string */
char *fossil; /* The fossil query parameter on ssh: */
unsigned flags; /* Boolean flags controlling URL processing */
int useProxy; /* Used to remember that a proxy is in use */
char *proxyUrlPath;
int proxyOrigPort; /* Tunneled port number for https through proxy */
};
#endif /* INTERFACE */
/*
** Parse the given URL. Populate members of the provided UrlData structure
** as follows:
**
** isFile True if FILE:
** isHttps True if HTTPS:
** isSsh True if SSH:
|
| ︙ | ︙ | |||
175 176 177 178 179 180 181 |
n = strlen(pUrlData->name);
if( pUrlData->name[0]=='[' && n>2 && pUrlData->name[n-1]==']' ){
pUrlData->name++;
pUrlData->name[n-2] = 0;
}
zLogin = mprintf("");
}
| | | 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
n = strlen(pUrlData->name);
if( pUrlData->name[0]=='[' && n>2 && pUrlData->name[n-1]==']' ){
pUrlData->name++;
pUrlData->name[n-2] = 0;
}
zLogin = mprintf("");
}
fossil_strtolwr(pUrlData->name);
if( c==':' ){
pUrlData->port = 0;
i++;
while( (c = zUrl[i])!=0 && fossil_isdigit(c) ){
pUrlData->port = pUrlData->port*10 + c - '0';
i++;
}
|
| ︙ | ︙ | |||
368 369 370 371 372 373 374 | static const char *zProxyOpt = 0; /* ** Extract any proxy options from the command-line. ** ** --proxy URL|off ** | > | > > | | > > > > > > > > > > > | 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 |
static const char *zProxyOpt = 0;
/*
** Extract any proxy options from the command-line.
**
** --proxy URL|off
**
** The original purpose of this routine is the above. But this
** also happens to be a convenient place to look for other
** network-related options:
**
** --nosync Temporarily disable "autosync"
**
** --ipv4 Disallow IPv6. Use only IPv4.
**
** --accept-any-cert Disable server SSL cert validation. Accept
** any SSL cert that the server provides.
** WARNING: this option opens you up to
** forged-DNS and man-in-the-middle attacks!
*/
void url_proxy_options(void){
zProxyOpt = find_option("proxy", 0, 1);
if( find_option("nosync",0,0) ) g.fNoSync = 1;
if( find_option("ipv4",0,0) ) g.fIPv4 = 1;
#ifdef FOSSIL_ENABLE_SSL
if( find_option("accept-any-cert",0,0) ){
ssl_disable_cert_verification();
}
#endif /* FOSSIL_ENABLE_SSL */
}
/*
** If the "proxy" setting is defined, then change the URL settings
** (initialized by a prior call to url_parse()) so that the HTTP
** header will be appropriate for the proxy and so that the TCP/IP
** connection will be opened to the proxy rather than to the server.
|
| ︙ | ︙ |
| ︙ | ︙ | |||
329 330 331 332 333 334 335 | ** COMMAND: user* ** ** Usage: %fossil user SUBCOMMAND ... ?-R|--repository FILE? ** ** Run various subcommands on users of the open repository or of ** the repository identified by the -R or --repository option. ** | | | | | | | | | 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 |
** COMMAND: user*
**
** Usage: %fossil user SUBCOMMAND ... ?-R|--repository FILE?
**
** Run various subcommands on users of the open repository or of
** the repository identified by the -R or --repository option.
**
** > fossil user capabilities USERNAME ?STRING?
**
** Query or set the capabilities for user USERNAME
**
** > fossil user default ?USERNAME?
**
** Query or set the default user. The default user is the
** user for command-line interaction.
**
** > fossil user list
** > fossil user ls
**
** List all users known to the repository
**
** > fossil user new ?USERNAME? ?CONTACT-INFO? ?PASSWORD?
**
** 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 ?PASSWORD?
**
** Change the web access password for a user.
*/
void user_cmd(void){
int n;
db_find_and_open_repository(0, 0);
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, caps, contact;
char *zPw;
blob_init(&caps, db_get("default-perms", 0), -1);
if( g.argc>=4 ){
blob_init(&login, g.argv[3], -1);
}else{
prompt_user("login: ", &login);
}
if( db_exists("SELECT 1 FROM user WHERE login=%B", &login) ){
|
| ︙ | ︙ |
| ︙ | ︙ | |||
296 297 298 299 300 301 302 303 304 305 306 307 308 309 | sqlite3_free(pOld); #elif (defined(__APPLE__) && !defined(WITHOUT_ICONV)) || defined(__CYGWIN__) fossil_free(pOld); #else /* No-op on all other unix */ #endif } /* ** Display UTF-8 on the console. Return the number of ** Characters written. If stdout or stderr is redirected ** to a file, -1 is returned and nothing is written ** to the console. */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
sqlite3_free(pOld);
#elif (defined(__APPLE__) && !defined(WITHOUT_ICONV)) || defined(__CYGWIN__)
fossil_free(pOld);
#else
/* No-op on all other unix */
#endif
}
/*
** For a given index in a UTF-8 string, return the nearest index that is the
** start of a new code point. The returned index is equal or lower than the
** given index. The end of the string (the null-terminator) is considered a
** valid start index. The given index is returned unchanged if the string
** contains invalid UTF-8 (i.e. overlong runs of trail bytes).
** This function is useful to find code point boundaries for truncation, for
** example, so that no incomplete UTF-8 sequences are left at the end of the
** truncated string.
** This function does not attempt to keep logical and/or visual constructs
** spanning across multiple code points intact, that is no attempts are made
** keep combining characters together with their base characters, or to keep
** more complex grapheme clusters intact.
*/
#define IsUTF8TrailByte(c) ( (c&0xc0)==0x80 )
int utf8_nearest_codepoint(const char *zString, int maxByteIndex){
int i,n;
for( n=0, i=maxByteIndex; n<4 && i>=0; n++, i-- ){
if( !IsUTF8TrailByte(zString[i]) ) return i;
}
return maxByteIndex;
}
/*
** Find the byte index corresponding to the given code point index in a UTF-8
** string. If the string contains fewer than the given number of code points,
** the index of the end of the string (the null-terminator) is returned.
** Incomplete, ill-formed and overlong sequences are counted as one sequence.
** The invalid lead bytes 0xC0 to 0xC1 and 0xF5 to 0xF7 are allowed to initiate
** (ill-formed) 2- and 4-byte sequences, respectively, the other invalid lead
** bytes 0xF8 to 0xFF are treated as invalid 1-byte sequences (as lone trail
** bytes).
*/
int utf8_codepoint_index(const char *zString, int nCodePoint){
int i; /* Counted bytes. */
int lenUTF8; /* Counted UTF-8 sequences. */
if( zString==0 ) return 0;
for(i=0, lenUTF8=0; zString[i]!=0 && lenUTF8<nCodePoint; i++, lenUTF8++){
char c = zString[i];
int cchUTF8=1; /* Code units consumed. */
int maxUTF8=1; /* Expected sequence length. */
if( (c&0xe0)==0xc0 )maxUTF8=2; /* UTF-8 lead byte 110vvvvv */
else if( (c&0xf0)==0xe0 )maxUTF8=3; /* UTF-8 lead byte 1110vvvv */
else if( (c&0xf8)==0xf0 )maxUTF8=4; /* UTF-8 lead byte 11110vvv */
while( cchUTF8<maxUTF8 &&
(zString[i+1]&0xc0)==0x80 ){ /* UTF-8 trail byte 10vvvvvv */
cchUTF8++;
i++;
}
}
return i;
}
/*
** Display UTF-8 on the console. Return the number of
** Characters written. If stdout or stderr is redirected
** to a file, -1 is returned and nothing is written
** to the console.
*/
|
| ︙ | ︙ |
| ︙ | ︙ | |||
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 |
if( munmap(p, n) ){
fossil_panic("munmap failed: %d\n", errno);
}
#else
fossil_free(p);
#endif
}
/*
** This function implements a cross-platform "system()" interface.
*/
int fossil_system(const char *zOrigCmd){
int rc;
#if defined(_WIN32)
/* On windows, we have to put double-quotes around the entire command.
** Who knows why - this is just the way windows works.
*/
char *zNewCmd = mprintf("\"%s\"", zOrigCmd);
wchar_t *zUnicode = fossil_utf8_to_unicode(zNewCmd);
if( g.fSystemTrace ) {
fossil_trace("SYSTEM: %s\n", zNewCmd);
}
rc = _wsystem(zUnicode);
fossil_unicode_free(zUnicode);
free(zNewCmd);
#else
/* On unix, evaluate the command directly.
*/
if( g.fSystemTrace ) fprintf(stderr, "SYSTEM: %s\n", zOrigCmd);
/* Unix systems should never shell-out while processing an HTTP request,
** either via CGI, SCGI, or direct HTTP. The following assert verifies
** this. And the following assert proves that Fossil is not vulnerable
** to the ShellShock or BashDoor bug.
*/
assert( g.cgiOutput==0 );
/* The regular system() call works to get a shell on unix */
fossil_limit_memory(0);
rc = system(zOrigCmd);
fossil_limit_memory(1);
#endif
return rc;
}
/*
** Like strcmp() except that it accepts NULL pointers. NULL sorts before
** all non-NULL string pointers. Also, this strcmp() is a binary comparison
** that does not consider locale.
*/
int fossil_strcmp(const char *zA, const char *zB){
if( zA==0 ){
if( zB==0 ) return 0;
return -1;
}else if( zB==0 ){
return +1;
}else{
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < < < < < | | 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 |
if( munmap(p, n) ){
fossil_panic("munmap failed: %d\n", errno);
}
#else
fossil_free(p);
#endif
}
/*
** Translate every upper-case character in the input string into
** its equivalent lower-case.
*/
char *fossil_strtolwr(char *zIn){
char *zStart = zIn;
if( zIn ){
while( *zIn ){
*zIn = fossil_tolower(*zIn);
zIn++;
}
}
return zStart;
}
/*
** If this local variable is set, fossil_assert_safe_command_string()
** returns false on an unsafe command-string rather than abort. Set
** this variable for testing.
*/
static int safeCmdStrTest = 0;
/*
** Check the input string to ensure that it is safe to pass into system().
** A string is unsafe for system() on unix if it contains any of the following:
**
** * Any occurrance of '$' or '`' except after \
** * Any of the following characters, unquoted: ;|& or \n except
** these characters are allowed as the very last character in the
** string.
** * Unbalanced single or double quotes
**
** This routine is intended as a second line of defense against attack.
** It should never fail. Dangerous shell strings should be detected and
** fixed before calling fossil_system(). This routine serves only as a
** safety net in case of bugs elsewhere in the system.
**
** If an unsafe string is seen, either abort (default) or print
** a warning message (if safeCmdStrTest is true).
*/
static void fossil_assert_safe_command_string(const char *z){
int unsafe = 0;
#ifndef _WIN32
/* Unix */
int inQuote = 0;
int i, c;
for(i=0; !unsafe && (c = z[i])!=0; i++){
switch( c ){
case '$':
case '`': {
if( inQuote!='\'' ) unsafe = i+1;
break;
}
case ';':
case '|':
case '&':
case '\n': {
if( inQuote!='\'' && z[i+1]!=0 ) unsafe = i+1;
break;
}
case '"':
case '\'': {
if( inQuote==0 ){
inQuote = c;
}else if( inQuote==c ){
inQuote = 0;
}
break;
}
case '\\': {
if( z[i+1]==0 ){
unsafe = i+1;
}else if( inQuote!='\'' ){
i++;
}
break;
}
}
}
if( inQuote ) unsafe = i;
#else
/* Windows */
int i, c;
int inQuote = 0;
for(i=0; !unsafe && (c = z[i])!=0; i++){
switch( c ){
case '>':
case '<':
case '|':
case '&':
case '\n': {
if( inQuote==0 && z[i+1]!=0 ) unsafe = i+1;
break;
}
case '\\': {
if( z[i+1]=='"' ){ i++; }
break;
}
case '"': {
if( inQuote==c ){
inQuote = 0;
}else{
inQuote = c;
}
break;
}
case '^': {
if( z[i+1]=='"' ){
unsafe = i+2;
}else if( z[i+1]!=0 ){
i++;
}
break;
}
}
}
if( inQuote ) unsafe = i;
#endif
if( unsafe ){
char *zMsg = mprintf("Unsafe command string: %s\n%*shere ----^",
z, unsafe+13, "");
if( safeCmdStrTest ){
fossil_print("%z\n", zMsg);
}else{
fossil_panic("%s", zMsg);
}
}
}
/*
** This function implements a cross-platform "system()" interface.
*/
int fossil_system(const char *zOrigCmd){
int rc;
#if defined(_WIN32)
/* On windows, we have to put double-quotes around the entire command.
** Who knows why - this is just the way windows works.
*/
char *zNewCmd = mprintf("\"%s\"", zOrigCmd);
wchar_t *zUnicode = fossil_utf8_to_unicode(zNewCmd);
if( g.fSystemTrace ) {
fossil_trace("SYSTEM: %s\n", zNewCmd);
}
fossil_assert_safe_command_string(zOrigCmd);
rc = _wsystem(zUnicode);
fossil_unicode_free(zUnicode);
free(zNewCmd);
#else
/* On unix, evaluate the command directly.
*/
if( g.fSystemTrace ) fprintf(stderr, "SYSTEM: %s\n", zOrigCmd);
fossil_assert_safe_command_string(zOrigCmd);
/* Unix systems should never shell-out while processing an HTTP request,
** either via CGI, SCGI, or direct HTTP. The following assert verifies
** this. And the following assert proves that Fossil is not vulnerable
** to the ShellShock or BashDoor bug.
*/
assert( g.cgiOutput==0 );
/* The regular system() call works to get a shell on unix */
fossil_limit_memory(0);
rc = system(zOrigCmd);
fossil_limit_memory(1);
#endif
return rc;
}
/*
** COMMAND: test-fossil-system
**
** Read lines of input and send them to fossil_system() for evaluation.
** Use this command to verify that fossil_system() will not run "unsafe"
** commands.
*/
void test_fossil_system_cmd(void){
char zLine[10000];
safeCmdStrTest = 1;
while(1){
size_t n;
printf("system-test> ");
fflush(stdout);
if( !fgets(zLine, sizeof(zLine), stdin) ) break;
n = strlen(zLine);
while( n>0 && fossil_isspace(zLine[n-1]) ) n--;
zLine[n] = 0;
printf("cmd: [%s]\n", zLine);
fflush(stdout);
fossil_system(zLine);
}
}
/*
** Like strcmp() except that it accepts NULL pointers. NULL sorts before
** all non-NULL string pointers. Also, this strcmp() is a binary comparison
** that does not consider locale.
*/
int fossil_strcmp(const char *zA, const char *zB){
if( zA==0 ){
if( zB==0 ) return 0;
return -1;
}else if( zB==0 ){
return +1;
}else{
return strcmp(zA,zB);
}
}
int fossil_strncmp(const char *zA, const char *zB, int nByte){
if( zA==0 ){
if( zB==0 ) return 0;
return -1;
}else if( zB==0 ){
|
| ︙ | ︙ | |||
404 405 406 407 408 409 410 | #endif } /* ** Returns TRUE if zSym is exactly HNAME_LEN_SHA1 or HNAME_LEN_K256 ** bytes long and contains only lower-case ASCII hexadecimal values. */ | | | 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 |
#endif
}
/*
** Returns TRUE if zSym is exactly HNAME_LEN_SHA1 or HNAME_LEN_K256
** bytes long and contains only lower-case ASCII hexadecimal values.
*/
int fossil_is_artifact_hash(const char *zSym){
int sz = zSym ? (int)strlen(zSym) : 0;
return (HNAME_LEN_SHA1==sz || HNAME_LEN_K256==sz) && validate16(zSym, sz);
}
/*
** Return true if the input string is NULL or all whitespace.
** Return false if the input string contains text.
|
| ︙ | ︙ | |||
523 524 525 526 527 528 529 |
void fossil_pledge(const char *promises){
if( pledge(promises, 0) ){
fossil_panic("pledge(\"%s\",NULL) fails with errno=%d",
promises, (int)errno);
}
}
#endif /* defined(HAVE_PLEDGE) */
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
void fossil_pledge(const char *promises){
if( pledge(promises, 0) ){
fossil_panic("pledge(\"%s\",NULL) fails with errno=%d",
promises, (int)errno);
}
}
#endif /* defined(HAVE_PLEDGE) */
/*
** Construct a random password and return it as a string. N is the
** recommended number of characters for the password.
**
** Space to hold the returned string is obtained from fossil_malloc()
** and should be freed by the caller.
*/
char *fossil_random_password(int N){
char zSrc[60];
int nSrc;
int i;
char z[60];
/* Source characters for the password. Omit characters like "0", "O",
** "1" and "I" that might be easily confused */
static const char zAlphabet[] =
/* 0 1 2 3 4 5 */
/* 123456789 123456789 123456789 123456789 123456789 123456 */
"23456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
if( N<8 ) N = 8;
else if( N>sizeof(zAlphabet)-2 ) N = sizeof(zAlphabet)-2;
nSrc = sizeof(zAlphabet) - 1;
memcpy(zSrc, zAlphabet, nSrc);
for(i=0; i<N; i++){
unsigned r;
sqlite3_randomness(sizeof(r), &r);
r %= nSrc;
z[i] = zSrc[r];
zSrc[r] = zSrc[--nSrc];
}
z[i] = 0;
return fossil_strdup(z);
}
/*
** COMMAND: test-random-password
**
** Usage: %fossil test-random-password ?N?
**
** Generate a random password string of approximately N characters in length.
** If N is omitted, use 10. Values of N less than 8 are changed to 8
** and greater than 55 and changed to 55.
*/
void test_random_password(void){
int N = 10;
if( g.argc>=3 ){
N = atoi(g.argv[2]);
}
fossil_print("%s\n", fossil_random_password(N));
}
|
| ︙ | ︙ | |||
66 67 68 69 70 71 72 |
** This routine is called just prior to each commit operation.
**
** Invoke verify_rid() on every record that has been added or modified
** in the repository, in order to make sure that the repository is sane.
*/
static int verify_at_commit(void){
int rid;
| | | 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
** This routine is called just prior to each commit operation.
**
** Invoke verify_rid() on every record that has been added or modified
** in the repository, in order to make sure that the repository is sane.
*/
static int verify_at_commit(void){
int rid;
content_clear_cache(0);
inFinalVerify = 1;
rid = bag_first(&toVerify);
while( rid>0 ){
verify_rid(rid);
rid = bag_next(&toVerify, rid);
}
bag_clear(&toVerify);
|
| ︙ | ︙ |
| ︙ | ︙ | |||
1057 1058 1059 1060 1061 1062 1063 |
"WITH allrid(x) AS ("
" SELECT rid FROM vfile"
" UNION SELECT mrid FROM vfile"
" UNION SELECT merge FROM vmerge"
" UNION SELECT %d"
")"
"SELECT group_concat(x,' ') FROM allrid"
| | | 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 |
"WITH allrid(x) AS ("
" SELECT rid FROM vfile"
" UNION SELECT mrid FROM vfile"
" UNION SELECT merge FROM vmerge"
" UNION SELECT %d"
")"
"SELECT group_concat(x,' ') FROM allrid"
" WHERE x<>0 AND x NOT IN (SELECT oldrid FROM idMap);",
oldVid
);
if( zUnresolved[0] ){
fossil_fatal("Unresolved RID values: %s\n", zUnresolved);
}
/* Make the changes to the VFILE and VMERGE tables */
|
| ︙ | ︙ |
| ︙ | ︙ | |||
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
/*
** Render wiki text according to its mimetype.
**
** text/x-fossil-wiki Fossil wiki
** text/x-markdown Markdown
** anything else... Plain text
*/
void wiki_render_by_mimetype(Blob *pWiki, const char *zMimetype){
if( zMimetype==0 || fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
wiki_convert(pWiki, 0, 0);
}else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
Blob tail = BLOB_INITIALIZER;
markdown_to_html(pWiki, 0, &tail);
@ %s(blob_str(&tail))
blob_reset(&tail);
}else{
@ <pre class='textPlain'>
@ %h(blob_str(pWiki))
@ </pre>
}
| > > > | 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
/*
** Render wiki text according to its mimetype.
**
** text/x-fossil-wiki Fossil wiki
** text/x-markdown Markdown
** anything else... Plain text
**
** If zMimetype is a null pointer, then use "text/x-fossil-wiki".
*/
void wiki_render_by_mimetype(Blob *pWiki, const char *zMimetype){
if( zMimetype==0 || fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
wiki_convert(pWiki, 0, 0);
}else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
Blob tail = BLOB_INITIALIZER;
markdown_to_html(pWiki, 0, &tail);
safe_html(&tail);
@ %s(blob_str(&tail))
blob_reset(&tail);
}else{
@ <pre class='textPlain'>
@ %h(blob_str(pWiki))
@ </pre>
}
|
| ︙ | ︙ | |||
218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
if( fTxt ){
style_submenu_element("Formatted", "%R/md_rules");
}else{
style_submenu_element("Plain-Text", "%R/md_rules?txt=1");
}
blob_init(&x, builtin_text("markdown.md"), -1);
blob_materialize(&x);
wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-markdown");
blob_reset(&x);
style_footer();
}
/*
** WEBPAGE: wiki_rules
| > | 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
if( fTxt ){
style_submenu_element("Formatted", "%R/md_rules");
}else{
style_submenu_element("Plain-Text", "%R/md_rules?txt=1");
}
blob_init(&x, builtin_text("markdown.md"), -1);
blob_materialize(&x);
safe_html_context(DOCSRC_TRUSTED);
wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-markdown");
blob_reset(&x);
style_footer();
}
/*
** WEBPAGE: wiki_rules
|
| ︙ | ︙ | |||
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
if( fTxt ){
style_submenu_element("Formatted", "%R/wiki_rules");
}else{
style_submenu_element("Plain-Text", "%R/wiki_rules?txt=1");
}
blob_init(&x, builtin_text("wiki.wiki"), -1);
blob_materialize(&x);
wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-fossil-wiki");
blob_reset(&x);
style_footer();
}
/*
** Returns non-zero if moderation is required for wiki changes and wiki
** attachments.
*/
int wiki_need_moderation(
int localUser /* Are we being called for a local interactive user? */
| > > > > > > > > > > > > > > > | 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 |
if( fTxt ){
style_submenu_element("Formatted", "%R/wiki_rules");
}else{
style_submenu_element("Plain-Text", "%R/wiki_rules?txt=1");
}
blob_init(&x, builtin_text("wiki.wiki"), -1);
blob_materialize(&x);
safe_html_context(DOCSRC_TRUSTED);
wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-fossil-wiki");
blob_reset(&x);
style_footer();
}
/*
** WEBPAGE: markup_help
**
** Show links to the md_rules and wiki_rules pages.
*/
void markup_help_page(void){
style_header("Fossil Markup Styles");
@ <ul>
@ <li><p>%z(href("%R/wiki_rules"))Fossil Wiki Formatting Rules</a></p></li>
@ <li><p>%z(href("%R/md_rules"))Markdown Formatting Rules</a></p></li>
@ </ul>
style_footer();
}
/*
** Returns non-zero if moderation is required for wiki changes and wiki
** attachments.
*/
int wiki_need_moderation(
int localUser /* Are we being called for a local interactive user? */
|
| ︙ | ︙ | |||
349 350 351 352 353 354 355 |
style_header("Wiki Search");
wiki_standard_submenu(W_HELP|W_LIST|W_SANDBOX);
search_screen(SRCH_WIKI, 0);
style_footer();
}
/* Return values from wiki_page_type() */
| > | | | | | > | | > > > > > > | | > > > | | | > > > > | | > > > > | | > | 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 |
style_header("Wiki Search");
wiki_standard_submenu(W_HELP|W_LIST|W_SANDBOX);
search_screen(SRCH_WIKI, 0);
style_footer();
}
/* Return values from wiki_page_type() */
#if INTERFACE
# define WIKITYPE_UNKNOWN (-1)
# define WIKITYPE_NORMAL 0
# define WIKITYPE_BRANCH 1
# define WIKITYPE_CHECKIN 2
# define WIKITYPE_TAG 3
#endif
/*
** Figure out what type of wiki page we are dealing with.
*/
int wiki_page_type(const char *zPageName){
if( db_get_boolean("wiki-about",1)==0 ){
return WIKITYPE_NORMAL;
}else
if( sqlite3_strglob("checkin/*", zPageName)==0
&& db_exists("SELECT 1 FROM blob WHERE uuid=%Q",zPageName+8)
){
return WIKITYPE_CHECKIN;
}else
if( sqlite3_strglob("branch/*", zPageName)==0 ){
return WIKITYPE_BRANCH;
}else
if( sqlite3_strglob("tag/*", zPageName)==0 ){
return WIKITYPE_TAG;
}
return WIKITYPE_NORMAL;
}
/*
** Add an appropriate style_header() for either the /wiki or /wikiedit page
** for zPageName. zExtra is an empty string for /wiki but has the text
** "Edit: " for /wikiedit.
**
** If the page is /wiki and the page is one of the special times (check-in,
** branch, or tag) and the "p" query parameter is omitted, then do a
** redirect to the display of the check-in, branch, or tag rather than
** continuing to the plain wiki display.
*/
static int wiki_page_header(
int eType, /* Page type. Might be WIKITYPE_UNKNOWN */
const char *zPageName, /* Name of the page */
const char *zExtra /* Extra prefix text on the page header */
){
if( eType==WIKITYPE_UNKNOWN ) eType = wiki_page_type(zPageName);
switch( eType ){
case WIKITYPE_NORMAL: {
style_header("%s%s", zExtra, zPageName);
break;
}
case WIKITYPE_CHECKIN: {
zPageName += 8;
if( zExtra[0]==0 && !P("p") ){
cgi_redirectf("%R/info/%s",zPageName);
}else{
style_header("Notes About Checkin %S", zPageName);
style_submenu_element("Checkin Timeline","%R/timeline?f=%s", zPageName);
style_submenu_element("Checkin Info","%R/info/%s", zPageName);
}
break;
}
case WIKITYPE_BRANCH: {
zPageName += 7;
if( zExtra[0]==0 && !P("p") ){
cgi_redirectf("%R/timeline?r=%t", zPageName);
}else{
style_header("Notes About Branch %h", zPageName);
style_submenu_element("Branch Timeline","%R/timeline?r=%t", zPageName);
}
break;
}
case WIKITYPE_TAG: {
zPageName += 4;
if( zExtra[0]==0 && !P("p") ){
cgi_redirectf("%R/timeline?t=%t",zPageName);
}else{
style_header("Notes About Tag %h", zPageName);
style_submenu_element("Tag Timeline","%R/timeline?t=%t",zPageName);
}
break;
}
}
return eType;
}
/*
|
| ︙ | ︙ | |||
436 437 438 439 440 441 442 |
return 1;
}
return g.perm.Write;
}
/*
** WEBPAGE: wiki
| > | > > > > > > > > > > | 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 |
return 1;
}
return g.perm.Write;
}
/*
** WEBPAGE: wiki
**
** Display a wiki page. Example: /wiki?name=PAGENAME
**
** Query parameters:
**
** name=NAME Name of the wiki page to display. Required.
** nsm Omit the submenu if present. (Mnemonic: No SubMenu)
** p Always show just the wiki page. For special
** pages for check-ins, branches, or tags, there will
** be a redirect to the associated /info page unless
** this query parameter is present.
*/
void wiki_page(void){
char *zTag;
int rid = 0;
int isSandbox;
unsigned submenuFlags = W_HELP;
Blob wiki;
Manifest *pWiki = 0;
const char *zPageName;
const char *zMimetype = 0;
char *zBody = mprintf("%s","<i>Empty Page</i>");
int noSubmenu = P("nsm")!=0;
login_check_credentials();
if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
zPageName = P("name");
if( zPageName==0 ){
if( search_restrict(SRCH_WIKI)!=0 ){
wiki_srchpage();
|
| ︙ | ︙ | |||
485 486 487 488 489 490 491 |
pWiki = manifest_get(rid, CFTYPE_WIKI, 0);
if( pWiki ){
zBody = pWiki->zWiki;
zMimetype = pWiki->zMimetype;
}
}
zMimetype = wiki_filter_mimetypes(zMimetype);
| | > | > > | | 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 |
pWiki = manifest_get(rid, CFTYPE_WIKI, 0);
if( pWiki ){
zBody = pWiki->zWiki;
zMimetype = pWiki->zMimetype;
}
}
zMimetype = wiki_filter_mimetypes(zMimetype);
if( !g.isHome && !noSubmenu ){
if( ((rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki))
&& wiki_special_permission(zPageName)
){
if( db_get_boolean("wysiwyg-wiki", 0) ){
style_submenu_element("Edit", "%R/wikiedit?name=%T&wysiwyg=1",
zPageName);
}else{
style_submenu_element("Edit", "%R/wikiedit?name=%T", zPageName);
}
}else if( rid && g.perm.ApndWiki ){
style_submenu_element("Edit", "%R/wikiappend?name=%T", zPageName);
}
if( g.perm.Hyperlink ){
style_submenu_element("History", "%R/whistory?name=%T", zPageName);
}
}
style_set_current_page("%T?name=%T", g.zPath, zPageName);
wiki_page_header(WIKITYPE_UNKNOWN, zPageName, "");
if( !noSubmenu ){
wiki_standard_submenu(submenuFlags);
}
if( zBody[0]==0 ){
@ <i>This page has been deleted</i>
}else{
blob_init(&wiki, zBody, -1);
safe_html_context(DOCSRC_WIKI);
wiki_render_by_mimetype(&wiki, zMimetype);
blob_reset(&wiki);
}
attachment_list(zPageName, "<hr /><h2>Attachments:</h2><ul>");
manifest_destroy(pWiki);
style_footer();
}
/*
** Write a wiki artifact into the repository
*/
int wiki_put(Blob *pWiki, int parent, int needMod){
int nrid;
if( !needMod ){
nrid = content_put_ex(pWiki, 0, 0, 0, 0);
if( parent ) content_deltify(parent, &nrid, 1, 0);
}else{
nrid = content_put_ex(pWiki, 0, 0, 0, 1);
moderation_table_create();
db_multi_exec("INSERT INTO modreq(objid) VALUES(%d)", nrid);
}
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid);
|
| ︙ | ︙ | |||
696 697 698 699 700 701 702 703 704 705 706 707 708 709 |
blob_zero(&wiki);
while( fossil_isspace(zBody[0]) ) zBody++;
blob_append(&wiki, zBody, -1);
if( P("preview")!=0 ){
havePreview = 1;
if( zBody[0] ){
@ Preview:<hr />
wiki_render_by_mimetype(&wiki, zMimetype);
@ <hr />
blob_reset(&wiki);
}
}
for(n=2, z=zBody; z[0]; z++){
if( z[0]=='\n' ) n++;
| > | 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 |
blob_zero(&wiki);
while( fossil_isspace(zBody[0]) ) zBody++;
blob_append(&wiki, zBody, -1);
if( P("preview")!=0 ){
havePreview = 1;
if( zBody[0] ){
@ Preview:<hr />
safe_html_context(DOCSRC_WIKI);
wiki_render_by_mimetype(&wiki, zMimetype);
@ <hr />
blob_reset(&wiki);
}
}
for(n=2, z=zBody; z[0]; z++){
if( z[0]=='\n' ) n++;
|
| ︙ | ︙ | |||
728 729 730 731 732 733 734 |
}
case WIKITYPE_TAG: {
zPlaceholder = mprintf("Enter notes about tag %s", zPageName+4);
break;
}
}
form_begin(0, "%R/wikiedit");
| | | 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 |
}
case WIKITYPE_TAG: {
zPlaceholder = mprintf("Enter notes about tag %s", zPageName+4);
break;
}
}
form_begin(0, "%R/wikiedit");
@ <div>%z(href("%R/markup_help"))Markup style</a>:
mimetype_option_menu(zMimetype);
@ <br /><textarea name="w" class="wikiedit" cols="80" \
@ rows="%d(n)" wrap="virtual" placeholder="%h(zPlaceholder)">\
@ %h(zBody)</textarea>
@ <br />
fossil_free(zPlaceholder);
if( db_get_boolean("wysiwyg-wiki", 0) ){
|
| ︙ | ︙ | |||
810 811 812 813 814 815 816 |
style_header("Create A New Wiki Page");
wiki_standard_submenu(W_ALL_BUT(W_NEW));
@ <p>Rules for wiki page names:</p>
well_formed_wiki_name_rules();
form_begin(0, "%R/wikinew");
@ <p>Name of new wiki page:
@ <input style="width: 35;" type="text" name="name" value="%h(zName)" /><br />
| | | 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 |
style_header("Create A New Wiki Page");
wiki_standard_submenu(W_ALL_BUT(W_NEW));
@ <p>Rules for wiki page names:</p>
well_formed_wiki_name_rules();
form_begin(0, "%R/wikinew");
@ <p>Name of new wiki page:
@ <input style="width: 35;" type="text" name="name" value="%h(zName)" /><br />
@ %z(href("%R/markup_help"))Markup style</a>:
mimetype_option_menu("text/x-fossil-wiki");
@ <br /><input type="submit" value="Create" />
@ </p></form>
if( zName[0] ){
@ <p><span class="wikiError">
@ "%h(zName)" is not a valid wiki page name!</span></p>
}
|
| ︙ | ︙ | |||
958 959 960 961 962 963 964 965 966 967 968 969 970 971 |
@ <p class="generalError">Error: Incorrect security code.</p>
}
if( P("preview")!=0 ){
Blob preview;
blob_zero(&preview);
appendRemark(&preview, zMimetype);
@ Preview:<hr />
wiki_render_by_mimetype(&preview, zMimetype);
@ <hr />
blob_reset(&preview);
}
zUser = PD("u", g.zLogin);
form_begin(0, "%R/wikiappend");
login_insert_csrf_secret();
| > | 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 |
@ <p class="generalError">Error: Incorrect security code.</p>
}
if( P("preview")!=0 ){
Blob preview;
blob_zero(&preview);
appendRemark(&preview, zMimetype);
@ Preview:<hr />
safe_html_context(DOCSRC_WIKI);
wiki_render_by_mimetype(&preview, zMimetype);
@ <hr />
blob_reset(&preview);
}
zUser = PD("u", g.zLogin);
form_begin(0, "%R/wikiappend");
login_insert_csrf_secret();
|
| ︙ | ︙ | |||
1208 1209 1210 1211 1212 1213 1214 |
zWDisplayName = mprintf("%s", zWName);
}
if( wrid==0 ){
if( !showAll ) continue;
@ <tr><td data-sortkey="%h(zSort)">\
@ %z(href("%R/whistory?name=%T",zWName))<s>%h(zWDisplayName)</s></a></td>
}else{
| | | 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 |
zWDisplayName = mprintf("%s", zWName);
}
if( wrid==0 ){
if( !showAll ) continue;
@ <tr><td data-sortkey="%h(zSort)">\
@ %z(href("%R/whistory?name=%T",zWName))<s>%h(zWDisplayName)</s></a></td>
}else{
@ <tr><td data-sortkey="%h(zSort)">\
@ %z(href("%R/wiki?name=%T",zWName))%h(zWDisplayName)</a></td>
}
zAge = human_readable_age(rNow - rWmtime);
@ <td data-sortkey="%016llx(iMtime)">%s(zAge)</td>
fossil_free(zAge);
@ <td>%z(href("%R/whistory?name=%T",zWName))%d(wcnt)</a></td>
if( showRid ){
|
| ︙ | ︙ | |||
1348 1349 1350 1351 1352 1353 1354 | /* ** COMMAND: wiki* ** ** Usage: %fossil wiki (export|create|commit|list) WikiName ** ** Run various subcommands to work with wiki entries or tech notes. ** | | | | | > > | > | > > > | | > > > > > > > | | 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 | /* ** COMMAND: wiki* ** ** Usage: %fossil wiki (export|create|commit|list) WikiName ** ** Run various subcommands to work with wiki entries or tech notes. ** ** > fossil wiki export ?OPTIONS? PAGENAME ?FILE? ** > fossil wiki export ?OPTIONS? -t|--technote DATETIME|TECHNOTE-ID ?FILE? ** ** Sends the latest version of either a wiki page or of a tech ** note to the given file or standard output. A filename of "-" ** writes the output to standard output. The directory parts of ** the output filename are created if needed. ** If PAGENAME is provided, the named wiki page will be output. ** ** Options: ** --technote|-t DATETIME|TECHNOTE-ID ** Specifies that a technote, rather than a wiki page, ** will be exported. If DATETIME is used, the most ** recently modified tech note with that DATETIME will ** output. ** -h|--html The body (only) is rendered in HTML form, without ** any page header/foot or HTML/BODY tag wrappers. ** -H|--HTML Works like -h|-html but wraps the output in ** <html><body>...</body></html>. ** -p|--pre If -h|-H is used and the page or technote has ** the text/plain mimetype, its HTML-escaped output ** will be wrapped in <pre>...</pre>. ** ** > fossil wiki (create|commit) PAGENAME ?FILE? ?OPTIONS? ** ** Create a new or commit changes to an existing wiki page or ** technote from FILE or from standard input. PAGENAME is the ** name of the wiki entry or the timeline comment of the ** technote. ** ** Options: |
| ︙ | ︙ | |||
1386 1387 1388 1389 1390 1391 1392 | ** updated. ** -t|--technote TECHNOTE-ID Specifies the technote to be ** updated by its technote id. ** --technote-tags TAGS The set of tags for a technote. ** --technote-bgcolor COLOR The color used for the technote ** on the timeline. ** | | | | 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 | ** updated. ** -t|--technote TECHNOTE-ID Specifies the technote to be ** updated by its technote id. ** --technote-tags TAGS The set of tags for a technote. ** --technote-bgcolor COLOR The color used for the technote ** on the timeline. ** ** > fossil wiki list ?OPTIONS? ** > fossil wiki ls ?OPTIONS? ** ** Lists all wiki entries, one per line, ordered ** case-insensitively by name. ** ** Options: ** -t|--technote Technotes will be listed instead of ** pages. The technotes will be in order |
| ︙ | ︙ | |||
1428 1429 1430 1431 1432 1433 1434 |
if( strncmp(g.argv[2],"export",n)==0 ){
const char *zPageName; /* Name of the wiki page to export */
const char *zFile; /* Name of the output file (0=stdout) */
const char *zETime; /* The name of the technote to export */
int rid; /* Artifact ID of the wiki page */
int i; /* Loop counter */
char *zBody = 0; /* Wiki page content */
| | | > > > > > > > > > > | > | > | > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < < | | | | | | > | < < | | | > | > > > > | | < | | | 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 |
if( strncmp(g.argv[2],"export",n)==0 ){
const char *zPageName; /* Name of the wiki page to export */
const char *zFile; /* Name of the output file (0=stdout) */
const char *zETime; /* The name of the technote to export */
int rid; /* Artifact ID of the wiki page */
int i; /* Loop counter */
char *zBody = 0; /* Wiki page content */
Blob body = empty_blob; /* Wiki page content */
Manifest *pWiki = 0; /* Parsed wiki page content */
int fHtml = 0; /* Export in HTML form */
FILE * pFile = 0; /* Output file */
int fPre = 0; /* Indicates that -h|-H should be
** wrapped in <pre>...</pre> if pWiki
** has the text/plain mimetype. */
fHtml = find_option("HTML","H",0)!=0
? 2
: (find_option("html","h",0)!=0 ? 1 : 0)
/* 1 == -html, 2 == -HTML */;
fPre = fHtml==0 ? 0 : find_option("pre","p",0)!=0;
zETime = find_option("technote","t",1);
verify_all_options();
if( !zETime ){
if( (g.argc!=4) && (g.argc!=5) ){
usage("export ?-html? PAGENAME ?FILE?");
}
zPageName = g.argv[3];
rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
" WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
" ORDER BY x.mtime DESC LIMIT 1",
zPageName
);
if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
zBody = pWiki->zWiki;
}
if( zBody==0 ){
fossil_fatal("wiki page [%s] not found",zPageName);
}
zFile = (g.argc==4) ? "-" : g.argv[4];
}else{
if( (g.argc!=3) && (g.argc!=4) ){
usage("export ?-html? ?FILE? --technote "
"DATETIME|TECHNOTE-ID");
}
rid = wiki_technote_to_rid(zETime);
if ( rid==-1 ){
fossil_fatal("ambiguous tech note id: %s", zETime);
}
if( (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0 ){
zBody = pWiki->zWiki;
}
if( zBody==0 ){
fossil_fatal("technote [%s] not found",zETime);
}
zFile = (g.argc==3) ? "-" : g.argv[3];
}
for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){}
zBody[i] = 0;
blob_init(&body, zBody, -1);
if(fHtml==0){
blob_append(&body, "\n", 1);
}else{
Blob html = empty_blob; /* HTML-ized content */
const char * zMimetype = wiki_filter_mimetypes(pWiki->zMimetype);
if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
wiki_convert(&body,&html,0);
}else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
markdown_to_html(&body,0,&html)
/* TODO: add -HTML|-H flag to work like -html|-h but also
** add <html><body> tag wrappers around the output. The
** hurdle here is that the markdown converter resets its
** input blob before appending the output, which is
** different from wiki_convert() and htmlize_to_blob(), and
** precludes us simply appending the opening <html><body>
** part to the body
*/;
safe_html_context(DOCSRC_WIKI);
safe_html(&html);
}else if( fossil_strcmp(zMimetype, "text/plain")==0 ){
htmlize_to_blob(&html,zBody,i);
}else{
fossil_fatal("Unsupported MIME type '%s' for wiki page '%s'.",
zMimetype, pWiki->zWikiTitle );
}
blob_reset(&body);
body = html /* transfer memory */;
}
pFile = fossil_fopen_for_output(zFile);
if(fHtml==2){
fwrite("<html><body>", 1, 12, pFile);
}
if(fPre!=0){
fwrite("<pre>", 1, 5, pFile);
}
fwrite(blob_buffer(&body), 1, blob_size(&body), pFile);
if(fPre!=0){
fwrite("</pre>", 1, 6, pFile);
}
if(fHtml==2){
fwrite("</body></html>\n", 1, 15, pFile);
}
fossil_fclose(pFile);
blob_reset(&body);
manifest_destroy(pWiki);
return;
}else if( strncmp(g.argv[2],"commit",n)==0
|| strncmp(g.argv[2],"create",n)==0 ){
const char *zPageName; /* page name */
Blob content; /* Input content */
int rid = 0;
Manifest *pWiki = 0; /* Parsed wiki page content */
const int isCreate = 'r'==g.argv[2][1] /* else "commit" */;
const char *zMimeType = find_option("mimetype", "M", 1);
const char *zETime = find_option("technote", "t", 1);
const char *zTags = find_option("technote-tags", NULL, 1);
const char *zClr = find_option("technote-bgcolor", NULL, 1);
verify_all_options();
if( g.argc!=4 && g.argc!=5 ){
usage("commit|create PAGENAME ?FILE? [--mimetype TEXT-FORMAT]"
" [--technote DATETIME] [--technote-tags TAGS]"
" [--technote-bgcolor COLOR]");
}
zPageName = g.argv[3];
if( g.argc==4 ){
blob_read_from_channel(&content, stdin, -1);
}else{
blob_read_from_file(&content, g.argv[4], ExtFILE);
}
if ( !zETime ){
rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
" WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
" ORDER BY x.mtime DESC LIMIT 1",
zPageName
);
if( rid>0 ){
pWiki = manifest_get(rid, CFTYPE_WIKI, 0);
}
}else{
rid = wiki_technote_to_rid(zETime);
if( rid>0 ){
pWiki = manifest_get(rid, CFTYPE_EVENT, 0);
}
}
if( !zMimeType || !*zMimeType ){
/* Try to deduce the mime type based on the prior version. */
if( pWiki!=0 && (pWiki->zMimetype && *pWiki->zMimetype) ){
zMimeType = pWiki->zMimetype;
}
}else{
zMimeType = wiki_filter_mimetypes(zMimeType);
}
if( isCreate && rid>0 ){
if ( !zETime ){
fossil_fatal("wiki page %s already exists", zPageName);
}else{
/* Creating a tech note with same timestamp is permitted
and should create a new tech note */
rid = 0;
}
}else if( !isCreate && rid == 0 ){
if ( !zETime ){
fossil_fatal("no such wiki page: %s", zPageName);
}else{
fossil_fatal("no such tech note: %s", zETime);
}
}
|
| ︙ | ︙ | |||
1559 1560 1561 1562 1563 1564 1565 |
}else{
fossil_fatal("ambiguous tech note id: %s", zETime);
}
}
manifest_destroy(pWiki);
blob_reset(&content);
}else if( strncmp(g.argv[2],"delete",n)==0 ){
| | > | | | < < < < < < < < < < < < < < < < < < < < | | | | | | 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 |
}else{
fossil_fatal("ambiguous tech note id: %s", zETime);
}
}
manifest_destroy(pWiki);
blob_reset(&content);
}else if( strncmp(g.argv[2],"delete",n)==0 ){
if( g.argc!=4 ){
usage("delete PAGENAME");
}
fossil_fatal("delete not yet implemented.");
}else if(( strncmp(g.argv[2],"list",n)==0 )
|| ( strncmp(g.argv[2],"ls",n)==0 )){
Stmt q;
const int fTechnote = find_option("technote","t",0)!=0;
const int showIds = find_option("show-technote-ids","s",0)!=0;
verify_all_options();
if (fTechnote==0){
db_prepare(&q,
"SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'"
" ORDER BY lower(tagname) /*sort*/"
);
}else{
db_prepare(&q,
"SELECT datetime(e.mtime), substr(t.tagname,7)"
" FROM event e, tag t"
" WHERE e.type='e'"
" AND e.tagid IS NOT NULL"
" AND t.tagid=e.tagid"
" ORDER BY e.mtime DESC /*sort*/"
);
}
while( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q, 0);
if( showIds ){
const char *zUuid = db_column_text(&q, 1);
fossil_print("%s ",zUuid);
}
fossil_print( "%s\n",zName);
}
db_finalize(&q);
}else{
goto wiki_cmd_usage;
}
return;
wiki_cmd_usage:
usage("export|create|commit|list ...");
}
/*
** Allowed flags for wiki_render_associated
*/
#if INTERFACE
#define WIKIASSOC_FULL_TITLE 0x00001 /* Full title */
#define WIKIASSOC_MENU_READ 0x00002 /* Add submenu link to read wiki */
#define WIKIASSOC_MENU_WRITE 0x00004 /* Add submenu link to add wiki */
#define WIKIASSOC_ALL 0x00007 /* All of the above */
#endif
/*
** Show the default Section label for an associated wiki page.
*/
static void wiki_section_label(
const char *zPrefix, /* "branch", "tag", or "checkin" */
const char *zName, /* Name of the object */
unsigned int mFlags /* Zero or more WIKIASSOC_* flags */
){
if( (mFlags & WIKIASSOC_FULL_TITLE)==0 ){
@ <div class="section accordion">About</div>
}else if( zPrefix[0]=='c' ){ /* checkin/... */
@ <div class="section accordion">About checkin %.20h(zName)</div>
}else{
@ <div class="section accordion">About %s(zPrefix) %h(zName)</div>
}
}
/*
** Add an "Wiki" button in a submenu that links to the read-wiki page.
*/
static void wiki_submenu_to_edit_wiki(
const char *zPrefix, /* "branch", "tag", or "checkin" */
const char *zName, /* Name of the object */
unsigned int mFlags /* Zero or more WIKIASSOC_* flags */
){
if( g.perm.RdWiki && (mFlags & WIKIASSOC_MENU_READ)!=0 ){
style_submenu_element("Wiki", "%R/wikiedit?name=%s/%t", zPrefix, zName);
}
}
/*
** Check to see if there exists a wiki page with a name zPrefix/zName.
** If there is, then render a <div class='section'>..</div> and
** return true.
|
| ︙ | ︙ | |||
1697 1698 1699 1700 1701 1702 1703 |
if( fossil_strcmp(pWiki->zMimetype, "text/x-markdown")==0 ){
Blob tail = BLOB_INITIALIZER;
Blob title = BLOB_INITIALIZER;
Blob markdown;
blob_init(&markdown, pWiki->zWiki, -1);
markdown_to_html(&markdown, &title, &tail);
if( blob_size(&title) ){
| | | > > > > | | | | | | | > | 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 |
if( fossil_strcmp(pWiki->zMimetype, "text/x-markdown")==0 ){
Blob tail = BLOB_INITIALIZER;
Blob title = BLOB_INITIALIZER;
Blob markdown;
blob_init(&markdown, pWiki->zWiki, -1);
markdown_to_html(&markdown, &title, &tail);
if( blob_size(&title) ){
@ <div class="section accordion">%h(blob_str(&title))</div>
}else{
wiki_section_label(zPrefix, zName, mFlags);
}
wiki_submenu_to_edit_wiki(zPrefix, zName, mFlags);
@ <div class="accordion_panel">
safe_html_context(DOCSRC_WIKI);
safe_html(&tail);
convert_href_and_output(&tail);
@ </div>
blob_reset(&tail);
blob_reset(&title);
blob_reset(&markdown);
}else if( fossil_strcmp(pWiki->zMimetype, "text/plain")==0 ){
wiki_section_label(zPrefix, zName, mFlags);
wiki_submenu_to_edit_wiki(zPrefix, zName, mFlags);
@ <div class="accordion_panel"><pre>
@ %h(pWiki->zWiki)
@ </pre></div>
}else{
Blob tail = BLOB_INITIALIZER;
Blob title = BLOB_INITIALIZER;
Blob wiki;
Blob *pBody;
blob_init(&wiki, pWiki->zWiki, -1);
if( wiki_find_title(&wiki, &title, &tail) ){
@ <div class="section accordion">%h(blob_str(&title))</div>
pBody = &tail;
}else{
wiki_section_label(zPrefix, zName, mFlags);
pBody = &wiki;
}
wiki_submenu_to_edit_wiki(zPrefix, zName, mFlags);
@ <div class="accordion_panel"><div class="wiki">
wiki_convert(pBody, 0, WIKI_BUTTONS);
@ </div></div>
blob_reset(&tail);
blob_reset(&title);
blob_reset(&wiki);
}
manifest_destroy(pWiki);
style_accordion();
return 1;
}
|
| ︙ | ︙ | |||
28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
#define WIKI_HTMLONLY 0x001 /* HTML markup only. No wiki */
#define WIKI_INLINE 0x002 /* Do not surround with <p>..</p> */
#define WIKI_NOBLOCK 0x004 /* No block markup of any kind */
#define WIKI_BUTTONS 0x008 /* Allow sub-menu buttons */
#define WIKI_NOBADLINKS 0x010 /* Ignore broken hyperlinks */
#define WIKI_LINKSONLY 0x020 /* No markup. Only decorate links */
#define WIKI_NEWLINE 0x040 /* Honor \n - break lines at each \n */
#endif
/*
** These are the only markup attributes allowed.
*/
enum allowed_attr_t {
| > > | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#define WIKI_HTMLONLY 0x001 /* HTML markup only. No wiki */
#define WIKI_INLINE 0x002 /* Do not surround with <p>..</p> */
#define WIKI_NOBLOCK 0x004 /* No block markup of any kind */
#define WIKI_BUTTONS 0x008 /* Allow sub-menu buttons */
#define WIKI_NOBADLINKS 0x010 /* Ignore broken hyperlinks */
#define WIKI_LINKSONLY 0x020 /* No markup. Only decorate links */
#define WIKI_NEWLINE 0x040 /* Honor \n - break lines at each \n */
#define WIKI_MARKDOWNLINKS 0x080 /* Resolve hyperlinks as in markdown */
#define WIKI_SAFE 0x100 /* Make the result safe for embedding */
#endif
/*
** These are the only markup attributes allowed.
*/
enum allowed_attr_t {
|
| ︙ | ︙ | |||
245 246 247 248 249 250 251 252 253 254 255 256 257 258 | #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) /* | > > > | 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | #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> */ /* MUTYPE values for elements that require strictly nested end-tags */ #define MUTYPE_Nested 0x0656 /* ** These markup types must have an end tag. */ #define MUTYPE_STACK (MUTYPE_BLOCK | MUTYPE_FONT | MUTYPE_LIST | MUTYPE_TABLE) /* |
| ︙ | ︙ | |||
466 467 468 469 470 471 472 | /* ** 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. */ | | | 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 |
/*
** 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.
*/
int html_tag_length(const char *z){
int n = 1;
int inparen = 0;
int c;
if( z[n]=='/' ){ n++; }
if( !fossil_isalpha(z[n]) ) return 0;
while( fossil_isalnum(z[n]) || z[n]=='-' ){ n++; }
c = z[n];
|
| ︙ | ︙ | |||
524 525 526 527 528 529 530 |
** \n
** [
**
** The "[" is only considered if flags contain ALLOW_LINKS or ALLOW_WIKI.
** The "\n" is only considered interesting if the flags constains ALLOW_WIKI.
*/
static int textLength(const char *z, int flags){
| < < | | < < | | < < < < | | 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 |
** \n
** [
**
** The "[" is only considered if flags contain ALLOW_LINKS or ALLOW_WIKI.
** The "\n" is only considered interesting if the flags constains ALLOW_WIKI.
*/
static int textLength(const char *z, int flags){
const char *zReject;
if( flags & ALLOW_WIKI ){
zReject = "<&[\n";
}else if( flags & ALLOW_LINKS ){
zReject = "<&[";
}else{
zReject = "<&";
}
return strcspn(z, zReject);
}
/*
** Return true if z[] begins with an HTML character element.
*/
static int isElement(const char *z){
int i;
|
| ︙ | ︙ | |||
661 662 663 664 665 666 667 |
**
** z points to the start of a token. Return the number of
** characters in that token. Write the token type into *pTokenType.
*/
static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){
int n;
if( z[0]=='<' ){
| | | 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 |
**
** z points to the start of a token. Return the number of
** characters in that token. Write the token type into *pTokenType.
*/
static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){
int n;
if( z[0]=='<' ){
n = html_tag_length(z);
if( n>0 ){
*pTokenType = TOKEN_MARKUP;
return n;
}else{
*pTokenType = TOKEN_CHARACTER;
return 1;
}
|
| ︙ | ︙ | |||
829 830 831 832 833 834 835 |
}else{
zValue = &z[i];
while( !fossil_isspace(z[i]) && z[i]!='>' ){ z++; }
}
if( attrOk ){
p->aAttr[p->nAttr].zValue = zValue;
p->aAttr[p->nAttr].cTerm = c = z[i];
| > > > | > | | 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 |
}else{
zValue = &z[i];
while( !fossil_isspace(z[i]) && z[i]!='>' ){ z++; }
}
if( attrOk ){
p->aAttr[p->nAttr].zValue = zValue;
p->aAttr[p->nAttr].cTerm = c = z[i];
if( z[i]==0 ){
i--;
}else{
z[i] = 0;
}
}
i++;
}
if( attrOk ){
seen |= aAttribute[iACode].iMask;
p->nAttr++;
}
while( fossil_isspace(z[i]) ){ i++; }
if( z[i]==0 || z[i]=='>' || (z[i]=='/' && z[i+1]=='>') ) break;
}
return seen;
}
/*
** Render markup on the given blob.
*/
|
| ︙ | ︙ | |||
864 865 866 867 868 869 870 |
blob_appendf(pOut, "=\"%s%s\"", g.zTop, zVal);
}else{
blob_appendf(pOut, "=\"%s\"", zVal);
}
}
}
if (p->iType & MUTYPE_SINGLE){
| | | | 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 |
blob_appendf(pOut, "=\"%s%s\"", g.zTop, zVal);
}else{
blob_appendf(pOut, "=\"%s\"", zVal);
}
}
}
if (p->iType & MUTYPE_SINGLE){
blob_append_string(pOut, " /");
}
blob_append_char(pOut, '>');
}
}
/*
** When the markup was parsed, some "\000" may have been inserted.
** This routine restores to those "\000" values back to their
** original content.
|
| ︙ | ︙ | |||
1046 1047 1048 1049 1050 1051 1052 |
/*
** Begin a new paragraph if that something that is needed.
*/
static void startAutoParagraph(Renderer *p){
if( p->wantAutoParagraph==0 ) return;
if( p->state & WIKI_LINKSONLY ) return;
if( p->wikiList==MARKUP_OL || p->wikiList==MARKUP_UL ) return;
| | | 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 |
/*
** Begin a new paragraph if that something that is needed.
*/
static void startAutoParagraph(Renderer *p){
if( p->wantAutoParagraph==0 ) return;
if( p->state & WIKI_LINKSONLY ) return;
if( p->wikiList==MARKUP_OL || p->wikiList==MARKUP_UL ) return;
blob_append_string(p->pOut, "<p>");
p->wantAutoParagraph = 0;
p->inAutoParagraph = 1;
}
/*
** End a paragraph if we are in one.
*/
|
| ︙ | ︙ | |||
1101 1102 1103 1104 1105 1106 1107 | /* ** zTarget is guaranteed to be a UUID. It might be the UUID of a ticket. ** If it is, store in *pClosed a true or false depending on whether or not ** the ticket is closed and return true. If zTarget ** is not the UUID of a ticket, return false. */ | | | | | 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 |
/*
** zTarget is guaranteed to be a UUID. It might be the UUID of a ticket.
** If it is, store in *pClosed a true or false depending on whether or not
** the ticket is closed and return true. If zTarget
** is not the UUID of a ticket, return false.
*/
int is_ticket(
const char *zTarget, /* Ticket UUID */
int *pClosed /* True if the ticket is closed */
){
static Stmt q;
int n;
int rc;
char zLower[HNAME_MAX+1];
char zUpper[HNAME_MAX+1];
n = strlen(zTarget);
memcpy(zLower, zTarget, n+1);
canonical16(zLower, n+1);
memcpy(zUpper, zLower, n+1);
zUpper[n-1]++;
if( !db_static_stmt_is_init(&q) ){
char *zClosedExpr = db_get("ticket-closed-expr", "status='Closed'");
db_static_prepare(&q,
"SELECT %z FROM ticket "
" WHERE tkt_uuid>=:lwr AND tkt_uuid<:upr",
zClosedExpr /*safe-for-%s*/
);
}
db_bind_text(&q, ":lwr", zLower);
db_bind_text(&q, ":upr", zUpper);
if( db_step(&q)==SQLITE_ROW ){
|
| ︙ | ︙ | |||
1140 1141 1142 1143 1144 1145 1146 | } /* ** Return a pointer to the name part of zTarget (skipping the "wiki:" prefix ** if there is one) if zTarget is a valid wiki page name. Return NULL if ** zTarget names a page that does not exist. */ | | | | 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 |
}
/*
** Return a pointer to the name part of zTarget (skipping the "wiki:" prefix
** if there is one) if zTarget is a valid wiki page name. Return NULL if
** zTarget names a page that does not exist.
*/
static const char *validWikiPageName(int mFlags, const char *zTarget){
if( strncmp(zTarget, "wiki:", 5)==0
&& wiki_name_is_wellformed((const unsigned char*)zTarget) ){
return zTarget+5;
}
if( strcmp(zTarget, "Sandbox")==0 ) return zTarget;
if( wiki_name_is_wellformed((const unsigned char *)zTarget)
&& ((mFlags & WIKI_NOBADLINKS)==0 ||
db_exists("SELECT 1 FROM tag WHERE tagname GLOB 'wiki-%q'"
" AND (SELECT value FROM tagxref WHERE tagid=tag.tagid"
" ORDER BY mtime DESC LIMIT 1) > 0", zTarget))
){
return zTarget;
}
return 0;
|
| ︙ | ︙ | |||
1209 1210 1211 1212 1213 1214 1215 | ** "History" permission. ** ** [http://www.fossil-scm.org/] ** [https://www.fossil-scm.org/] ** [ftp://www.fossil-scm.org/] ** [mailto:fossil-users@lists.fossil-scm.org] ** | | > > > > > > < < < < | > | | > > > > > > > | | | | | | | | | | | | | < < < | > | | > > > > > > > > > > | > | > | > | > | 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 |
** "History" permission.
**
** [http://www.fossil-scm.org/]
** [https://www.fossil-scm.org/]
** [ftp://www.fossil-scm.org/]
** [mailto:fossil-users@lists.fossil-scm.org]
**
** [/path] -> Refers to the root of the Fossil hierarchy, not
** the root of the URI domain
**
** [./relpath]
** [../relpath]
**
** [#fragment]
**
** [0123456789abcdef]
**
** [WikiPageName]
** [wiki:WikiPageName]
**
** [2010-02-27 07:13]
*/
void wiki_resolve_hyperlink(
Blob *pOut, /* Write the HTML output here */
int mFlags, /* Rendering option flags */
const char *zTarget, /* Hyperlink target; text within [...] */
char *zClose, /* Write hyperlink closing text here */
int nClose, /* Bytes available in zClose[] */
const char *zOrig, /* Complete document text */
const char *zTitle /* Title of the link */
){
const char *zTerm = "</a>";
const char *z;
char *zExtra = 0;
const char *zExtraNS = 0;
if( zTitle ){
zExtra = mprintf(" title='%h'", zTitle);
zExtraNS = zExtra+1;
}
assert( nClose>=20 );
if( strncmp(zTarget, "http:", 5)==0
|| strncmp(zTarget, "https:", 6)==0
|| strncmp(zTarget, "ftp:", 4)==0
|| strncmp(zTarget, "mailto:", 7)==0
){
blob_appendf(pOut, "<a href=\"%s\"%s>", zTarget, zExtra);
}else if( zTarget[0]=='/' ){
blob_appendf(pOut, "<a href=\"%R%h\"%s>", zTarget, zExtra);
}else if( zTarget[0]=='.'
&& (zTarget[1]=='/' || (zTarget[1]=='.' && zTarget[2]=='/'))
&& (mFlags & WIKI_LINKSONLY)==0 ){
blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra);
}else if( zTarget[0]=='#' ){
blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra);
}else if( is_valid_hname(zTarget) ){
int isClosed = 0;
if( strlen(zTarget)<=HNAME_MAX && is_ticket(zTarget, &isClosed) ){
/* Special display processing for tickets. Display the hyperlink
** as crossed out if the ticket is closed.
*/
if( isClosed ){
if( g.perm.Hyperlink ){
blob_appendf(pOut,
"%z<span class=\"wikiTagCancelled\">[",
xhref(zExtraNS,"%R/info/%s",zTarget)
);
zTerm = "]</span></a>";
}else{
blob_appendf(pOut,"<span class=\"wikiTagCancelled\">[");
zTerm = "]</span>";
}
}else{
if( g.perm.Hyperlink ){
blob_appendf(pOut,"%z[", xhref(zExtraNS,"%R/info/%s", zTarget));
zTerm = "]</a>";
}else{
blob_appendf(pOut, "[");
zTerm = "]";
}
}
}else if( !in_this_repo(zTarget) ){
if( (mFlags & (WIKI_LINKSONLY|WIKI_NOBADLINKS))!=0 ){
zTerm = "";
}else{
blob_appendf(pOut, "<span class=\"brokenlink\">[");
zTerm = "]</span>";
}
}else if( g.perm.Hyperlink ){
blob_appendf(pOut, "%z[",xhref(zExtraNS, "%R/info/%s", zTarget));
zTerm = "]</a>";
}else{
zTerm = "";
}
}else if( (z = validWikiPageName(mFlags, zTarget))!=0 ){
/* The link is to a valid wiki page name */
const char *zOverride = wiki_is_overridden(zTarget);
if( zOverride ){
blob_appendf(pOut, "<a href=\"%R/info/%S\"%s>", zOverride, zExtra);
}else{
blob_appendf(pOut, "<a href=\"%R/wiki?name=%T\"%s>", z, zExtra);
}
}else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-'
&& db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){
/* Dates or date-and-times in ISO8610 resolve to a link to the
** timeline for that date */
blob_appendf(pOut, "<a href=\"%R/timeline?c=%T\"%s>", zTarget, zExtra);
}else if( mFlags & WIKI_MARKDOWNLINKS ){
/* If none of the above, and if rendering links for markdown, then
** create a link to the literal text of the target */
blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra);
}else if( zOrig && zTarget>=&zOrig[2]
&& zTarget[-1]=='[' && !fossil_isspace(zTarget[-2]) ){
/* If the hyperlink markup is not preceded by whitespace, then it
** is probably a C-language subscript or similar, not really a
** hyperlink. Just ignore it. */
zTerm = "";
}else if( (mFlags & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){
/* Also ignore the link if various flags are set */
zTerm = "";
}else{
blob_appendf(pOut, "<span class=\"brokenlink\">[%h]", zTarget);
zTerm = "</span>";
}
if( zExtra ) fossil_free(zExtra);
assert( strlen(zTerm)<nClose );
sqlite3_snprintf(nClose, zClose, "%s", zTerm);
}
/*
** Check to see if the given parsed markup is the correct
** </verbatim> tag.
|
| ︙ | ︙ | |||
1361 1362 1363 1364 1365 1366 1367 |
}else{
n = nextWikiToken(z, p, &tokenType);
}
p->state &= ~(AT_NEWLINE|AT_PARAGRAPH);
switch( tokenType ){
case TOKEN_PARAGRAPH: {
if( inlineOnly ){
| | | | | | | | | | | | | | | | | 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 |
}else{
n = nextWikiToken(z, p, &tokenType);
}
p->state &= ~(AT_NEWLINE|AT_PARAGRAPH);
switch( tokenType ){
case TOKEN_PARAGRAPH: {
if( inlineOnly ){
/* blob_append_string(p->pOut, " ¶ "); */
blob_append_string(p->pOut, " ");
}else{
if( p->wikiList ){
popStackToTag(p, p->wikiList);
p->wikiList = 0;
}
endAutoParagraph(p);
blob_append_string(p->pOut, "\n\n");
p->wantAutoParagraph = 1;
}
p->state |= AT_PARAGRAPH|AT_NEWLINE;
break;
}
case TOKEN_NEWLINE: {
if( p->renderFlags & WIKI_NEWLINE ){
blob_append_string(p->pOut, "<br>\n");
}else{
blob_append_string(p->pOut, "\n");
}
p->state |= AT_NEWLINE;
break;
}
case TOKEN_BUL_LI: {
if( inlineOnly ){
blob_append_string(p->pOut, " • ");
}else{
if( p->wikiList!=MARKUP_UL ){
if( p->wikiList ){
popStackToTag(p, p->wikiList);
}
endAutoParagraph(p);
pushStack(p, MARKUP_UL);
blob_append_string(p->pOut, "<ul>");
p->wikiList = MARKUP_UL;
}
popStackToTag(p, MARKUP_LI);
startAutoParagraph(p);
pushStack(p, MARKUP_LI);
blob_append_string(p->pOut, "<li>");
}
break;
}
case TOKEN_NUM_LI: {
if( inlineOnly ){
blob_append_string(p->pOut, " # ");
}else{
if( p->wikiList!=MARKUP_OL ){
if( p->wikiList ){
popStackToTag(p, p->wikiList);
}
endAutoParagraph(p);
pushStack(p, MARKUP_OL);
blob_append_string(p->pOut, "<ol>");
p->wikiList = MARKUP_OL;
}
popStackToTag(p, MARKUP_LI);
startAutoParagraph(p);
pushStack(p, MARKUP_LI);
blob_append_string(p->pOut, "<li>");
}
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);
}
endAutoParagraph(p);
pushStack(p, MARKUP_OL);
blob_append_string(p->pOut, "<ol>");
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_string(p->pOut, "<blockquote>");
p->wantAutoParagraph = 0;
p->wikiList = MARKUP_BLOCKQUOTE;
}
break;
}
case TOKEN_CHARACTER: {
startAutoParagraph(p);
if( z[0]=='<' ){
blob_append_string(p->pOut, "<");
}else if( z[0]=='&' ){
blob_append_string(p->pOut, "&");
}
break;
}
case TOKEN_LINK: {
char *zTarget;
char *zDisplay = 0;
int i, j;
|
| ︙ | ︙ | |||
1489 1490 1491 1492 1493 1494 1495 |
}
z[i] = 0;
if( zDisplay==0 ){
zDisplay = zTarget;
}else{
while( fossil_isspace(*zDisplay) ) zDisplay++;
}
| > | | 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 |
}
z[i] = 0;
if( zDisplay==0 ){
zDisplay = zTarget;
}else{
while( fossil_isspace(*zDisplay) ) zDisplay++;
}
wiki_resolve_hyperlink(p->pOut, p->state,
zTarget, zClose, sizeof(zClose), zOrig, 0);
if( linksOnly || zClose[0]==0 || p->inVerbatim ){
if( cS1 ) z[iS1] = cS1;
if( zClose[0]!=']' ){
blob_appendf(p->pOut, "[%h]%s", zTarget, zClose);
}else{
blob_appendf(p->pOut, "%h%s", zTarget, zClose);
}
|
| ︙ | ︙ | |||
1547 1548 1549 1550 1551 1552 1553 |
if( markup.iCode==MARKUP_DIV && markup.endTag &&
(zId = markupId(&markup))!=0 &&
(iDiv = findTagWithId(p, MARKUP_DIV, zId))>=0
){
if( p->inVerbatim ){
p->inVerbatim = 0;
p->state = p->preVerbState;
| | | | | | 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 |
if( markup.iCode==MARKUP_DIV && markup.endTag &&
(zId = markupId(&markup))!=0 &&
(iDiv = findTagWithId(p, MARKUP_DIV, zId))>=0
){
if( p->inVerbatim ){
p->inVerbatim = 0;
p->state = p->preVerbState;
blob_append_string(p->pOut, "</pre>");
}
while( p->nStack>iDiv+1 ) popStack(p);
if( p->aStack[iDiv].allowWiki ){
p->state |= ALLOW_WIKI;
}else{
p->state &= ~ALLOW_WIKI;
}
assert( p->nStack==iDiv+1 );
p->nStack--;
}else
/* If within <verbatim id=ID> ignore everything other than
** </verbatim id=ID> and the </dev id=ID2> above.
*/
if( p->inVerbatim ){
if( endVerbatim(p, &markup) ){
p->inVerbatim = 0;
p->state = p->preVerbState;
blob_append_string(p->pOut, "</pre>");
}else{
unparseMarkup(&markup);
blob_append_string(p->pOut, "<");
n = 1;
}
}else
/* Render invalid markup literally. The markup appears in the
** final output as plain text.
*/
if( markup.iCode==MARKUP_INVALID ){
unparseMarkup(&markup);
startAutoParagraph(p);
blob_append_string(p->pOut, "<");
n = 1;
}else
/* If the markup is not font-change markup ignore it if the
** font-change-only flag is set.
*/
if( (markup.iType&MUTYPE_FONT)==0 && (p->state & FONT_MARKUP_ONLY)!=0 ){
|
| ︙ | ︙ | |||
1641 1642 1643 1644 1645 1646 1647 |
}else if( markup.aAttr[ii].iACode==ATTR_LINKS
&& !is_false(markup.aAttr[ii].zValue) ){
p->state |= ALLOW_LINKS;
}
}
if( !vAttrDidAppend ) {
endAutoParagraph(p);
| | | | | 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 |
}else if( markup.aAttr[ii].iACode==ATTR_LINKS
&& !is_false(markup.aAttr[ii].zValue) ){
p->state |= ALLOW_LINKS;
}
}
if( !vAttrDidAppend ) {
endAutoParagraph(p);
blob_append_string(p->pOut, "<pre class='verbatim'>");
}
p->wantAutoParagraph = 0;
}else
if( markup.iType==MUTYPE_LI ){
if( backupToType(p, MUTYPE_LIST)==0 ){
endAutoParagraph(p);
pushStack(p, MARKUP_UL);
blob_append_string(p->pOut, "<ul>");
}
pushStack(p, MARKUP_LI);
renderMarkup(p->pOut, &markup);
}else
if( markup.iType==MUTYPE_TR ){
if( backupToType(p, MUTYPE_TABLE) ){
pushStack(p, MARKUP_TR);
renderMarkup(p->pOut, &markup);
}
}else
if( markup.iType==MUTYPE_TD ){
if( backupToType(p, MUTYPE_TABLE|MUTYPE_TR) ){
if( stackTopType(p)==MUTYPE_TABLE ){
pushStack(p, MARKUP_TR);
blob_append_string(p->pOut, "<tr>");
}
pushStack(p, markup.iCode);
renderMarkup(p->pOut, &markup);
}
}else
if( markup.iType==MUTYPE_HYPERLINK ){
if( !isButtonHyperlink(p, &markup, z, &n) ){
|
| ︙ | ︙ | |||
1719 1720 1721 1722 1723 1724 1725 |
*/
void wiki_convert(Blob *pIn, Blob *pOut, int flags){
Renderer renderer;
memset(&renderer, 0, sizeof(renderer));
renderer.renderFlags = flags;
renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH|flags;
| < < < | < < < < < < < < < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 |
*/
void wiki_convert(Blob *pIn, Blob *pOut, int flags){
Renderer renderer;
memset(&renderer, 0, sizeof(renderer));
renderer.renderFlags = flags;
renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH|flags;
if( flags & WIKI_INLINE ){
renderer.wantAutoParagraph = 0;
}else{
renderer.wantAutoParagraph = 1;
}
if( wikiUsesHtml() ){
renderer.state |= WIKI_HTMLONLY;
}
if( pOut ){
renderer.pOut = pOut;
}else{
renderer.pOut = cgi_output_blob();
}
blob_to_utf8_no_bom(pIn, 0);
wiki_render(&renderer, blob_str(pIn));
endAutoParagraph(&renderer);
while( renderer.nStack ){
popStack(&renderer);
}
blob_append_char(renderer.pOut, '\n');
free(renderer.aStack);
}
/*
** COMMAND: test-wiki-render
**
** Usage: %fossil test-wiki-render FILE [OPTIONS]
**
** Translate the input FILE from Fossil-wiki into HTML and write
** the resulting HTML on standard output.
**
** Options:
** --buttons Set the WIKI_BUTTONS flag
** --htmlonly Set the WIKI_HTMLONLY flag
** --linksonly Set the WIKI_LINKSONLY flag
** --nobadlinks Set the WIKI_NOBADLINKS flag
** --inline Set the WIKI_INLINE flag
** --noblock Set the WIKI_NOBLOCK flag
*/
void test_wiki_render(void){
Blob in, out;
int flags = 0;
if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS;
if( find_option("htmlonly",0,0)!=0 ) flags |= WIKI_HTMLONLY;
if( find_option("linksonly",0,0)!=0 ) flags |= WIKI_LINKSONLY;
if( find_option("nobadlinks",0,0)!=0 ) flags |= WIKI_NOBADLINKS;
if( find_option("inline",0,0)!=0 ) flags |= WIKI_INLINE;
if( find_option("noblock",0,0)!=0 ) flags |= WIKI_NOBLOCK;
db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
verify_all_options();
if( g.argc!=3 ) usage("FILE");
blob_zero(&out);
blob_read_from_file(&in, g.argv[2], ExtFILE);
wiki_convert(&in, &out, flags);
blob_write_to_file(&out, "-");
}
/*
** COMMAND: test-markdown-render
**
** Usage: %fossil test-markdown-render FILE ...
**
** Render markdown in FILE as HTML on stdout.
** Options:
**
** --safe Restrict the output to use only "safe" HTML
*/
void test_markdown_render(void){
Blob in, out;
int i;
int bSafe = 0;
db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
bSafe = find_option("safe",0,0)!=0;
verify_all_options();
for(i=2; i<g.argc; i++){
blob_zero(&out);
blob_read_from_file(&in, g.argv[i], ExtFILE);
if( g.argc>3 ){
fossil_print("<!------ %h ------->\n", g.argv[i]);
}
markdown_to_html(&in, 0, &out);
safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED );
safe_html(&out);
blob_write_to_file(&out, "-");
blob_reset(&in);
blob_reset(&out);
}
}
/*
** Search for a <title>...</title> at the beginning of a wiki page.
** Return true (nonzero) if a title is found. Return zero if there is
** not title.
**
** If a title is found, initialize the pTitle blob to be the content
|
| ︙ | ︙ | |||
1833 1834 1835 1836 1837 1838 1839 | ** ** Where "target" can be either an artifact ID prefix or a wiki page ** name. For each such hyperlink found, add an entry to the ** backlink table. */ void wiki_extract_links( char *z, /* The wiki text from which to extract links */ | | < < < < < < < | < < < < | < < < < < < < | 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 |
**
** Where "target" can be either an artifact ID prefix or a wiki page
** name. For each such hyperlink found, add an entry to the
** backlink table.
*/
void wiki_extract_links(
char *z, /* The wiki text from which to extract links */
Backlink *pBklnk, /* Backlink extraction context */
int flags /* wiki parsing flags */
){
Renderer renderer;
int tokenType;
ParsedMarkup markup;
int n;
int inlineOnly;
int wikiHtmlOnly = 0;
memset(&renderer, 0, sizeof(renderer));
renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH;
if( flags & WIKI_NOBLOCK ){
renderer.state |= INLINE_MARKUP_ONLY;
}
if( wikiUsesHtml() ){
renderer.state |= WIKI_HTMLONLY;
wikiHtmlOnly = 1;
}
inlineOnly = (renderer.state & INLINE_MARKUP_ONLY)!=0;
while( z[0] ){
if( wikiHtmlOnly ){
n = nextRawToken(z, &renderer, &tokenType);
}else{
n = nextWikiToken(z, &renderer, &tokenType);
}
switch( tokenType ){
case TOKEN_LINK: {
char *zTarget;
int i;
zTarget = &z[1];
for(i=0; zTarget[i] && zTarget[i]!='|' && zTarget[i]!=']'; i++){}
while(i>1 && zTarget[i-1]==' '){ i--; }
backlink_create(pBklnk, zTarget, i);
break;
}
case TOKEN_MARKUP: {
const char *zId;
int iDiv;
parseMarkup(&markup, z);
|
| ︙ | ︙ | |||
2002 2003 2004 2005 2006 2007 2008 |
}
z += n;
}
free(renderer.aStack);
}
/*
| | < < < | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 |
}
z += n;
}
free(renderer.aStack);
}
/*
** Return the length, in bytes, of the HTML token that z is pointing to.
*/
int html_token_length(const char *z){
int n;
char c;
if( (c=z[0])=='<' ){
n = html_tag_length(z);
if( n<=0 ) n = 1;
}else if( fossil_isspace(c) ){
for(n=1; z[n] && fossil_isspace(z[n]); n++){}
}else if( c=='&' ){
n = z[1]=='#' ? 2 : 1;
while( fossil_isalnum(z[n]) ) n++;
if( z[n]==';' ) n++;
}else{
n = 1;
for(n=1; 1; n++){
if( (c = z[n]) > '<' ) continue;
if( c=='<' || c=='&' || fossil_isspace(c) || c==0 ) break;
}
}
return n;
}
/*
** z points to someplace in the middle of HTML markup. Return the length
** of the subtoken that starts on z.
*/
int html_subtoken_length(const char *z){
int n;
char c;
c = z[0];
if( fossil_isspace(c) ){
for(n=1; z[n] && fossil_isspace(z[n]); n++){}
return n;
}
if( c=='"' || c=='\'' ){
for(n=1; z[n] && z[n]!=c && z[n]!='>'; n++){}
if( z[n]==c ) n++;
return n;
}
if( c=='>' ){
return 0;
}
if( c=='=' ){
return 1;
}
if( fossil_isalnum(c) || c=='/' ){
for(n=1; (c=z[n])!=0 && (fossil_isalnum(c) || c=='-' || c=='_'); n++){}
return n;
}
return 1;
}
/*
** z points to an HTML markup token: <TAG ATTR=VALUE ...>
** This routine looks for the VALUE associated with zAttr and returns
** a pointer to the start of that value and sets *pLen to be the length
** in bytes for the value. Or it returns NULL if no such attr exists.
*/
const char *html_attribute(const char *zMarkup, const char *zAttr, int *pLen){
int i = 1;
int n;
int nAttr;
int iMatchCnt = 0;
assert( zMarkup[0]=='<' );
assert( zMarkup[1]!=0 );
n = html_subtoken_length(zMarkup+i);
if( n==0 ) return 0;
i += n;
nAttr = (int)strlen(zAttr);
while( 1 ){
const char *zStart = zMarkup+i;
n = html_subtoken_length(zStart);
if( n==0 ) break;
i += n;
if( fossil_isspace(zStart[0]) ) continue;
if( n==nAttr && fossil_strnicmp(zAttr,zStart,nAttr)==0 ){
iMatchCnt = 1;
}else if( n==1 && zStart[0]=='=' && iMatchCnt==1 ){
iMatchCnt = 2;
}else if( iMatchCnt==2 ){
if( (zStart[0]=='"' || zStart[0]=='\'') && zStart[n-1]==zStart[0] ){
zStart++;
n -= 2;
}
*pLen = n;
return zStart;
}else{
iMatchCnt = 0;
}
}
return 0;
}
/*
** COMMAND: test-html-tokenize
**
** Tokenize an HTML file. Return the offset and length and text of
** each token - one token per line. Omit white-space tokens.
*/
void test_html_tokenize(void){
Blob in;
char *z;
int i;
int iOfst, n;
for(i=2; i<g.argc; i++){
blob_read_from_file(&in, g.argv[i], ExtFILE);
z = blob_str(&in);
for(iOfst=0; z[iOfst]; iOfst+=n){
n = html_token_length(z+iOfst);
if( fossil_isspace(z[iOfst]) ) continue;
fossil_print("%d %d %.*s\n", iOfst, n, n, z+iOfst);
if( z[iOfst]=='<' && n>1 ){
int j,k;
for(j=iOfst+1; (k = html_subtoken_length(z+j))>0; j+=k){
if( fossil_isspace(z[j]) || z[j]=='=' ) continue;
fossil_print("# %d %d %.*s\n", j, k, k, z+j);
}
}
}
blob_reset(&in);
}
}
/*
** Attempt to reformat messy HTML to be easily readable by humans.
**
** * Try to keep lines less than 80 characters in length
** * Collapse white space into a single space
** * Put a blank line before:
|
| ︙ | ︙ | |||
2050 2051 2052 2053 2054 2055 2056 |
void htmlTidy(const char *zIn, Blob *pOut){
int n;
int nPre = 0;
int iCur = 0;
int wantSpace = 0;
int omitSpace = 1;
while( zIn[0] ){
| | | | | | | | | | | | 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 |
void htmlTidy(const char *zIn, Blob *pOut){
int n;
int nPre = 0;
int iCur = 0;
int wantSpace = 0;
int omitSpace = 1;
while( zIn[0] ){
n = html_token_length(zIn);
if( zIn[0]=='<' && n>1 ){
int i, j;
int isCloseTag;
int eTag;
int eType;
char zTag[32];
isCloseTag = zIn[1]=='/';
for(i=0, j=1+isCloseTag; i<30 && fossil_isalnum(zIn[j]); i++, j++){
zTag[i] = fossil_tolower(zIn[j]);
}
zTag[i] = 0;
eTag = findTag(zTag);
eType = aMarkup[eTag].iType;
if( eTag==MARKUP_PRE ){
if( isCloseTag ){
nPre--;
blob_append(pOut, zIn, n);
zIn += n;
if( nPre==0 ){ blob_append_char(pOut, '\n'); iCur = 0; }
continue;
}else{
if( iCur && nPre==0 ){ blob_append_char(pOut, '\n'); iCur = 0; }
nPre++;
}
}else if( eType & (MUTYPE_BLOCK|MUTYPE_TABLE) ){
if( !isCloseTag && nPre==0 && blob_size(pOut)>0 ){
blob_append(pOut, "\n\n", 1 + (iCur>0));
iCur = 0;
}
wantSpace = 0;
omitSpace = 1;
}else if( (eType & (MUTYPE_LIST|MUTYPE_LI|MUTYPE_TR|MUTYPE_TD))!=0
|| eTag==MARKUP_HR
){
if( nPre==0 && (!isCloseTag || (eType&MUTYPE_LIST)!=0) && iCur>0 ){
blob_append_char(pOut, '\n');
iCur = 0;
}
wantSpace = 0;
omitSpace = 1;
}
if( wantSpace && nPre==0 ){
if( iCur+n+1>=80 ){
blob_append_char(pOut, '\n');
iCur = 0;
}else{
blob_append_char(pOut, ' ');
iCur++;
}
}
blob_append(pOut, zIn, n);
iCur += n;
wantSpace = 0;
if( eTag==MARKUP_BR || eTag==MARKUP_HR ){
blob_append_char(pOut, '\n');
iCur = 0;
}
}else if( fossil_isspace(zIn[0]) ){
if( nPre ){
blob_append(pOut, zIn, n);
}else{
wantSpace = !omitSpace;
}
}else{
if( wantSpace && nPre==0 ){
if( iCur+n+1>=80 ){
blob_append_char(pOut, '\n');
iCur = 0;
}else{
blob_append_char(pOut, ' ');
iCur++;
}
}
blob_append(pOut, zIn, n);
iCur += n;
wantSpace = omitSpace = 0;
}
zIn += n;
}
if( iCur ) blob_append_char(pOut, '\n');
}
/*
** COMMAND: test-html-tidy
**
** Run the htmlTidy() routine on the content of all files named on
** the command-line and write the results to standard output.
|
| ︙ | ︙ | |||
2169 2170 2171 2172 2173 2174 2175 |
int i, j;
int inTitle = 0; /* True between <title>...</title> */
int seenText = 0; /* True after first non-whitespace seen */
int nNL = 0; /* Number of \n characters at the end of pOut */
int nWS = 0; /* True if pOut ends with whitespace */
while( fossil_isspace(zIn[0]) ) zIn++;
while( zIn[0] ){
| | | | | | 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 |
int i, j;
int inTitle = 0; /* True between <title>...</title> */
int seenText = 0; /* True after first non-whitespace seen */
int nNL = 0; /* Number of \n characters at the end of pOut */
int nWS = 0; /* True if pOut ends with whitespace */
while( fossil_isspace(zIn[0]) ) zIn++;
while( zIn[0] ){
n = html_token_length(zIn);
if( zIn[0]=='<' && n>1 ){
int isCloseTag;
int eTag;
int eType;
char zTag[32];
isCloseTag = zIn[1]=='/';
for(i=0, j=1+isCloseTag; i<30 && fossil_isalnum(zIn[j]); i++, j++){
zTag[i] = fossil_tolower(zIn[j]);
}
zTag[i] = 0;
eTag = findTag(zTag);
eType = aMarkup[eTag].iType;
if( eTag==MARKUP_INVALID && fossil_strnicmp(zIn,"<style",6)==0 ){
zIn += n;
while( zIn[0] ){
n = html_token_length(zIn);
if( fossil_strnicmp(zIn, "</style",7)==0 ) break;
zIn += n;
}
if( zIn[0]=='<' ) zIn += n;
continue;
}
if( eTag==MARKUP_TITLE ){
inTitle = !isCloseTag;
}
if( !isCloseTag && seenText && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
if( nNL==0 ){
blob_append_char(pOut, '\n');
nNL++;
}
nWS = 1;
}
}else if( fossil_isspace(zIn[0]) ){
if( seenText ){
nNL = 0;
if( !inTitle ){ /* '\n' -> ' ' within <title> */
for(i=0; i<n; i++) if( zIn[i]=='\n' ) nNL++;
}
if( !nWS ){
blob_append_char(pOut, nNL ? '\n' : ' ');
nWS = 1;
}
}
}else if( zIn[0]=='&' ){
char c = '?';
if( zIn[1]=='#' ){
int x = atoi(&zIn[1]);
|
| ︙ | ︙ | |||
2234 2235 2236 2237 2238 2239 2240 |
if( aEntity[jj].n==n && strncmp(aEntity[jj].z,zIn,n)==0 ){
c = aEntity[jj].c;
break;
}
}
}
if( fossil_isspace(c) ){
| | | | | | | 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 |
if( aEntity[jj].n==n && strncmp(aEntity[jj].z,zIn,n)==0 ){
c = aEntity[jj].c;
break;
}
}
}
if( fossil_isspace(c) ){
if( nWS==0 && seenText ) blob_append_char(pOut, c);
nWS = 1;
nNL = c=='\n';
}else{
if( !seenText && !inTitle ) blob_append_char(pOut, '\n');
seenText = 1;
nNL = nWS = 0;
blob_append_char(pOut, c);
}
}else{
if( !seenText && !inTitle ) blob_append_char(pOut, '\n');
seenText = 1;
nNL = nWS = 0;
blob_append(pOut, zIn, n);
}
zIn += n;
}
if( nNL==0 ) blob_append_char(pOut, '\n');
}
/*
** COMMAND: test-html-to-text
**
** Usage: %fossil test-html-to-text FILE ...
**
|
| ︙ | ︙ | |||
2279 2280 2281 2282 2283 2284 2285 |
blob_zero(&out);
html_to_plaintext(blob_str(&in), &out);
blob_reset(&in);
fossil_puts(blob_str(&out), 0);
blob_reset(&out);
}
}
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 |
blob_zero(&out);
html_to_plaintext(blob_str(&in), &out);
blob_reset(&in);
fossil_puts(blob_str(&out), 0);
blob_reset(&out);
}
}
/****************************************************************************
** safe-html:
**
** An interface for preventing HTML constructs (ex: <style>, <form>, etc)
** from being inserted into Wiki and Forum posts using Markdown. See the
** comment on safe_html_append() for additional information on what is meant
** by "safe".
**
** The safe-html restrictions only apply to Markdown, as Fossil-Wiki only
** allows safe-html by design - unsafe-HTML is never and has never been
** allowed in Fossil-Wiki.
**
** This code is in the wikiformat.c file so that it can have access to the
** white-list of acceptable HTML in the aMarkup[] array.
*/
/*
** An instance of this object keeps track of the nesting of HTML
** elements for safe_html_append().
*/
typedef struct HtmlTagStack HtmlTagStack;
struct HtmlTagStack {
int n; /* Current tag stack depth */
int nAlloc; /* Space allocated for aStack[] */
int *aStack; /* The stack of tags */
int aSpace[10]; /* Initial static space, to avoid malloc() */
};
/*
** Initialize bulk memory to a valid empty tagstack.
*/
static void html_tagstack_init(HtmlTagStack *p){
p->n = 0;
p->nAlloc = 0;
p->aStack = p->aSpace;
}
/*
** Push a new element onto the tag statk
*/
static void html_tagstack_push(HtmlTagStack *p, int e){
if( p->n>=ArraySize(p->aSpace) && p->n>=p->nAlloc ){
if( p->nAlloc==0 ){
int *aNew;
p->nAlloc = 50;
aNew = fossil_malloc( sizeof(p->aStack[0])*p->nAlloc );
memcpy(aNew, p->aStack, sizeof(p->aStack[0])*p->n );
p->aStack = aNew;
}else{
p->nAlloc *= 2;
p->aStack = fossil_realloc(p->aStack, sizeof(p->aStack[0])*p->nAlloc );
}
}
p->aStack[p->n++] = e;
}
/*
** Clear a tag stack, reclaiming any memory allocations.
*/
static void html_tagstack_clear(HtmlTagStack *p){
if( p->nAlloc ){
fossil_free(p->aStack);
p->nAlloc = 0;
p->aStack = p->aSpace;
}
p->n = 0;
}
/*
** The HTML end-tag eEnd wants to be added to pBlob.
**
** If an open-tag for eEnd exists anywhere on the stack, then
** pop it and all prior elements from the task, issuing appropriate
** end-tags as you go.
**
** If there is no open-tag for eEnd on the stack, then this
** routine is a no-op.
*/
static void html_tagstack_pop(HtmlTagStack *p, Blob *pBlob, int eEnd){
int i, e;
if( eEnd!=0 ){
for(i=p->n-1; i>=0 && p->aStack[i]!=eEnd; i--){}
if( i<0 ){
blob_appendf(pBlob, "<span class='error'></%s></span>",
aMarkup[eEnd].zName);
return;
}
}else if( p->n==0 ){
return;
}
do{
e = p->aStack[--p->n];
if( e==eEnd || (aMarkup[e].iType & MUTYPE_Nested)!=0 ){
blob_appendf(pBlob, "</%s>", aMarkup[e].zName);
}
}while( e!=eEnd && p->n>0 );
}
/*
** Append a safe translation of HTML text to a Blob object.
**
** Restriction: The input to this routine must be writable.
* Temporary changes may be made to the input, but the input is restored
** to its original state prior to returning. If zHtml[nHtml] is not a
** zero character, then a zero might be written in that position
** temporarily, but that slot will also be restored before this routine
** returns.
*/
static void safe_html_append(Blob *pBlob, char *zHtml, int nHtml){
char cLast;
int i, j, n;
HtmlTagStack s;
ParsedMarkup markup;
if( nHtml<=0 ) return;
cLast = zHtml[nHtml];
zHtml[nHtml] = 0;
html_tagstack_init(&s);
i = 0;
while( i<nHtml ){
if( zHtml[i]=='<' ){
j = i;
}else{
char *z = strchr(zHtml+i, '<');
if( z==0 ){
blob_append(pBlob, zHtml+i, nHtml-i);
break;
}
j = (int)(z - zHtml);
blob_append(pBlob, zHtml+i, j-i);
}
n = html_tag_length(zHtml+j);
if( n==0 ){
blob_append(pBlob, "<", 4);
i = j+1;
continue;
}else{
i = j + n;
}
parseMarkup(&markup, zHtml+j);
if( markup.iCode==MARKUP_INVALID ){
unparseMarkup(&markup);
blob_appendf(pBlob, "<span class='error'><%.*s></span>",
n-2, zHtml+j+1);
continue;
}
if( (markup.iType & MUTYPE_Nested)==0 || markup.iCode==MARKUP_P ){
renderMarkup(pBlob, &markup);
}else{
if( markup.endTag ){
html_tagstack_pop(&s, pBlob, markup.iCode);
}else{
renderMarkup(pBlob, &markup);
html_tagstack_push(&s, markup.iCode);
}
}
unparseMarkup(&markup);
}
html_tagstack_pop(&s, pBlob, 0);
html_tagstack_clear(&s);
zHtml[nHtml] = cLast;
}
/*
** This local variable is true if the safe_html() function is enabled.
** In other words, this is true if the output of Markdown should be
** restricted to use only "safe" HTML.
*/
static int safeHtmlEnable = 1;
#if INTERFACE
/*
** Allowed values for the eTrust parameter to safe_html_context().
*/
#define DOCSRC_FILE 1 /* Document is a checked-in file */
#define DOCSRC_FORUM 2 /* Document is a forum post */
#define DOCSRC_TICKET 3 /* Document is a ticket comment */
#define DOCSRC_WIKI 4 /* Document is a wiki page */
#define DOCSRC_TRUSTED 5 /* safe_html() is always a no-op */
#define DOCSRC_UNTRUSTED 6 /* safe_html() is always enabled */
#endif /* INTERFACE */
/*
** Specify the context in which a markdown document with potentially
** unsafe HTML will be rendered.
*/
void safe_html_context(int eTrust){
static const char *zSafeHtmlSetting = 0;
char cPerm = 0;
if( eTrust==DOCSRC_TRUSTED ){
safeHtmlEnable = 0;
return;
}
if( eTrust==DOCSRC_UNTRUSTED ){
safeHtmlEnable = 1;
return;
}
if( zSafeHtmlSetting==0 ){
zSafeHtmlSetting = db_get("safe-html", "");
}
switch( eTrust ){
case DOCSRC_FILE: cPerm = 'b'; break;
case DOCSRC_FORUM: cPerm = 'f'; break;
case DOCSRC_TICKET: cPerm = 't'; break;
case DOCSRC_WIKI: cPerm = 'w'; break;
}
safeHtmlEnable = (strchr(zSafeHtmlSetting,cPerm)==0);
}
/*
** The input blob contains HTML. If safe-html is enabled, then
** convert the input into "safe HTML". The following modifications
** are made:
**
** 1. Remove any elements that are not on the AllowedMarkup list.
** (ex: <script>, <form>, etc.)
**
** 2. Remove any attributes that are not on the AllowedMarkup list.
** (ex: onload=, id=, etc.)
**
** 3. Omit any surplus close-tags. This prevents the script from
** terminating an <div> or similar in the outer context.
**
** 4. Insert additional close-tags as necessary so that any
** tag in the input that needs a close-tag has one. This
** prevents tags in the embedded script from affecting the
** display of content that follows this script in the enclosing
** context.
**
** This modifications are intended to make the generated HTML safe
** to be embedded in a larger HTML document, such that the embedded
** HTML has no influence on the formatting and operation of the
** larger document.
**
** If safe-html is disabled, then this routine is a no-op.
*/
void safe_html(Blob *in){
Blob out; /* Holding area for the revised text during construction */
char *z; /* Original input text */
int n; /* Number of bytes in the original input text */
int k;
if( safeHtmlEnable==0 ) return;
z = blob_str(in);
n = blob_size(in);
blob_init(&out, 0, 0);
while( fossil_isspace(z[0]) ){ z++; n--; }
for(k=n-1; k>5 && fossil_isspace(z[k]); k--){}
if( fossil_strnicmp(z, "<div",4)==0 && !fossil_isalpha(z[4])
&& fossil_strnicmp(z+k-5, "</div>",6)==0
){
/* The input contains an outer <div>...</div>. Preserve the
** full scope of that <div>. */
int m = html_tag_length(z);
k -= 5;
blob_append(&out, z, m);
safe_html_append(&out, z+m, k-m);
blob_append(&out, z+k, n-k);
}else{
safe_html_append(&out, z, n);
}
blob_reset(in);
*in = out;
}
/*
** COMMAND: test-safe-html
**
** Usage: %fossil test-safe-html FILE ...
**
** Read files named on the command-line. Send the text of each file
** through safe_html_append() and then write the result on
** standard output.
*/
void test_safe_html_cmd(void){
int i;
Blob x;
for(i=2; i<g.argc; i++){
char *z;
int n;
blob_read_from_file(&x, g.argv[i], ExtFILE);
blob_terminate(&x);
safe_html(&x);
z = blob_str(&x);
n = blob_size(&x);
while( n>0 && (z[n-1]=='\n' || z[n-1]=='\r') ) n--;
fossil_print("%.*s\n", n, z);
blob_reset(&x);
}
}
|
| ︙ | ︙ | |||
201 202 203 204 205 206 207 |
};
/*
** Accepts connections on DualSocket.
*/
static void DualSocket_accept(DualSocket* pListen, DualSocket* pClient,
DualAddr* pClientAddr){
| | | | | | | 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 |
};
/*
** Accepts connections on DualSocket.
*/
static void DualSocket_accept(DualSocket* pListen, DualSocket* pClient,
DualAddr* pClientAddr){
fd_set rs;
int rs_count = 0;
assert( pListen!=NULL && pClient!=NULL && pClientAddr!= NULL );
DualSocket_init(pClient);
DualAddr_init(pClientAddr);
FD_ZERO(&rs);
if( pListen->s4!=INVALID_SOCKET ){
FD_SET(pListen->s4, &rs);
++rs_count;
}
if( pListen->s6!=INVALID_SOCKET ){
FD_SET(pListen->s6, &rs);
++rs_count;
}
if( select(rs_count, &rs, 0, 0, 0 /*blocking*/)==SOCKET_ERROR ){
return;
}
if( FD_ISSET(pListen->s4, &rs) ){
pClient->s4 = accept(pListen->s4, (struct sockaddr*)&pClientAddr->a4.addr,
&pClientAddr->a4.len);
}
if( FD_ISSET(pListen->s6, &rs) ){
pClient->s6 = accept(pListen->s6, (struct sockaddr*)&pClientAddr->a6.addr,
|
| ︙ | ︙ |
| ︙ | ︙ | |||
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | Blob *pIn; /* Input text from the other side */ Blob *pOut; /* Compose our reply here */ Blob line; /* The current line of input */ Blob aToken[6]; /* Tokenized version of line */ Blob err; /* Error message text */ int nToken; /* Number of tokens in line */ int nIGotSent; /* Number of "igot" cards sent */ int nGimmeSent; /* Number of gimme cards sent */ int nFileSent; /* Number of files sent */ int nDeltaSent; /* Number of deltas sent */ int nFileRcvd; /* Number of files received */ int nDeltaRcvd; /* Number of deltas received */ int nDanglingFile; /* Number of dangling deltas received */ int mxSend; /* Stop sending "file" when pOut reaches this size */ int resync; /* Send igot cards for all holdings */ u8 syncPrivate; /* True to enable syncing private content */ u8 nextIsPrivate; /* If true, next "file" received is a private */ | > | > > | 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 | Blob *pIn; /* Input text from the other side */ Blob *pOut; /* Compose our reply here */ Blob line; /* The current line of input */ Blob aToken[6]; /* Tokenized version of line */ Blob err; /* Error message text */ int nToken; /* Number of tokens in line */ int nIGotSent; /* Number of "igot" cards sent */ int nPrivIGot; /* Number of private "igot" cards */ int nGimmeSent; /* Number of gimme cards sent */ int nFileSent; /* Number of files sent */ int nDeltaSent; /* Number of deltas sent */ int nFileRcvd; /* Number of files received */ int nDeltaRcvd; /* Number of deltas received */ int nDanglingFile; /* Number of dangling deltas received */ int mxSend; /* Stop sending "file" when pOut reaches this size */ int resync; /* Send igot cards for all holdings */ u8 syncPrivate; /* True to enable syncing private content */ u8 nextIsPrivate; /* If true, next "file" received is a private */ u32 remoteVersion; /* Version of fossil running on the other side */ u32 remoteDate; /* Date for specific client software edition */ u32 remoteTime; /* Time of date correspoding on remoteDate */ time_t maxTime; /* Time when this transfer should be finished */ }; /* ** The input blob contains an artifact. Convert it into a record ID. ** Create a phantom record if no prior record exists and |
| ︙ | ︙ | |||
522 523 524 525 526 527 528 |
** this routine becomes a no-op.
*/
static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int nativeDelta){
Blob content, uuid;
int size = 0;
int isPriv = content_is_private(rid);
| | > > > > > > > > > > > > | | 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 |
** this routine becomes a no-op.
*/
static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int nativeDelta){
Blob content, uuid;
int size = 0;
int isPriv = content_is_private(rid);
if( isPriv && pXfer->syncPrivate==0 ){
if( pXfer->remoteDate>=20200413 && pUuid && blob_size(pUuid)>0 ){
/* If the artifact is private and we are not doing a private sync,
** at least tell the other side that the artifact exists and is
** known to be private. But only do this for newer clients since
** older ones will throw an error if they get a private igot card
** and private syncing is disallowed */
blob_appendf(pXfer->pOut, "igot %b 1\n", pUuid);
pXfer->nIGotSent++;
pXfer->nPrivIGot++;
}
return;
}
if( db_exists("SELECT 1 FROM onremote WHERE rid=%d", rid) ){
return;
}
blob_zero(&uuid);
db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d AND size>=0", rid);
if( blob_size(&uuid)==0 ){
return;
}
if( blob_size(&uuid)>HNAME_LEN_SHA1 && pXfer->remoteVersion<20000 ){
xfer_cannot_send_sha3_error(pXfer);
return;
}
if( pUuid ){
if( blob_compare(pUuid, &uuid)!=0 ){
blob_reset(&uuid);
return;
|
| ︙ | ︙ | |||
624 625 626 627 628 629 630 |
zUuid = db_column_text(&q1, 0);
szU = db_column_int(&q1, 1);
szC = db_column_bytes(&q1, 2);
zContent = db_column_raw(&q1, 2);
srcIsPrivate = db_column_int(&q1, 3);
zDelta = db_column_text(&q1, 4);
if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
| | | 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 |
zUuid = db_column_text(&q1, 0);
szU = db_column_int(&q1, 1);
szC = db_column_bytes(&q1, 2);
zContent = db_column_raw(&q1, 2);
srcIsPrivate = db_column_int(&q1, 3);
zDelta = db_column_text(&q1, 4);
if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
if( pXfer->remoteVersion<20000 && db_column_bytes(&q1,0)!=HNAME_LEN_SHA1 ){
xfer_cannot_send_sha3_error(pXfer);
db_reset(&q1);
return;
}
blob_appendf(pXfer->pOut, "cfile %s ", zUuid);
if( !isPrivate && srcIsPrivate ){
content_get(rid, &fullContent);
|
| ︙ | ︙ | |||
688 689 690 691 692 693 694 |
" WHERE name=%Q",
zName
);
}
if( db_step(&q1)==SQLITE_ROW ){
sqlite3_int64 mtime = db_column_int64(&q1, 0);
const char *zHash = db_column_text(&q1, 1);
| | | 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 |
" WHERE name=%Q",
zName
);
}
if( db_step(&q1)==SQLITE_ROW ){
sqlite3_int64 mtime = db_column_int64(&q1, 0);
const char *zHash = db_column_text(&q1, 1);
if( pXfer->remoteVersion<20000 && db_column_bytes(&q1,1)>HNAME_LEN_SHA1 ){
xfer_cannot_send_sha3_error(pXfer);
db_reset(&q1);
return;
}
if( blob_size(pXfer->pOut)>=pXfer->mxSend ){
/* If we have already reached the send size limit, send a (short)
** uvigot card rather than a uvfile card. This only happens on the
|
| ︙ | ︙ | |||
954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 |
}
return cnt;
}
/*
** Send an igot message for every entry in unclustered table.
** Return the number of cards sent.
*/
static int send_unclustered(Xfer *pXfer){
Stmt q;
int cnt = 0;
if( pXfer->resync ){
db_prepare(&q,
"SELECT uuid, rid FROM blob"
" WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
" AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
| > > > > > > > > > > > > > > > > > | | | > | 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 |
}
return cnt;
}
/*
** Send an igot message for every entry in unclustered table.
** Return the number of cards sent.
**
** Except:
** * Do not send igot cards for shunned artifacts
** * Do not send igot cards for phantoms
** * Do not send igot cards for private artifacts
** * Do not send igot cards for any artifact that is in the
** ONREMOTE table, if that table exists.
**
** If the pXfer->resync flag is set, that means we are doing a "--verily"
** sync and all artifacts that don't meet the restrictions above should
** be sent.
*/
static int send_unclustered(Xfer *pXfer){
Stmt q;
int cnt = 0;
const char *zExtra;
if( db_table_exists("temp","onremote") ){
zExtra = " AND NOT EXISTS(SELECT 1 FROM onremote WHERE rid=blob.rid)";
}else{
zExtra = "";
}
if( pXfer->resync ){
db_prepare(&q,
"SELECT uuid, rid FROM blob"
" WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
" AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
" AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)%s"
" AND blob.rid<=%d"
" ORDER BY blob.rid DESC",
zExtra /*safe-for-%s*/, pXfer->resync
);
}else{
db_prepare(&q,
"SELECT uuid FROM unclustered JOIN blob USING(rid) /*scan*/"
" WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
" AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
" AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)%s",
zExtra /*safe-for-%s*/
);
}
while( db_step(&q)==SQLITE_ROW ){
blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
cnt++;
if( pXfer->resync && pXfer->mxSend<blob_size(pXfer->pOut) ){
pXfer->resync = db_column_int(&q, 1)-1;
|
| ︙ | ︙ | |||
1189 1190 1191 1192 1193 1194 1195 |
if( blob_buffer(&xfer.line)[0]=='#' ) continue;
if( blob_size(&xfer.line)==0 ) continue;
xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
/* file HASH SIZE \n CONTENT
** file HASH DELTASRC SIZE \n CONTENT
**
| | | | | | | > > > | > > > | > > > > > > > > > > > > | > > > | 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 |
if( blob_buffer(&xfer.line)[0]=='#' ) continue;
if( blob_size(&xfer.line)==0 ) continue;
xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
/* file HASH SIZE \n CONTENT
** file HASH DELTASRC SIZE \n CONTENT
**
** Server accepts a file from the client.
*/
if( blob_eq(&xfer.aToken[0], "file") ){
if( !isPush ){
cgi_reset_content();
@ error not\sauthorized\sto\swrite
nErr++;
break;
}
xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList);
if( blob_size(&xfer.err) ){
cgi_reset_content();
@ error %T(blob_str(&xfer.err))
nErr++;
break;
}
}else
/* cfile HASH USIZE CSIZE \n CONTENT
** cfile HASH DELTASRC USIZE CSIZE \n CONTENT
**
** Server accepts a compressed file from the client.
*/
if( blob_eq(&xfer.aToken[0], "cfile") ){
if( !isPush ){
cgi_reset_content();
@ error not\sauthorized\sto\swrite
nErr++;
break;
}
xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList);
if( blob_size(&xfer.err) ){
cgi_reset_content();
@ error %T(blob_str(&xfer.err))
nErr++;
break;
}
}else
/* uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT
**
** Server accepts an unversioned file from the client.
*/
if( blob_eq(&xfer.aToken[0], "uvfile") ){
xfer_accept_unversioned_file(&xfer, g.perm.WrUnver);
if( blob_size(&xfer.err) ){
cgi_reset_content();
@ error %T(blob_str(&xfer.err))
nErr++;
break;
}
}else
/* gimme HASH
**
** Client is requesting a file from the server. Send it.
*/
if( blob_eq(&xfer.aToken[0], "gimme")
&& xfer.nToken==2
&& blob_is_hname(&xfer.aToken[1])
){
nGimme++;
if( isPull ){
int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
if( rid ){
send_file(&xfer, rid, &xfer.aToken[1], deltaFlag);
}
}
}else
/* uvgimme NAME
**
** Client is requesting an unversioned file from the server. Send it.
*/
if( blob_eq(&xfer.aToken[0], "uvgimme")
&& xfer.nToken==2
&& blob_is_filename(&xfer.aToken[1])
){
send_unversioned_file(&xfer, blob_str(&xfer.aToken[1]), 0);
}else
/* igot HASH ?ISPRIVATE?
**
** Client announces that it has a particular file. If the ISPRIVATE
** argument exists and is "1", then the file is a private file.
*/
if( xfer.nToken>=2
&& blob_eq(&xfer.aToken[0], "igot")
&& blob_is_hname(&xfer.aToken[1])
){
if( isPush ){
int rid = 0;
int isPriv = 0;
if( xfer.nToken==2 || blob_eq(&xfer.aToken[2],"1")==0 ){
/* Client says the artifact is public */
rid = rid_from_uuid(&xfer.aToken[1], 1, 0);
}else if( g.perm.Private ){
/* Client says the artifact is private and the client has
** permission to push private content. Create a new phantom
** artifact that is marked private. */
rid = rid_from_uuid(&xfer.aToken[1], 1, 1);
isPriv = 1;
}else{
/* Client says the artifact is private and the client is unable
** or unwilling to send us the artifact. If we already hold the
** artifact here on the server as a phantom, make sure that
** phantom is marked as private so that we don't keep asking about
** it in subsequent sync requests. */
rid = rid_from_uuid(&xfer.aToken[1], 0, 1);
isPriv = 1;
}
if( rid ){
remote_has(rid);
if( isPriv ){
content_make_private(rid);
}else{
content_make_public(rid);
}
}
}
}else
/* pull SERVERCODE PROJECTCODE
** push SERVERCODE PROJECTCODE
|
| ︙ | ︙ | |||
1386 1387 1388 1389 1390 1391 1392 |
deltaFlag = 1;
}
@ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
}else
/* login USER NONCE SIGNATURE
**
| > | | 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 |
deltaFlag = 1;
}
@ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
}else
/* login USER NONCE SIGNATURE
**
** The client has sent login credentials to the server.
** Validate the login. This has to happen before anything else.
** The client can send multiple logins. Permissions are cumulative.
*/
if( blob_eq(&xfer.aToken[0], "login")
&& xfer.nToken==4
){
if( disableLogin ){
g.perm.Read = g.perm.Write = g.perm.Private = g.perm.Admin = 1;
|
| ︙ | ︙ | |||
1408 1409 1410 1411 1412 1413 1414 |
break;
}
}
}else
/* reqconfig NAME
**
| | | | | 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 |
break;
}
}
}else
/* reqconfig NAME
**
** Client is requesting a configuration value from the server
*/
if( blob_eq(&xfer.aToken[0], "reqconfig")
&& xfer.nToken==2
){
if( g.perm.Read ){
char *zName = blob_str(&xfer.aToken[1]);
if( zName[0]=='/' ){
/* New style configuration transfer */
int groupMask = configure_name_to_mask(&zName[1], 0);
if( !g.perm.Admin ) groupMask &= ~(CONFIGSET_USER|CONFIGSET_SCRIBER);
if( !g.perm.RdAddr ) groupMask &= ~CONFIGSET_ADDR;
configure_send_group(xfer.pOut, groupMask, 0);
}
}
}else
/* config NAME SIZE \n CONTENT
**
** Client has sent a configuration value to the server.
** This is only permitted for high-privilege users.
*/
if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3
&& blob_is_int(&xfer.aToken[2], &size) ){
const char *zName = blob_str(&xfer.aToken[1]);
Blob content;
blob_zero(&content);
blob_extract(xfer.pIn, size, &content);
|
| ︙ | ︙ | |||
1472 1473 1474 1475 1476 1477 1478 |
if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
/* Process the cookie */
}else
/* private
**
| | > > | > > | | > > | > > > > > > | 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 |
if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
/* Process the cookie */
}else
/* private
**
** The client card indicates that the next "file" or "cfile" will contain
** private content.
*/
if( blob_eq(&xfer.aToken[0], "private") ){
if( !g.perm.Private ){
server_private_xfer_not_authorized();
}else{
xfer.nextIsPrivate = 1;
}
}else
/* pragma NAME VALUE...
**
** The client issue pragmas to try to influence the behavior of the
** server. These are requests only. Unknown pragmas are silently
** ignored.
*/
if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
/* pragma send-private
**
** The client is requesting private artifacts.
**
** If the user has the "x" privilege (which must be set explicitly -
** it is not automatic with "a" or "s") then this pragma causes
** private information to be pulled in addition to public records.
*/
if( blob_eq(&xfer.aToken[1], "send-private") ){
login_check_credentials();
if( !g.perm.Private ){
server_private_xfer_not_authorized();
}else{
xfer.syncPrivate = 1;
}
}
/* pragma send-catalog
**
** The client wants to see igot cards for all known artifacts.
** This is used as part of "sync --verily" to help ensure that
** no artifacts have been missed on prior syncs.
*/
if( blob_eq(&xfer.aToken[1], "send-catalog") ){
xfer.resync = 0x7fffffff;
}
/* pragma client-version VERSION ?DATE? ?TIME?
**
** The client announces to the server what version of Fossil it
** is running. The DATE and TIME are a pure numeric ISO8601 time
** for the specific check-in of the client.
*/
if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "client-version") ){
xfer.remoteVersion = atoi(blob_str(&xfer.aToken[2]));
if( xfer.nToken>=5 ){
xfer.remoteDate = atoi(blob_str(&xfer.aToken[3]));
xfer.remoteTime = atoi(blob_str(&xfer.aToken[4]));
@ pragma server-version %d(RELEASE_VERSION_NUMBER) \
@ %d(MANIFEST_NUMERIC_DATE) %d(MANIFEST_NUMERIC_TIME)
}
}
/* pragma uv-hash HASH
**
** The client wants to make sure that unversioned files are all synced.
** If the HASH does not match, send a complete catalog of
** "uvigot" cards.
|
| ︙ | ︙ | |||
1548 1549 1550 1551 1552 1553 1554 |
uvCatalogSent = 1;
}
/* pragma ci-lock CHECKIN-HASH CLIENT-ID
**
** The client wants to make non-branch commit against the check-in
** identified by CHECKIN-HASH. The server will remember this and
| | | | | 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 |
uvCatalogSent = 1;
}
/* pragma ci-lock CHECKIN-HASH CLIENT-ID
**
** The client wants to make non-branch commit against the check-in
** identified by CHECKIN-HASH. The server will remember this and
** subsequent ci-lock requests from different clients will generate
** a ci-lock-fail pragma in the reply.
*/
if( blob_eq(&xfer.aToken[1], "ci-lock")
&& xfer.nToken==4
&& blob_is_hname(&xfer.aToken[2])
){
Stmt q;
sqlite3_int64 iNow = time(0);
sqlite3_int64 maxAge = db_get_int("lock-timeout",60);
int seenFault = 0;
db_prepare(&q,
"SELECT json_extract(value,'$.login'),"
" mtime,"
" json_extract(value,'$.clientid'),"
" (SELECT rid FROM blob WHERE uuid=substr(name,9)),"
" name"
" FROM config WHERE name GLOB 'ci-lock-*'"
);
while( db_step(&q)==SQLITE_ROW ){
int x = db_column_int(&q,3);
const char *zName = db_column_text(&q,4);
if( db_column_int64(&q,1)<=iNow-maxAge || !is_a_leaf(x) ){
/* check-in locks expire after maxAge seconds, or when the
** check-in is no longer a leaf */
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
continue;
}
if( fossil_strcmp(zName+8, blob_str(&xfer.aToken[2]))==0 ){
const char *zClientId = db_column_text(&q, 2);
|
| ︙ | ︙ | |||
1744 1745 1746 1747 1748 1749 1750 | ** routine is called by the client. ** ** Records are pushed to the server if pushFlag is true. Records ** are pulled if pullFlag is true. A full sync occurs if both are ** true. */ int client_sync( | | | | > | 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 |
** routine is called by the client.
**
** Records are pushed to the server if pushFlag is true. Records
** are pulled if pullFlag is true. A full sync occurs if both are
** true.
*/
int client_sync(
unsigned syncFlags, /* Mask of SYNC_* flags */
unsigned configRcvMask, /* Receive these configuration items */
unsigned configSendMask, /* Send these configuration items */
const char *zAltPCode /* Alternative project code (usually NULL) */
){
int go = 1; /* Loop until zero */
int nCardSent = 0; /* Number of cards sent */
int nCardRcvd = 0; /* Number of cards received */
int nCycle = 0; /* Number of round trips to the server */
int size; /* Size of a config value or uvfile */
int origConfigRcvMask; /* Original value of configRcvMask */
|
| ︙ | ︙ | |||
1805 1806 1807 1808 1809 1810 1811 |
transport_stats(0, 0, 1);
socket_global_init();
memset(&xfer, 0, sizeof(xfer));
xfer.pIn = &recv;
xfer.pOut = &send;
xfer.mxSend = db_get_int("max-upload", 250000);
xfer.maxTime = -1;
| | | 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 |
transport_stats(0, 0, 1);
socket_global_init();
memset(&xfer, 0, sizeof(xfer));
xfer.pIn = &recv;
xfer.pOut = &send;
xfer.mxSend = db_get_int("max-upload", 250000);
xfer.maxTime = -1;
xfer.remoteVersion = RELEASE_VERSION_NUMBER;
if( syncFlags & SYNC_PRIVATE ){
g.perm.Private = 1;
xfer.syncPrivate = 1;
}
blobarray_zero(xfer.aToken, count(xfer.aToken));
blob_zero(&send);
|
| ︙ | ︙ | |||
1853 1854 1855 1856 1857 1858 1859 |
") WITHOUT ROWID;"
"INSERT INTO uv_toSend(name,mtimeOnly)"
" SELECT name, 0 FROM unversioned WHERE hash IS NOT NULL;"
);
}
/*
| | > | > > | > | 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 |
") WITHOUT ROWID;"
"INSERT INTO uv_toSend(name,mtimeOnly)"
" SELECT name, 0 FROM unversioned WHERE hash IS NOT NULL;"
);
}
/*
** The request from the client always begin with a clone, pull,
** or push message.
*/
blob_appendf(&send, "pragma client-version %d %d %d\n",
RELEASE_VERSION_NUMBER, MANIFEST_NUMERIC_DATE,
MANIFEST_NUMERIC_TIME);
if( syncFlags & SYNC_CLONE ){
blob_appendf(&send, "clone 3 %d\n", cloneSeqno);
syncFlags &= ~(SYNC_PUSH|SYNC_PULL);
nCardSent++;
/* TBD: Request all transferable configuration values */
content_enable_dephantomize(0);
zOpType = "Clone";
}else if( syncFlags & SYNC_PULL ){
blob_appendf(&send, "pull %s %s\n", zSCode,
zAltPCode ? zAltPCode : zPCode);
nCardSent++;
zOpType = (syncFlags & SYNC_PUSH)?"Sync":"Pull";
if( (syncFlags & SYNC_RESYNC)!=0 && nCycle<2 ){
blob_appendf(&send, "pragma send-catalog\n");
nCardSent++;
}
}
|
| ︙ | ︙ | |||
1894 1895 1896 1897 1898 1899 1900 |
db_record_repository_filename(0);
db_multi_exec(
"CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
);
manifest_crosslink_begin();
| | | | < | | | | | > | 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 |
db_record_repository_filename(0);
db_multi_exec(
"CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
);
manifest_crosslink_begin();
/* Client sends the most recently received cookie back to the server.
** Let the server figure out if this is a cookie that it cares about.
*/
zCookie = db_get("cookie", 0);
if( zCookie ){
blob_appendf(&send, "cookie %s\n", zCookie);
}
/* Client sends gimme cards for phantoms
*/
if( (syncFlags & SYNC_PULL)!=0
|| ((syncFlags & SYNC_CLONE)!=0 && cloneSeqno==1)
){
request_phantoms(&xfer, mxPhantomReq);
}
if( syncFlags & SYNC_PUSH ){
send_unsent(&xfer);
nCardSent += send_unclustered(&xfer);
if( syncFlags & SYNC_PRIVATE ) send_private(&xfer);
}
/* Client sends configuration parameter requests. On a clone, delay sending
** this until the second cycle since the login card might fail on
** the first cycle.
*/
if( configRcvMask && ((syncFlags & SYNC_CLONE)==0 || nCycle>0) ){
const char *zName;
if( zOpType==0 ) zOpType = "Pull";
zName = configure_first_name(configRcvMask);
while( zName ){
blob_appendf(&send, "reqconfig %s\n", zName);
zName = configure_next_name(configRcvMask);
nCardSent++;
}
origConfigRcvMask = configRcvMask;
configRcvMask = 0;
}
/* Client sends a request to sync unversioned files.
** On a clone, delay sending this until the second cycle since
** the login card might fail on the first cycle.
*/
if( (syncFlags & SYNC_UNVERSIONED)!=0
&& ((syncFlags & SYNC_CLONE)==0 || nCycle>0)
&& !uvHashSent
){
blob_appendf(&send, "pragma uv-hash %s\n", unversioned_content_hash(0));
nCardSent++;
uvHashSent = 1;
}
/* On a "fossil config push", the client send configuration parameters
** being pushed up to the server */
if( configSendMask ){
if( zOpType==0 ) zOpType = "Push";
nCardSent += configure_send_group(xfer.pOut, configSendMask, 0);
configSendMask = 0;
}
/* Send unversioned files present here on the client but missing or
|
| ︙ | ︙ | |||
2006 2007 2008 2009 2010 2011 2012 |
}
blob_appendf(&send, "pragma ci-lock %s %s\n", zCkinLock, zClientId);
zCkinLock = 0;
}else if( zClientId ){
blob_appendf(&send, "pragma ci-unlock %s\n", zClientId);
}
| | | 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 |
}
blob_appendf(&send, "pragma ci-lock %s %s\n", zCkinLock, zClientId);
zCkinLock = 0;
}else if( zClientId ){
blob_appendf(&send, "pragma ci-unlock %s\n", zClientId);
}
/* Append randomness to the end of the uplink message. This makes all
** messages unique so that that the login-card nonce will always
** be unique.
*/
zRandomness = db_text(0, "SELECT hex(randomblob(20))");
blob_appendf(&send, "# %s\n", zRandomness);
free(zRandomness);
|
| ︙ | ︙ | |||
2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 |
}
nCardSent = 0;
nCardRcvd = 0;
xfer.nFileSent = 0;
xfer.nDeltaSent = 0;
xfer.nGimmeSent = 0;
xfer.nIGotSent = 0;
lastPctDone = -1;
blob_reset(&send);
| > | > > | > | 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 |
}
nCardSent = 0;
nCardRcvd = 0;
xfer.nFileSent = 0;
xfer.nDeltaSent = 0;
xfer.nGimmeSent = 0;
xfer.nIGotSent = 0;
xfer.nPrivIGot = 0;
lastPctDone = -1;
blob_reset(&send);
blob_appendf(&send, "pragma client-version %d %d %d\n",
RELEASE_VERSION_NUMBER, MANIFEST_NUMERIC_DATE,
MANIFEST_NUMERIC_TIME);
rArrivalTime = db_double(0.0, "SELECT julianday('now')");
/* Send the send-private pragma if we are trying to sync private data */
if( syncFlags & SYNC_PRIVATE ){
blob_append(&send, "pragma send-private\n", -1);
}
/* Begin constructing the next message (which might never be
** sent) by beginning with the pull or push cards
*/
if( syncFlags & SYNC_PULL ){
blob_appendf(&send, "pull %s %s\n", zSCode,
zAltPCode ? zAltPCode : zPCode);
nCardSent++;
}
if( syncFlags & SYNC_PUSH ){
blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
nCardSent++;
}
go = 0;
|
| ︙ | ︙ | |||
2107 2108 2109 2110 2111 2112 2113 |
fflush(stdout);
}
}
/* file HASH SIZE \n CONTENT
** file HASH DELTASRC SIZE \n CONTENT
**
| | | | > | | | | 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 |
fflush(stdout);
}
}
/* file HASH SIZE \n CONTENT
** file HASH DELTASRC SIZE \n CONTENT
**
** Client receives a file transmitted from the server.
*/
if( blob_eq(&xfer.aToken[0],"file") ){
xfer_accept_file(&xfer, (syncFlags & SYNC_CLONE)!=0, 0, 0);
nArtifactRcvd++;
}else
/* cfile HASH USIZE CSIZE \n CONTENT
** cfile HASH DELTASRC USIZE CSIZE \n CONTENT
**
** Client receives a compressed file transmitted from the server.
*/
if( blob_eq(&xfer.aToken[0],"cfile") ){
xfer_accept_compressed_file(&xfer, 0, 0);
nArtifactRcvd++;
}else
/* uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT
**
** Client accepts an unversioned file from the server.
*/
if( blob_eq(&xfer.aToken[0], "uvfile") ){
xfer_accept_unversioned_file(&xfer, 1);
nArtifactRcvd++;
nUvFileRcvd++;
if( syncFlags & SYNC_VERBOSE ){
fossil_print("\rUnversioned-file received: %s\n",
blob_str(&xfer.aToken[1]));
}
}else
/* gimme HASH
**
** Client receives an artifact request from the server.
** If the file is a manifest, assume that the server will also want
** to know all of the content artifacts associated with the manifest
** and send those too.
*/
if( blob_eq(&xfer.aToken[0], "gimme")
&& xfer.nToken==2
&& blob_is_hname(&xfer.aToken[1])
){
if( syncFlags & SYNC_PUSH ){
int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
|
| ︙ | ︙ | |||
2174 2175 2176 2177 2178 2179 2180 |
&& blob_eq(&xfer.aToken[0], "igot")
&& blob_is_hname(&xfer.aToken[1])
){
int rid;
int isPriv = xfer.nToken>=3 && blob_eq(&xfer.aToken[2],"1");
rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
if( rid>0 ){
| > > > | > | 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 |
&& blob_eq(&xfer.aToken[0], "igot")
&& blob_is_hname(&xfer.aToken[1])
){
int rid;
int isPriv = xfer.nToken>=3 && blob_eq(&xfer.aToken[2],"1");
rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
if( rid>0 ){
if( isPriv ){
content_make_private(rid);
}else{
content_make_public(rid);
}
}else if( isPriv && !g.perm.Private ){
/* ignore private files */
}else if( (syncFlags & (SYNC_PULL|SYNC_CLONE))!=0 ){
rid = content_new(blob_str(&xfer.aToken[1]), isPriv);
if( rid ) newPhantom = 1;
}
remote_has(rid);
|
| ︙ | ︙ | |||
2259 2260 2261 2262 2263 2264 2265 |
zName);
}
}else
/* push SERVERCODE PRODUCTCODE
**
** Should only happen in response to a clone. This message tells
| | | | 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 |
zName);
}
}else
/* push SERVERCODE PRODUCTCODE
**
** Should only happen in response to a clone. This message tells
** the client what product code to use for the new database.
*/
if( blob_eq(&xfer.aToken[0],"push")
&& xfer.nToken==3
&& (syncFlags & SYNC_CLONE)!=0
&& blob_is_hname(&xfer.aToken[2])
){
if( zPCode==0 ){
zPCode = mprintf("%b", &xfer.aToken[2]);
db_set("project-code", zPCode, 0);
}
if( cloneSeqno>0 ) blob_appendf(&send, "clone 3 %d\n", cloneSeqno);
nCardSent++;
}else
/* config NAME SIZE \n CONTENT
**
** Client receive a configuration value from the server.
**
** The received configuration setting is silently ignored if it was
** not requested by a prior "reqconfig" sent from client to server.
*/
if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3
&& blob_is_int(&xfer.aToken[2], &size) ){
const char *zName = blob_str(&xfer.aToken[1]);
|
| ︙ | ︙ | |||
2298 2299 2300 2301 2302 2303 2304 |
blob_reset(&content);
blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
}else
/* cookie TEXT
**
| | | | > | | 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 |
blob_reset(&content);
blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
}else
/* cookie TEXT
**
** The client reserves a cookie from the server. 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
/* private
**
** The server tells the client that the next "file" or "cfile" will
** contain private content.
*/
if( blob_eq(&xfer.aToken[0], "private") ){
xfer.nextIsPrivate = 1;
}else
/* clone_seqno N
**
** When doing a clone, the server tries to send all of its artifacts
** in sequence. This card indicates the sequence number of the next
** blob that needs to be sent. If N<=0 that indicates that all blobs
** have been sent.
*/
if( blob_eq(&xfer.aToken[0], "clone_seqno") && xfer.nToken==2 ){
blob_is_int(&xfer.aToken[1], &cloneSeqno);
}else
/* message MESSAGE
**
** A message is received from the server. Print it.
** Similar to "error" but does not stop processing.
**
** If the "login failed" message is seen, clear the sync password prior
** to the next cycle.
*/
if( blob_eq(&xfer.aToken[0],"message") && xfer.nToken==2 ){
char *zMsg = blob_terminate(&xfer.aToken[1]);
defossilize(zMsg);
|
| ︙ | ︙ | |||
2359 2360 2361 2362 2363 2364 2365 |
/* pragma NAME VALUE...
**
** The server can send pragmas to try to convey meta-information to
** the client. These are informational only. Unknown pragmas are
** silently ignored.
*/
if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
| > > > > > > > > > > > > > > > > | > > > > > | 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 |
/* pragma NAME VALUE...
**
** The server can send pragmas to try to convey meta-information to
** the client. These are informational only. Unknown pragmas are
** silently ignored.
*/
if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
/* pragma server-version VERSION ?DATE? ?TIME?
**
** The servger announces to the server what version of Fossil it
** is running. The DATE and TIME are a pure numeric ISO8601 time
** for the specific check-in of the client.
*/
if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "server-version") ){
xfer.remoteVersion = atoi(blob_str(&xfer.aToken[2]));
if( xfer.nToken>=5 ){
xfer.remoteDate = atoi(blob_str(&xfer.aToken[3]));
xfer.remoteTime = atoi(blob_str(&xfer.aToken[4]));
}
}
/* pragma uv-pull-only
**
** If the server is unwill to accept new unversioned content (because
** this client lacks the necessary permissions) then it sends a
** "uv-pull-only" pragma so that the client will know not to waste
** bandwidth trying to upload unversioned content. If the server
** does accept new unversioned content, it sends "uv-push-ok".
*/
if( blob_eq(&xfer.aToken[1], "uv-pull-only") ){
fossil_print(
"Warning: uv-pull-only \n"
" Unable to push unversioned content because you lack\n"
" sufficient permission on the server\n"
);
if( syncFlags & SYNC_UV_REVERT ) uvDoPush = 1;
}else if( blob_eq(&xfer.aToken[1], "uv-push-ok") ){
uvDoPush = 1;
}
/* pragma ci-lock-fail USER-HOLDING-LOCK LOCK-TIME
**
|
| ︙ | ︙ | |||
2397 2398 2399 2400 2401 2402 2403 |
}
g.ckinLockFail = fossil_strdup(zUser);
}
}else
/* error MESSAGE
**
| > | | 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 |
}
g.ckinLockFail = fossil_strdup(zUser);
}
}else
/* error MESSAGE
**
** The server is reporting an error. The client will abandon
** the sync session.
**
** Except, when cloning we will sometimes get an error on the
** first message exchange because the project-code is unknown
** and so the login card on the request was invalid. The project-code
** is returned in the reply before the error card, so second and
** subsequent messages should be OK. Nevertheless, we need to ignore
** the error card on the first message of a clone.
|
| ︙ | ︙ | |||
2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 |
/* If we have one or more files queued to send, then go
** another round
*/
if( xfer.nFileSent+xfer.nDeltaSent>0 || uvDoPush ){
go = 1;
}
/* If this is a clone, the go at least two rounds */
if( (syncFlags & SYNC_CLONE)!=0 && nCycle==1 ) go = 1;
/* Stop the cycle if the server sends a "clone_seqno 0" card and
** we have gone at least two rounds. Always go at least two rounds
** on a clone in order to be sure to retrieve the configuration
| > | 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 |
/* If we have one or more files queued to send, then go
** another round
*/
if( xfer.nFileSent+xfer.nDeltaSent>0 || uvDoPush ){
go = 1;
}
if( xfer.nPrivIGot>0 && nCycle==1 ) go = 1;
/* If this is a clone, the go at least two rounds */
if( (syncFlags & SYNC_CLONE)!=0 && nCycle==1 ) go = 1;
/* Stop the cycle if the server sends a "clone_seqno 0" card and
** we have gone at least two rounds. Always go at least two rounds
** on a clone in order to be sure to retrieve the configuration
|
| ︙ | ︙ |
| ︙ | ︙ | |||
78 79 80 81 82 83 84 |
@ <input type="submit" name="sync" value="%h(zButton)" />
@ </div></form>
@
if( P("sync") ){
user_select();
url_enable_proxy(0);
@ <pre class="xfersetup">
| | | 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
@ <input type="submit" name="sync" value="%h(zButton)" />
@ </div></form>
@
if( P("sync") ){
user_select();
url_enable_proxy(0);
@ <pre class="xfersetup">
client_sync(syncFlags, 0, 0, 0);
@ </pre>
}
}
style_footer();
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
590 591 592 593 594 595 596 |
if( find_option("dereference","h",0)!=0 ){
eFType = ExtFILE;
}
zip_open();
for(i=3; i<g.argc; i++){
blob_zero(&file);
blob_read_from_file(&file, g.argv[i], eFType);
| | | 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 |
if( find_option("dereference","h",0)!=0 ){
eFType = ExtFILE;
}
zip_open();
for(i=3; i<g.argc; i++){
blob_zero(&file);
blob_read_from_file(&file, g.argv[i], eFType);
zip_add_file(&sArchive, g.argv[i], &file, file_perm(0,eFType));
blob_reset(&file);
}
zip_close(&sArchive);
blob_write_to_file(&zip, g.argv[2]);
}
/*
|
| ︙ | ︙ | |||
612 613 614 615 616 617 618 | ** If the RID object does not exist in the repository, then ** pZip is zeroed. ** ** zDir is a "synthetic" subdirectory which all zipped files get ** added to as part of the zip file. It may be 0 or an empty string, ** in which case it is ignored. The intention is to create a zip which ** politely expands into a subdir instead of filling your current dir | | | 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 | ** If the RID object does not exist in the repository, then ** pZip is zeroed. ** ** zDir is a "synthetic" subdirectory which all zipped files get ** added to as part of the zip file. It may be 0 or an empty string, ** in which case it is ignored. The intention is to create a zip which ** politely expands into a subdir instead of filling your current dir ** with source files. For example, pass a commit hash or "ProjectName". ** */ static void zip_of_checkin( int eType, /* Type of archive (ZIP or SQLAR) */ int rid, /* The RID of the checkin to build the archive from */ Blob *pZip, /* Write the archive content into this blob */ const char *zDir, /* Top-level directory of the archive */ |
| ︙ | ︙ |
| ︙ | ︙ | |||
24 25 26 27 28 29 30 |
}
proc manifest_comment {comment} {
string map [list { } {\\s} \n {\\n} \r {\\r}] $comment
}
proc uuid_from_commit {res var} {
| | | | | | | | | | | | | | 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 |
}
proc manifest_comment {comment} {
string map [list { } {\\s} \n {\\n} \r {\\r}] $comment
}
proc uuid_from_commit {res var} {
upvar $var HASH
regexp {^New_Version: ([0-9a-f]{40})[0-9a-f]*$} $res m HASH
}
proc uuid_from_branch {res var} {
upvar $var HASH
regexp {^New branch: ([0-9a-f]{40})[0-9a-f]*$} $res m HASH
}
proc uuid_from_checkout {var} {
global RESULT
upvar $var HASH
fossil status
regexp {checkout:\s+([0-9a-f]{40})[0-9a-f]*} $RESULT m HASH
}
# Make sure we are not in an open repository and initialize new repository
test_setup
########################################
# Setup: Add file and commit #
########################################
if {![uuid_from_checkout HASHINIT]} {
test amend-checkout-failure false
test_cleanup_then_return
}
write_file datafile "data"
fossil add datafile
fossil commit -m "c1"
if {![uuid_from_commit $RESULT HASH]} {
test amend-setup-failure false
test_cleanup_then_return
}
########################################
# Test: -branch #
########################################
set HASHB HASHB
write_file datafile "data.file"
fossil commit -m "c2"
if {![uuid_from_commit $RESULT HASHB]} {
test amend-branch.setup false
}
fossil amend $HASHB -branch amended-branch
test amend-branch-1.1 {[regexp {tags:\s+amended-branch} $RESULT]}
fossil branch ls
test amend-branch-1.2 {[string first "* amended-branch" $RESULT] != -1}
fossil tag list
test amend-branch-1.3 {[string first amended-branch $RESULT] != -1}
fossil tag list --raw $HASHB
test amend-branch-1.4 {[string first "branch=amended-branch" $RESULT] != -1}
test amend-branch-1.5 {[string first "sym-amended-branch" $RESULT] != -1}
fossil timeline -n 1
test amend-branch-1.6 {[string match {*Move*to*branch*amended-branch*} $RESULT]}
########################################
# Test: -bgcolor #
|
| ︙ | ︙ | |||
100 101 102 103 104 105 106 |
acf #acf
123 #123
#1234 #1234
1234 1234
123456 #123456
} {
incr tc
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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 |
acf #acf
123 #123
#1234 #1234
1234 1234
123456 #123456
} {
incr tc
fossil amend $HASH -bgcolor $color
test amend-bgcolor-1.$tc.a {[string match "*hash:*$HASH*" $RESULT]}
fossil tag list --raw $HASH
test amend-bgcolor-1.$tc.b {[string first "bgcolor=$result" $RESULT] != -1}
fossil timeline -n 1
test amend-bgcolor-1.$tc.c {
[string match "*Change*background*color*to*\"$result\"*" $RESULT]
}
if {[artifact_from_timeline $RESULT artid]} {
fossil artifact $artid
test amend-bgcolor-1.$tc.d {
[string match "*T +bgcolor $HASH* $result*" $RESULT]
}
} else {
if {$VERBOSE} { protOut "No artifact found in timeline output" }
test amend-bgcolor-1.$tc.d false
}
}
fossil amend $HASH -bgcolor {}
test amend-bgcolor-2.1 {[string match "*hash:*$HASH*" $RESULT]}
fossil tag list --raw $HASH
test amend-bgcolor-2.2 {
[string first "bgcolor=" $RESULT] == -1 &&
[string first "bgcolor" $RESULT] != -1
}
fossil timeline -n 1
test amend-bgcolor-2.3 {[string match "*Cancel*background*color.*" $RESULT]}
if {[artifact_from_timeline $RESULT artid]} {
fossil artifact $artid
test amend-bgcolor-2.4 {[string match "*T -bgcolor $HASH*" $RESULT]}
} else {
if {$VERBOSE} { protOut "No artifact found in timeline output" }
test amend-bgcolor-2.4 false
}
########################################
# Test: -branchcolor #
########################################
set HASH2 HASH2
fossil branch new brclr $HASH
if {![uuid_from_branch $RESULT HASH2]} {
test amend-branchcolor.setup false
}
fossil update $HASH2
fossil amend $HASH2 -branchcolor yellow
test amend-branchcolor-1.1 {[string match "*hash:*$HASH2*" $RESULT]}
fossil tag ls --raw $HASH2
test amend-branchcolor-1.2 {[string first "bgcolor=yellow" $RESULT] != -1}
fossil timeline -n 1
test amend-branchcolor-1.3 {
[string match {*Change*branch*background*color*to*"yellow".*} $RESULT]
}
if {[regexp {(?x)[0-9]{2}(?::[0-9]{2}){2}\s+\[([0-9a-f]+)]} $RESULT m artid]} {
fossil artifact $artid
test amend-branchcolor-1.4 {
[string match "*T \*bgcolor $HASH2* yellow*" $RESULT]
}
} else {
if {$VERBOSE} { protOut "No artifact found in timeline output" }
test amend-branchcolor-1.4 false
}
set HASHN HASHN
write_file datafile "brclr"
fossil commit -m "brclr"
if {![uuid_from_commit $RESULT HASHN]} {
test amend-branchcolor-propagating.setup false
}
write_file datafile "bc1"
fossil commit -m "mc1"
write_file datafile "bc2"
fossil commit -m "mc2"
fossil amend $HASHN -branchcolor deadbe
test amend-branchcolor-2.1 {[string match "*hash:*$HASHN*" $RESULT]}
fossil tag ls --raw current
test amend-branchcolor-2.2 {[string first "bgcolor=#deadbe" $RESULT] != -1}
fossil timeline -n 1
test amend-branchcolor-2.3 {
[string match {*Change*branch*background*color*to*"#deadbe".*} $RESULT]
}
########################################
# Test: -author #
########################################
fossil amend $HASH -author author-test
test amend-author-1.1 {[string match {*comment:*(user:*author-test)*} $RESULT]}
fossil tag ls --raw $HASH
test amend-author-1.2 {[string first "user=author-test" $RESULT] != -1}
fossil timeline -n 1
test amend-author-1.3 {[string match {*Change*user*to*"author-test".*} $RESULT]}
########################################
# Test: -date #
########################################
set timestamp [clock scan yesterday]
set date [clock format $timestamp -format "%Y-%m-%d" -gmt 1]
set time [clock format $timestamp -format "%H:%M:%S" -gmt 1]
set datetime "$date $time"
fossil amend $HASHINIT -date $datetime
test amend-date-1.1 {[string match "*hash:*$HASHINIT*$datetime*" $RESULT]}
fossil tag ls --raw $HASHINIT
test amend-date-1.2 {[string first "date=$datetime" $RESULT] != -1}
fossil timeline -n 1
test amend-date-1.3 {[string match "*Timestamp*$date*$time*" $RESULT]}
set badformats {
"%+"
"%Y-%m-%d %H:%M%:%S %Z"
"%d/%m/%Y %H:%M%:%S %Z"
"%d/%m/%Y %H:%M%:%S"
"%d/%m/%Y"
}
set sc 0
foreach badformat $badformats {
incr sc
set datetime [clock format $timestamp -format $badformat -gmt 1]
fossil amend $HASHINIT -date $datetime -expectError
test amend-date-2.$sc {[string first "YYYY-MM-DD HH:MM:SS" $RESULT] != -1}
}
########################################
# Test: -hide #
########################################
set HASHH HASHH
fossil revert
fossil update trunk
fossil branch new tohide current
if {![uuid_from_branch $RESULT HASHH]} {
test amend-hide-setup false
}
fossil amend $HASHH -hide
test amend-hide-1.1 {[string match "*hash:*$HASHH*" $RESULT]}
fossil tag ls --raw $HASHH
test amend-hide-1.2 {[string first "hidden" $RESULT] != -1}
fossil timeline -n 1
test amend-hide-1.3 {[string match {*Add*propagating*"hidden".*} $RESULT]}
########################################
# Test: -close #
########################################
set HASHC HASHC
fossil branch new cllf $HASH
if {![uuid_from_branch $RESULT HASHC]} {
test amend-close.setup false
}
fossil update $HASHC
fossil amend $HASHC -close
test amend-close-1.1.a {[string match "*hash:*$HASHC*" $RESULT]}
test amend-close-1.1.b {
[string match "*comment:*Create*new*branch*named*\"cllf\"*" $RESULT]
}
fossil tag ls --raw $HASHC
test amend-close-1.2 {[string first "closed" $RESULT] != -1}
fossil timeline -n 1
test amend-close-1.3 {[string match {*Mark*"Closed".*} $RESULT]}
write_file datafile "cllf"
fossil commit -m "should fail" -expectError
test amend-close-2 {[string first "closed leaf" $RESULT] != -1}
set HASH3 HASH3
fossil revert
fossil update trunk
write_file datafile "cb"
fossil commit -m "closed-branch" --branch "closebranch"
if {![uuid_from_commit $RESULT HASH3]} {
test amend-close-3.setup false
}
write_file datafile "b1"
fossil commit -m "m1"
write_file datafile "b2"
fossil commit -m "m2"
fossil amend $HASH3 --close
test amend-close-3.1 {[string match "*hash:*$HASH3*" $RESULT]}
fossil tag ls --raw current
test amend-close-3.2 {[string first "closed" $RESULT] != -1}
fossil timeline -n 1
test amend-close-3.3 {
[string match "*Add*propagating*\"closed\".*" $RESULT]
}
write_file datafile "changed"
|
| ︙ | ︙ | |||
308 309 310 311 312 313 314 |
}
foreach res $result {
append t1exp ", $res"
append t2exp "sym-$res*"
append t3exp "Add*tag*\"$res\".*"
append t5exp "Cancel*tag*\"$res\".*"
}
| | | | | | | | | | | | | | | | | | | | | | | < < < | | | | 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 |
}
foreach res $result {
append t1exp ", $res"
append t2exp "sym-$res*"
append t3exp "Add*tag*\"$res\".*"
append t5exp "Cancel*tag*\"$res\".*"
}
eval fossil amend $HASH $tags
test amend-tag-$tc.1 {[string match "*hash:*$HASH*tags:*$t1exp*" $RESULT]}
fossil tag ls --raw $HASH
test amend-tag-$tc.2 {[string match $t2exp $RESULT]}
fossil timeline -n 1
test amend-tag-$tc.3 {[string match $t3exp $RESULT]}
eval fossil amend $HASH $cancels
test amend-tag-$tc.4 {![string match "*tags:*$t1exp*" $RESULT]}
fossil timeline -n 1
test amend-tag-$tc.5 {[string match $t5exp $RESULT]}
}
########################################
# Test: -comment #
########################################
proc prep-test {comment content} {
global HASH RESULT
fossil revert
fossil update trunk
write_file datafile $comment
fossil commit -m $content
if {![uuid_from_commit $RESULT HASH]} {
set HASH ""
}
}
proc test-comment {name HASH comment} {
global VERBOSE RESULT
test amend-comment-$name.1 {
[string match "*hash:*$HASH*comment:*$comment*" $RESULT]
}
fossil timeline -n 1
if {[artifact_from_timeline $RESULT artid]} {
fossil artifact $artid
test amend-comment-$name.2 {
[string match "*T +comment $HASH* *[manifest_comment $comment]*" $RESULT]
}
} else {
if {$VERBOSE} { protOut "No artifact found in timeline output: $RESULT" }
test amend-comment-$name.2 false
}
fossil timeline -n 1
test amend-comment-$name.3 {
[string match "*[short_uuid $HASH]*Edit*check-in*comment.*" $RESULT]
}
fossil info $HASH
test amend-comment-$name.4 {
[string match "*hash:*$HASH*comment:*$comment*" $RESULT]
}
}
prep-test "revision 1" "revision 1"
fossil amend $HASH -comment "revised revision 1"
test-comment 1 $HASH "revised revision 1"
prep-test "revision 2" "revision 2"
fossil amend $HASH -m "revised revision 2 with -m"
test-comment 2 $HASH "revised revision 2 with -m"
prep-test "revision 3" "revision 3"
write_file commitmsg "revision 3 revised"
fossil amend $HASH -message-file commitmsg
test-comment 3 $HASH "revision 3 revised"
prep-test "revision 4" "revision 4"
write_file commitmsg "revision 4 revised with -M"
fossil amend $HASH -M commitmsg
test-comment 4 $HASH "revision 4 revised with -M"
prep-test "final comment" "final content"
if {[catch {exec which ed} result] == 0} {
fossil settings editor "ed -s"
set comment "interactive edited comment"
fossil_maybe_answer "a\n$comment\n.\nw\nq\n" amend $HASH --edit-comment
test-comment 5 $HASH $comment
}
########################################
# Test: NULL hash #
########################################
fossil amend {} -close -expectError
test amend-null-uuid {$CODE && [string first "no such check-in" $RESULT] != -1}
###############################################################################
test_cleanup
|
| ︙ | ︙ | |||
16 17 18 19 20 21 22 | ############################################################################ # # Test command line parsing # test_setup "" | | > > > > > > > > | > > > | > > | > > > > > | | 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 |
############################################################################
#
# Test command line parsing
#
test_setup ""
proc cmd-line {usefile testname args} {
set i 1
foreach {cmdline result} $args {
if {$usefile} {
set cmdlinefile [file join \
[getTemporaryPath] fossil-cmd-line-$testname.txt]
write_file $cmdlinefile $cmdline
fossil test-echo --args $cmdlinefile
file delete $cmdlinefile
} else {
fossil test-echo $cmdline
}
test cmd-line-$testname.$i \
{[lrange [split $::RESULT \n] 3 end]=="\{argv\[2\] = \[$result\]\}"}
incr i
}
}
cmd-line false 100 abc abc a\"bc a\"bc \"abc\" \"abc\"
#
# NOTE: Use an --args file on Windows to avoid unwanted glob expansion
# from MinGW and/or the MSVCRT.
#
cmd-line $is_windows 101 * * *.* *.*
###############################################################################
test_cleanup
|
| ︙ | ︙ | |||
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | write_file line-0064 "$a6\n" set a7 $a6$a6 set a8 $a7$a7 set a9 $a8$a8 set a10 $a9$a9 write_file line-1024 "$a10\n" set a11 $a10$a10 set a12 $a11$a11 write_file line-4096 "$a12\n" set a13 $a12$a12 write_file line-8192 "$a13\n" set a14 $a13$a13 set a15 $a14$a14 set a16 $a15$a15 write_file line-64K "$a16\n" # UTF-8 extends 7-bit ASCII using bytes 80 and above to encode # larger character codes. Unicode uses U+0 through U+10FFFF only, # with U+D800 through U+DFFF reserved for surrogate pairs. # UTF-8 is valid if it is the shortest possible coding, encodes a | > > > | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | write_file line-0064 "$a6\n" set a7 $a6$a6 set a8 $a7$a7 set a9 $a8$a8 set a10 $a9$a9 write_file line-1024 "$a10\n" set a11 $a10$a10 write_file line-2048 "$a11\n" set a12 $a11$a11 write_file line-4096 "$a12\n" set a13 $a12$a12 write_file line-8192 "$a13\n" set a14 $a13$a13 write_file line-16K "$a14\n" set a15 $a14$a14 write_file line-32K "$a15\n" set a16 $a15$a15 write_file line-64K "$a16\n" # UTF-8 extends 7-bit ASCII using bytes 80 and above to encode # larger character codes. Unicode uses U+0 through U+10FFFF only, # with U+D800 through U+DFFF reserved for surrogate pairs. # UTF-8 is valid if it is the shortest possible coding, encodes a |
| ︙ | ︙ | |||
116 117 118 119 120 121 122 123 124 | 1\tbinary\tbinary data 1\tcr-lf-crlf.txt\tmixed line endings 1\tcr-only.txt\tCR line endings 1\tcrlf.txt\tCR/LF line endings 0\tempty\t 0\tline-0064\t 0\tline-1024\t 0\tline-4096\t 1\tline-64K\tlong lines | > > > | | 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | 1\tbinary\tbinary data 1\tcr-lf-crlf.txt\tmixed line endings 1\tcr-only.txt\tCR line endings 1\tcrlf.txt\tCR/LF line endings 0\tempty\t 0\tline-0064\t 0\tline-1024\t 0\tline-16K\t 0\tline-2048\t 1\tline-32K\tlong lines 0\tline-4096\t 1\tline-64K\tlong lines 0\tline-8192\t 0\tplain.txt\t 1\tutf-16be-bombe-hello\tUnicode 1\tutf-16be-bomle-hello\tUnicode 1\tutf-16be-hello\tbinary data 1\tutf-16be.txt\tUnicode 1\tutf-16le-bombe-hello\tUnicode 1\tutf-16le-bomle-hello\tUnicode |
| ︙ | ︙ |
> > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<!DOCTYPE html>
<html>
<head>
<title>Title: Content Security Policy Test</title>
</head>
<body>
<h1>Content Security Policy Test</h1>
<p>If the content-security-policy is ineffective, a pop-up dialog
box will appears. If there is no dialog box, then CSP is working
correctly.</p>
<script>alert('Content Security Policy is ineffective');</script>
<img src='/' onerror='alert("CSP is ineffective")'>
<p>As a double-check, open the Developer Console in your web-browser
and verify that two CSP violations were detected and blocked.</p>
</body>
|
| ︙ | ︙ | |||
28 29 30 31 32 33 34 | file mkdir .fossil-settings write_file [file join .fossil-settings binary-glob] "*" write_file file0.dat ""; # no content. write_file file1.dat "test file 1 (one line no term)." write_file file2.dat "test file 2 (NUL character).\0" | | | | | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | file mkdir .fossil-settings write_file [file join .fossil-settings binary-glob] "*" write_file file0.dat ""; # no content. write_file file1.dat "test file 1 (one line no term)." write_file file2.dat "test file 2 (NUL character).\0" write_file file3.dat "test file 3 (long line).[string repeat x 32768]" write_file file4.dat "test file 4 (long line).[string repeat y 32768]\ntwo" write_file file5.dat "[string repeat z 32768]\ntest file 5 (long line)." fossil add $rootDir fossil commit -m "c1" ############################################################################### fossil ls |
| ︙ | ︙ | |||
54 55 56 57 58 59 60 | ================================================================== --- file0.dat +++ file0.dat cannot compute difference between binary files}} ############################################################################### | | | 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
==================================================================
--- file0.dat
+++ file0.dat
cannot compute difference between binary files}}
###############################################################################
write_file file1.dat [string repeat z 32768]
fossil diff file1.dat
test diff-file1-1 {[normalize_result] eq {Index: file1.dat
==================================================================
--- file1.dat
+++ file1.dat
cannot compute difference between binary files}}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
48 49 50 51 52 53 54 | return "" } ############################################################################### set fileName [lindex $argv 0] | | | | 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 |
return ""
}
###############################################################################
set fileName [lindex $argv 0]
if {[file exists $fileName]} {
set data [readFile $fileName]
} else {
set data ""
}
###############################################################################
if {[info exists env(FAKE_EDITOR_SCRIPT)]} {
#
# NOTE: If an error is caught while evaluating this script, catch
# it and return, which will also skip writing the (possibly
# modified) content back to the original file.
#
set script $env(FAKE_EDITOR_SCRIPT)
set code [catch $script error]
if {$code != 0} {
if {[info exists env(FAKE_EDITOR_VERBOSE)]} {
if {[info exists errorInfo]} {
puts stdout "ERROR ($code): $errorInfo"
} else {
puts stdout "ERROR ($code): $error"
}
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
58 59 60 61 62 63 64 |
proc getLatestTrunkCheckIn {} {
tclEval {
#
# NOTE: Get the unique Id of the latest check-in on trunk.
#
return [lindex [regexp -line -inline -nocase -- \
| | | 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
proc getLatestTrunkCheckIn {} {
tclEval {
#
# NOTE: Get the unique Id of the latest check-in on trunk.
#
return [lindex [regexp -line -inline -nocase -- \
{^(?:uuid|hash):\s+([0-9A-F]{40}) } [eval [getFossilCommand \
$repository "" info trunk]]] end]
}
}
proc theSumOfAllFiles { id } {
#
# NOTE: Copy check-in Id value to the Tcl interpreter.
|
| ︙ | ︙ |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Graph Test Cases
There are test cases for the merge-riser coalescing logic that
was added on 2020-06-08.
* [e19cfba5373369b](/info/e19cfba5373369b?diff=0)
* [c779b6890464cae](/info/c779b6890464cae?diff=0)
* [eed3946bd92a499](/info/eed3946bd92a499?diff=0)
* [9e1fa626e47f147](/info/9e1fa626e47f147?diff=0)
* [68bd2e7bedb8d05](/info/68bd2e7bedb8d05?diff=0)
* [8ac66ef33b464d2](/info/8ac66ef33b464d2?diff=0)
* [ef6979eac9abded](/info/ef6979eac9abded?diff=0)
* [7766e689926c703](/info/7766e689926c703?diff=0)
* [642f4dcfa24f1f9](/info/642f4dcfa24f1f9?diff=0)
* [3ea66260b5555d2](/info/3ea66260b5555d2?diff=0)
* [66ae70a54b20656](/info/66ae70a54b20656?diff=0)
* [b0f2a0ac53926c9](/info/b0f2a0ac53926c9?diff=0)
* [303e7af7c31866c](/info/303e7af7c31866c?diff=0)
* [b31afcc2cab1dc4](/info/b31afcc2cab1dc4?diff=0)
* [1a164e5fb76a46b](/info/1a164e5fb76a46b?diff=0)
* [f325b2343e6a18f](/info/f325b2343e6a18f?diff=0)
* [2d75e87b760c0a9](/info/2d75e87b760c0a9?diff=0)
* [76442af7e13267b](/info/76442af7e13267b?diff=0)
The list above was generated by the following script:
~~~~~
.mode list
SELECT printf(' * [%s](/info/%s?diff=0)', hash, hash) FROM (
SELECT count(*) AS cnt, sum(cherrypick=1) AS cp, sum(cherrypick=0) AS n,
(SELECT substr(uuid,1,15) FROM blob WHERE rid=cid) AS hash
FROM (
SELECT cid, 0 AS cherrypick FROM plink WHERE NOT isprim
UNION ALL
SELECT childid, 1 FROM cherrypick
)
GROUP BY cid
HAVING (cp>0 AND n>0) OR cp>3 OR n>2
ORDER BY cnt
);
~~~~~
Similar links to the SQLite repository:
* [7f72fc4f47445a2](https://sqlite.org/src/info/7f72fc4f47445a2?diff=0)
* [db2935473eab91c](https://sqlite.org/src/info/db2935473eab91c?diff=0)
* [a56506b9387a067](https://sqlite.org/src/info/a56506b9387a067?diff=0)
* [d59567dda231e7f](https://sqlite.org/src/info/d59567dda231e7f?diff=0)
* [2b750b0f74e5a11](https://sqlite.org/src/info/2b750b0f74e5a11?diff=0)
* [c697d2f83c2d8ea](https://sqlite.org/src/info/c697d2f83c2d8ea?diff=0)
* [b330c7ff6fd1230](https://sqlite.org/src/info/b330c7ff6fd1230?diff=0)
* [746fcd2fd412ddc](https://sqlite.org/src/info/746fcd2fd412ddc?diff=0)
* [71866b367f32b5a](https://sqlite.org/src/info/71866b367f32b5a?diff=0)
* [05418b2a4a6e6a9](https://sqlite.org/src/info/05418b2a4a6e6a9?diff=0)
Generated by a very similar script:
~~~~~
SELECT printf(' * [%s](https://sqlite.org/src/info/%s?diff=0)', hash, hash) FROM (
SELECT count(*) AS cnt, sum(cherrypick=1) AS cp, sum(cherrypick=0) AS n,
(SELECT substr(uuid,1,15) FROM blob WHERE rid=cid) AS hash
FROM (
SELECT cid, 0 AS cherrypick FROM plink WHERE NOT isprim
UNION ALL
SELECT childid, 1 FROM cherrypick
)
GROUP BY cid
HAVING (cp>0 AND n>0) OR cp>2 OR n>2
ORDER BY cnt
);
~~~~~
|
| ︙ | ︙ | |||
21 22 23 24 25 26 27 | # Make sure we have a build with the json command at all and that it # is not stubbed out. This assumes the current (as of 2016-01-27) # practice of eliminating all trace of the fossil json command when # not configured. If that changes, these conditions might not prevent # the rest of this file from running. fossil test-th-eval "hasfeature json" | | | > > > | 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 |
# Make sure we have a build with the json command at all and that it
# is not stubbed out. This assumes the current (as of 2016-01-27)
# practice of eliminating all trace of the fossil json command when
# not configured. If that changes, these conditions might not prevent
# the rest of this file from running.
fossil test-th-eval "hasfeature json"
if {[normalize_result] ne "1"} {
puts "Fossil was not compiled with JSON support."
test_cleanup_then_return
}
# We need a JSON parser to effectively test the JSON produced by
# fossil. It looks like the one from tcllib is exactly what we need.
# On ActiveTcl, add it with teacup. On other platforms, YMMV.
# teacup install json
# teacup install json::write
if {[catch {package require json}] != 0} then {
puts "The \"json\" package is not available."
test_cleanup_then_return
}
proc json2dict {txt} {
set rc [catch {::json::json2dict $txt} result options]
if {$rc != 0} {
protOut "JSON ERROR: $result"
return {}
}
|
| ︙ | ︙ | |||
72 73 74 75 76 77 78 |
# RESULT to the HTTP response body, and JR to a Tcl dict conversion of
# the response body.
#
# Returns the status code from the HTTP header.
proc fossil_http_json {url {cookie "Muppet=Monster"} args} {
global RESULT JR
set request "GET $url HTTP/1.1\r\nHost: localhost\r\nUser-Agent: Fossil-http-json\r\nCookie: $cookie"
| | > | 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# RESULT to the HTTP response body, and JR to a Tcl dict conversion of
# the response body.
#
# Returns the status code from the HTTP header.
proc fossil_http_json {url {cookie "Muppet=Monster"} args} {
global RESULT JR
set request "GET $url HTTP/1.1\r\nHost: localhost\r\nUser-Agent: Fossil-http-json\r\nCookie: $cookie"
set RESULT [fossil_maybe_answer $request http {*}$args --ipaddr 127.0.0.1]
set head ""; set body ""; set status "--NO_MATCH--"
regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
if {$status eq "200"} {
set JR [json2dict $body]
}
return $status
}
|
| ︙ | ︙ | |||
113 114 115 116 117 118 119 | \r }] } # handle the actual request flush stdout #exec $fossilexe | | > | 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
\r
}]
}
# handle the actual request
flush stdout
#exec $fossilexe
set RESULT [fossil_maybe_answer $request http {*}$args --ipaddr 127.0.0.1]
# separate HTTP headers from body
set head ""; set body ""; set status "--NO_MATCH--"
regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body
regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg
if {$status eq "200"} {
if {[string length $body] > 0} {
set JR [json2dict $body]
} else {
set JR ""
|
| ︙ | ︙ | |||
168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
test_dict_keys $testname [dict get $::JR payload] $okfields $badfields
}
#### VERSION AKA HAI
# The JSON API generally assumes we have a respository, so let it have one.
test_setup
# Check for basic envelope fields in the result with an error
fossil_json -expectError
test_json_envelope json-enverr [concat resultCode fossil timestamp \
resultText command procTimeUs procTimeMs] {}
test json-enverr-rc-1 {[dict get $JR resultCode] eq "FOSSIL-3002"}
| > > > | 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
test_dict_keys $testname [dict get $::JR payload] $okfields $badfields
}
#### VERSION AKA HAI
# The JSON API generally assumes we have a respository, so let it have one.
test_setup
# Stop backoffice from running during this test as it can cause hangs.
fossil settings backoffice-disable 1
# Check for basic envelope fields in the result with an error
fossil_json -expectError
test_json_envelope json-enverr [concat resultCode fossil timestamp \
resultText command procTimeUs procTimeMs] {}
test json-enverr-rc-1 {[dict get $JR resultCode] eq "FOSSIL-3002"}
|
| ︙ | ︙ | |||
305 306 307 308 309 310 311 |
test json-cap-CLI {[dict get $JR payload permissionFlags setup]}
# json cap via POST with authToken in request envelope
set anon2 [read_file anon-2]
fossil_post_json "/json/cap" $anon2
test json-cap-POSTenv-env-0 {[string length $JR] > 0}
test_json_envelope_ok json-cap-POSTenv-env
| > | > > > | > > | > > | | | > > | > > | | 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 |
test json-cap-CLI {[dict get $JR payload permissionFlags setup]}
# json cap via POST with authToken in request envelope
set anon2 [read_file anon-2]
fossil_post_json "/json/cap" $anon2
test json-cap-POSTenv-env-0 {[string length $JR] > 0}
test_json_envelope_ok json-cap-POSTenv-env
if {[catch {test json-cap-POSTenv-name \
{[dict get $JR payload name] eq "anonymous"} knownBug} jerr]} then {
test json-cap-POSTenv-name-threw 0
protOut "CAUGHT: $jerr"
}
test json-cap-POSTenv-notsetup {![dict get $JR payload permissionFlags setup]}
# json cap via GET with authToken in Cookie header
fossil_post_json "/json/cap" {} $AnonCookie
test json-cap-GETcookie-env-0 {[string length $JR] > 0}
test_json_envelope_ok json-cap-GETcookie-env-0
if {[catch {test json-cap-GETcookie-name-0 \
{[dict get $JR payload name] eq "anonymous"}} jerr]} then {
test json-cap-GETcookie-name-0-threw 0
protOut "CAUGHT: $jerr"
}
test json-cap-GETcookie-notsetup-0 {![dict get $JR payload permissionFlags setup]}
# json cap via GET with authToken in a parameter
fossil_post_json "/json/cap?authToken=[dict get $AuthAnon authToken]" {}
test json-cap-GETcookie-env-1 {[string length $JR] > 0}
test_json_envelope_ok json-cap-GETcookie-env-1
if {[catch {test json-cap-GETcookie-name-1 \
{[dict get $JR payload name] eq "anonymous"}} jerr]} then {
test json-cap-GETcookie-name-1-threw 0
protOut "CAUGHT: $jerr"
}
test json-cap-GETcookie-notsetup-1 {![dict get $JR payload permissionFlags setup]}
# whoami
# via CLI with no auth token supplied
fossil_json whoami
test_json_envelope_ok json-whoami-cli-env
test_json_payload json-whoami-cli {name capabilities} {}
|
| ︙ | ︙ | |||
673 674 675 676 677 678 679 | # which writes something (timeline creates a temp table). The "repo # is not writable" error comes back as HTML. i don't know if the # error happens before we have made the determination that the app is # in JSON mode or if the error handling is incorrectly not # recognizing JSON mode. # #test_setup x.fossil | < | | < | > > > > | | | > | > > > | > > | > | 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 |
# which writes something (timeline creates a temp table). The "repo
# is not writable" error comes back as HTML. i don't know if the
# error happens before we have made the determination that the app is
# in JSON mode or if the error handling is incorrectly not
# recognizing JSON mode.
#
#test_setup x.fossil
fossil_http_json /json/query?sql=PRAGMA%20repository.journal_mode%3Dwal $U1Cookie
test json-ROrepo-1-1 {$CODE == 0}
test json-ROrepo-1-2 {[regexp {\}\s*$} $RESULT]}
test json-ROrepo-1-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]}
test_json_envelope_ok json-http-timeline1
if {$is_windows} then {
catch {exec attrib +r .rep.fossil}; # Windows
} else {
catch {exec chmod 444 .rep.fossil}; # Unix
}
protOut "chmod 444 repo"
fossil_http_json /json/query?sql=PRAGMA%20repository.journal_mode%3Ddelete $U1Cookie -expectError --json-preserve-rc
test json-ROrepo-2-1 {$CODE != 0}
test json-ROrepo-2-2 {[regexp {\}\s*$} $RESULT]}
test json-ROrepo-2-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]}
#test_json_envelope_ok json-http-timeline2
if {$is_windows} then {
catch {exec attrib -r .rep.fossil}; # Windows
catch {exec attrib -r .rep.fossil-shm}
catch {exec attrib -r .rep.fossil-wal}
} else {
catch {exec chmod 666 .rep.fossil}; # Unix
catch {exec chmod 666 .rep.fossil-shm}
catch {exec chmod 666 .rep.fossil-wal}
}
protOut "chmod 666 repo"
#### Result Codes
# Test cases designed to stimulate each (documented) error code.
# FOSSIL-0000
# Not returned by any command. We generally verify that in the
# test_json_envelope_ok command by verifying that the resultCode
|
| ︙ | ︙ |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Markdown Emphasis Test Cases
<style>
div.markdown table {
border: 2px solid black;
border-spacing: 0;
}
div.markdown th {
border-left: 1px solid black;
border-right: 1px solid black;
border-bottom: 1px solid black;
padding: 4px 1em 4px;
text-align: left;
}
div.markdown td {
border-left: 1px solid black;
border-right: 1px solid black;
padding: 4px 1em 4px;
text-align: left;
}
</style>
See <https://spec.commonmark.org/0.29/#emphasis-and-strong-emphasis>
| Id | Source Text | Actual Rendering | Correct Rendering |
-----------------------------------------------------------------------------
| 1:| `*foo bar*` | *foo bar* | <em>foo bar</em> |
| 2:| `a * foo bar*` | a * foo bar* | a * foo bar* |
| 3:| `a*"foo"*` | a*"foo"* | a*"foo"* |
| 4:| `* a *` | * a * | * a * |
| 5:| `foo*bar*` | foo*bar* | foo<em>bar</em> |
| 6:| `5*6*78` | 5*6*78 | 5<em>6</em>78 |
| 7:| `_foo bar_` | _foo bar_ | <em>foo bar</em> |
| 8:| `_ foo bar_` | _ foo bar_ | _ foo bar_ |
| 9:| `a_"foo"_` | a_"foo"_ | a_"foo"_ |
| 10:| `foo_bar_` | foo_bar_ | foo_bar_ |
| 11:| `5_6_78` | 5_6_78 | 5_6_78 |
| 12:| `aa_"bb"_cc` | aa_"bb"_cc | aa_"bb"_cc |
| 13:| `foo-_(bar)_` | foo-_(bar)_ | foo-<em>(bar)</em> |
| 14:| `*(*foo` | *(*foo | *(*foo |
| 15:| `*(*foo*)*` | *(*foo*)* | <em>(<em>foo</em>)</em> |
| 16:| `*foo*bar` | *foo*bar | <em>foo</em>bar |
| 17:| `_foo bar _` | _foo bar _ | _foo bar _ |
| 18:| `_(_foo)` | _(_foo) | _(_foo) |
| 19:| `_(_foo_)_` | _(_foo_)_ | <em>(</em>foo<em>)</em> |
| 20:| `_foo_bar` | _foo_bar | _foo_bar |
| 21:| `_foo_bar_baz_` | _foo_bar_baz_ | <em>foo_bar_baz</em> |
| 22:| `foo_bar_baz` | foo_bar_baz | foo_bar_baz |
| 23:| `_(bar)_` | _(bar)_ | <em>(bar)</em> |
# Strong emphasis
| Id | Source Text | Actual Rendering | Correct Rendering |
-------------------------------------------------------------------------------------------
| 1:| `**foo bar**` | **foo bar** | <strong>foo bar</strong> |
| 2:| `a ** foo bar**` | a ** foo bar** | a ** foo bar** |
| 3:| `a**"foo"**` | a**"foo"** | a**"foo"** |
| 4:| `** a **` | ** a ** | ** a ** |
| 5:| `foo**bar**` | foo**bar** | foo<strong>bar</strong> |
| 6:| `5**6**78` | 5**6**78 | 5<strong>6</strong>78 |
| 7:| `__foo bar__` | __foo bar__ | <strong>foo bar</strong> |
| 8:| `__ foo bar__` | __ foo bar__ | __ foo bar__ |
| 9:| `a__"foo"__` | a__"foo"__ | a__"foo"__ |
| 10:| `foo__bar__` | foo__bar__ | foo__bar__ |
| 11:| `5__6__78` | 5__6__78 | 5__6__78 |
| 12:| `aa__"bb"__cc` | aa__"bb"__cc | aa__"bb"__cc |
| 13:| `foo-__(bar)__` | foo-__(bar)__ | foo-<strong>(bar)</strong> |
| 14:| `**(**foo` | **(**foo | **(**foo |
| 15:| `**(**foo**)**` | **(**foo**)** | <strong>(<strong>foo</strong>)</strong> |
| 16:| `**foo**bar` | **foo**bar | <strong>foo</strong>bar |
| 17:| `__foo bar __` | __foo bar __ | __foo bar __ |
| 18:| `__(__foo)` | __(__foo) | __(__foo) |
| 19:| `__(__foo__)__` | __(__foo__)__ | <strong>(</strong>foo<strong>)</strong> |
| 20:| `__foo__bar` | __foo__bar | __foo__bar |
| 21:| `__foo__bar__baz__` | __foo__bar__baz__ | <strong>foo__bar__baz</strong> |
| 22:| `foo__bar__baz` | foo__bar__baz | foo__bar__baz |
| 23:| `__(bar)__` | __(bar)__ | <strong>(bar)</strong> |
|
| ︙ | ︙ | |||
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | write_file [file join $rootDir subdirB f9] "f9" file mkdir [file join $rootDir subdirC] write_file [file join $rootDir subdirC f10] "f10" write_file [file join $rootDir subdirC f11] "f11" write_file f12 "f12" fossil add f1 f2 f3 f4 f5 f6 f7 f8 subdirB/f9 subdirC/f10 subdirC/f11 f12 fossil commit -m "c1" ######################################## # Test 1: Soft Move Relative Directory # ######################################## file mkdir [file join $rootDir subdir1] | > > > > | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | write_file [file join $rootDir subdirB f9] "f9" file mkdir [file join $rootDir subdirC] write_file [file join $rootDir subdirC f10] "f10" write_file [file join $rootDir subdirC f11] "f11" write_file f12 "f12" file mkdir [file join $rootDir subdirE a] write_file [file join $rootDir subdirE a f14] "f14" fossil add f1 f2 f3 f4 f5 f6 f7 f8 subdirB/f9 subdirC/f10 subdirC/f11 f12 fossil add subdirE/a/f14 fossil commit -m "c1" ######################################## # Test 1: Soft Move Relative Directory # ######################################## file mkdir [file join $rootDir subdir1] |
| ︙ | ︙ | |||
411 412 413 414 415 416 417 |
############################################
# Test 18: Move Directory to New Directory #
############################################
fossil mv --hard subdirC subdirD
test mv-file-new-directory-7 {
| | | > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
############################################
# Test 18: Move Directory to New Directory #
############################################
fossil mv --hard subdirC subdirD
test mv-file-new-directory-7 {
[normalize_result] eq "RENAME subdirC/f10 subdirD/f10\nRENAME subdirC/f11 subdirD/f11\nMOVED_FILE ${rootDir}/subdirC/f10\nMOVED_FILE ${rootDir}/subdirC/f11"
}
test mv-file-new-directory-8 {[file size subdirD/f10] == 3}
test mv-file-new-directory-9 {[read_file subdirD/f10] eq "f10"}
test mv-file-new-directory-10 {[file size subdirD/f11] == 3}
test mv-file-new-directory-11 {[read_file subdirD/f11] eq "f11"}
fossil revert
test mv-file-new-directory-12 {
[normalize_result] eq "DELETE subdirD/f10\nDELETE subdirD/f11\nREVERT subdirC/f10\nREVERT subdirC/f11${undoMsg}"
}
test mv-file-new-directory-13 {[file size subdirC/f10] == 3}
test mv-file-new-directory-14 {[read_file subdirC/f10] eq "f10"}
test mv-file-new-directory-15 {[file size subdirC/f11] == 3}
test mv-file-new-directory-16 {[read_file subdirC/f11] eq "f11"}
cd $rootDir
###############################################################################
# Test 19: Follow-up Soft Rename of a Directory Already Renamed on Filesystem #
###############################################################################
file rename [file join $rootDir subdirE/a] [file join $rootDir subdirE/a_renamed]
fossil mv subdirE/a subdirE/a_renamed
test mv-soft-already-renamed-directory-1 {
[normalize_result] eq "RENAME subdirE/a/f14 subdirE/a_renamed/f14"
}
test mv-soft-already-renamed-directory-2 {[file size subdirE/a_renamed/f14] == 3}
test mv-soft-already-renamed-directory-3 {[read_file subdirE/a_renamed/f14] eq "f14"}
fossil revert
test mv-soft-already-renamed-directory-4 {
[normalize_result] eq "DELETE subdirE/a_renamed/f14\nREVERT subdirE/a/f14${undoMsg}"
}
test mv-soft-already-renamed-directory-5 {[file size subdirE/a/f14] == 3}
test mv-soft-already-renamed-directory-6 {[read_file subdirE/a/f14] eq "f14"}
file delete -force [file join $rootDir subdirE/a_renamed]
cd $rootDir
###############################################################################
test_cleanup
|
| ︙ | ︙ | |||
10 11 12 13 14 15 16 17 18 19 20 21 22 23 | name of the executable under test and $SRC is the source tree. Verify that there are no errors. <li><p> Click on each of the links in in the [./graph-test-1.wiki] document and verify that all graphs are rendered correctly. <li><p> Click on each of the links in in the [./diff-test-1.wiki] document and verify that all diffs are rendered correctly. <li><p> | > > > > > > > > > > | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | name of the executable under test and $SRC is the source tree. Verify that there are no errors. <li><p> Click on each of the links in in the [./graph-test-1.wiki] document and verify that all graphs are rendered correctly. <li><p> Click on each of the links in in the [./graph-test-2.md] document and verify that all graphs are rendered correctly. <ol type="a"> <li> Also view the same check-ins on a /timeline view by clicking on the date for each check-in in the /info view, as the graph rendering is slightly different. </ol> <li><p> Click on each of the links in in the [./diff-test-1.wiki] document and verify that all diffs are rendered correctly. <li><p> |
| ︙ | ︙ |
| ︙ | ︙ | |||
184 185 186 187 188 189 190 191 192 193 194 |
fossil mv --soft f1 f1new
test 3-mv-1 {[file exists f1]}
test 3-mv-2 {![file exists f1new]}
revert-test 3-1 {} {
REVERT f1
DELETE f1new
} -exists {f1} -notexists {f1n}
###############################################################################
test_cleanup
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
fossil mv --soft f1 f1new
test 3-mv-1 {[file exists f1]}
test 3-mv-2 {![file exists f1new]}
revert-test 3-1 {} {
REVERT f1
DELETE f1new
} -exists {f1} -notexists {f1n}
# Test reverting of files under a sub-directory
test_setup
file mkdir d
write_file d/f1 "d/f1"
write_file d/f2 "d/f2"
write_file d/f3 "d/f3"
write_file d/f4 "d/f4"
fossil add d
fossil delete d/f1
fossil commit -m "d/f2 d/f3 d/f4"
## Changes to revert
fossil add d/f1
write_file d/f2 "4-1:d/f2"
fossil changes d/f2
fossil delete --soft d/f3
revert-test 4-1 {d/f1} {
UNMANAGE d/f1
} -changes {
EDITED d/f2
DELETED d/f3
} -addremove {
ADDED d/f1
} -exists {d/f1 d/f2 d/f3}
revert-test 4-2 {d/f2} {
REVERT d/f2
} -changes {
ADDED d/f1
DELETED d/f3
} -exists {d/f1 d/f2 d/f3}
revert-test 4-3 {d/f3} {
REVERT d/f3
} -changes {
ADDED d/f1
EDITED d/f2
} -exists {d/f1 d/f2 d/f3}
fossil mv --soft d/f4 d/f4new
test 4-4-mv-1 {[file exists d/f4]}
test 4-4-mv-2 {![file exists d/f4new]}
revert-test 4-4 {d/f4} {
DELETE d/f4new
REVERT d/f4
} -changes {
ADDED d/f1
EDITED d/f2
DELETED d/f3
} -exists {d/f4} -notexists {d/f4new}
## Commit changes before testing reverting of directory rename,
## otherwise there're could be sequencing issues
fossil redo
fossil commit -m "4-5:setup"
fossil mv --soft d dnew
revert-test 4-5 {d/f1 d/f2 d/f3 d/f4} {
REVERT d/f1
REVERT d/f2
UNMANAGE d/f3
REVERT d/f4
DELETE dnew/f1
DELETE dnew/f2
DELETE dnew/f4
} -addremove {
ADDED d/f3
} -exists {d/f1 d/f2 d/f3 d/f4} -notexists {dnew}
## Test reverting of changes in whole sub-directory tree
test_setup
file mkdir d
write_file f0 "f0"
write_file d/f1 "d/f1"
write_file d/f2 "d/f2"
write_file d/f3 "d/f3"
write_file d/f4 "d/f4"
fossil add f0 d
fossil delete d/f1
fossil commit -m "f0 d/f2 d/f3 d/f4"
## Changes to revert
fossil add d/f1
write_file d/f2 "5-1:d/f2"
fossil changes d/f2
fossil delete --soft d/f3
revert-test 5-1 {d} {
UNMANAGE d/f1
REVERT d/f2
REVERT d/f3
} -addremove {
ADDED d/f1
} -exists {f0 d/f1 d/f2 d/f3}
write_file f0 "5-2:f0"
fossil changes f0
revert-test 5-2 {f0 d} {
UNMANAGE d/f1
REVERT d/f2
REVERT d/f3
REVERT f0
} -addremove {
ADDED d/f1
} -exists {f0 d/f1 d/f2 d/f3}
## Commit changes before testing the revert of directory rename,
## otherwise there're could be sequencing issues
fossil commit -m "5-3:setup"
fossil changes
fossil mv --soft d dnew
revert-test 5-3 {d} {
REVERT d/f1
REVERT d/f2
REVERT d/f4
DELETE dnew/f1
DELETE dnew/f2
DELETE dnew/f4
} -addremove {
ADDED d/f3
} -exists {f0 d/f1 d/f2 d/f3 d/f4} -notexists {dnew}
## Reset/redo the undone results of revert to get to a clean checkout
fossil redo
file mkdir d/e
file mkdir d/e/f
write_file d/e/fe1 "d/e/fe1"
write_file d/e/f/ff1 "d/e/f/ff1"
file mkdir d1
file mkdir d1/e
write_file d1/e/fe1 "d1/e/fe1"
write_file d1/e/fe2 "d1/e/fe2"
fossil add d1/e/fe1
fossil commit d1/e/fe1 -m "d1/e/fe1"
write_file d1/e/fe1 "5-4:d1/e/fe1"
fossil changes d1/e/fe1
fossil add d d1
revert-test 5-4 {d d1} {
UNMANAGE d/f3
UNMANAGE d/e/fe1
UNMANAGE d/e/f/ff1
REVERT d1/e/fe1
UNMANAGE d1/e/fe2
} -addremove {
ADDED d/f3
ADDED d/e/fe1
ADDED d/e/f/ff1
ADDED d1/e/fe2
} -exists {d/f1 d/f2 d/f3 d/f4 d/e/fe1 d/e/fe1 d/e/f/ff1
d1/e/fe1 d1/e/fe2}
###############################################################################
test_cleanup
|
| ︙ | ︙ | |||
14 15 16 17 18 19 20 | # http://www.hwaci.com/drh/ # ############################################################################ # # Test manifest setting # | < < < < < < > > > > > > > > > | 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 |
# http://www.hwaci.com/drh/
#
############################################################################
#
# Test manifest setting
#
proc file_contains {fname match} {
set fp [open $fname r]
set contents [read $fp]
close $fp
set lines [split $contents "\n"]
foreach line $lines {
if {[regexp $match $line]} {
return 1
}
}
return 0
}
# We need SHA1 to effectively test the manifest files produced by
# fossil. It looks like the one from tcllib is exactly what we need.
# On ActiveTcl, add it with teacup. On other platforms, YMMV.
# teacup install sha1
if {[catch {package require sha1}] != 0} {
puts "The \"sha1\" package is not available."
test_cleanup_then_return
}
# We need a respository, so let it have one.
test_setup
#### Verify classic behavior of the manifest setting
# Setting is off by default, and there are no extra files.
|
| ︙ | ︙ |
| ︙ | ︙ | |||
37 38 39 40 41 42 43 |
# letter. It also assumes that any output lines that start with a
# lowercase letter contain a setting name starting at that same point.
#
proc extract_setting_names { data } {
set names [list]
foreach {dummy name} [regexp \
| | | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# letter. It also assumes that any output lines that start with a
# lowercase letter contain a setting name starting at that same point.
#
proc extract_setting_names { data } {
set names [list]
foreach {dummy name} [regexp \
-all -line -inline -- {^([a-z][a-z0-9\-]*) ?.*$} $data] {
lappend names $name
}
return $names
}
###############################################################################
|
| ︙ | ︙ |
> > | 1 2 | This file has a name that contains spaces. It is used to help verify that fossil can handle filenames that contain spaces. |
| ︙ | ︙ | |||
168 169 170 171 172 173 174 175 176 177 178 179 180 |
}
set keepNewline 0
set index [lsearch -exact $args -keepNewline]
if {$index != -1} {
set keepNewline 1
set args [lreplace $args $index $index]
}
foreach a $args {
lappend cmd $a
}
protOut $cmd
flush stdout
| > > > > > > > > > > | | | | > | | < | < | | > | | < | > > > > > > > > | | | | > | 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 |
}
set keepNewline 0
set index [lsearch -exact $args -keepNewline]
if {$index != -1} {
set keepNewline 1
set args [lreplace $args $index $index]
}
set whatIf 0
set index [lsearch -exact $args -whatIf]
if {$index != -1} {
set whatIf 1
set args [lreplace $args $index $index]
}
foreach a $args {
lappend cmd $a
}
protOut $cmd
flush stdout
if {$whatIf} {
protOut [pwd]; protOut $answer
set result WHAT-IF-MODE; set rc 42
} else {
if {[string length $answer] > 0} {
protOut $answer
set prompt_file [file join $::tempPath fossil_prompt_answer]
write_file $prompt_file $answer\n
set execCmd [list eval exec]
if {$keepNewline} {lappend execCmd -keepnewline}
lappend execCmd $cmd <$prompt_file
set rc [catch $execCmd result]
file delete $prompt_file
} else {
set execCmd [list eval exec]
if {$keepNewline} {lappend execCmd -keepnewline}
lappend execCmd $cmd
set rc [catch $execCmd result]
}
}
set ab(str) {child process exited abnormally}
set ab(len) [string length $ab(str)]
set ab(off) [expr {$ab(len) - 1}]
if {$rc && $expectError && \
[string range $result end-$ab(off) end] eq $ab(str)} {
set result [string range $result 0 end-$ab(len)]
}
global RESULT CODE
set CODE $rc
if {!$whatIf} {
if {($rc && !$expectError) || (!$rc && $expectError)} {
protOut "ERROR ($rc): $result" 1
} elseif {$::VERBOSE} {
protOut "RESULT ($rc): $result"
}
}
set RESULT $result
}
# Read a file into memory.
#
proc read_file {filename} {
|
| ︙ | ︙ | |||
241 242 243 244 245 246 247 |
crlf-glob \
crnl-glob \
dotfiles \
empty-dirs \
encoding-glob \
ignore-glob \
keep-glob \
| | < < < < < < < < > > > > > > > > > > > > > > > > > > > > > | 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 |
crlf-glob \
crnl-glob \
dotfiles \
empty-dirs \
encoding-glob \
ignore-glob \
keep-glob \
manifest]
return [lsort -dictionary $result]
}
# Returns the list of all supported settings.
#
proc get_all_settings {} {
#
# TODO: If the list of supported settings in "db.c" is modified, this list
# (and procedure) most likely needs to be modified as well.
#
set result [list \
access-log \
admin-log \
allow-symlinks \
auto-captcha \
auto-hyperlink \
auto-shun \
autosync \
autosync-tries \
backoffice-disable \
backoffice-logfile \
backoffice-nodelay \
binary-glob \
case-sensitive \
clean-glob \
clearsign \
comment-format \
crlf-glob \
crnl-glob \
default-csp \
default-perms \
diff-binary \
diff-command \
dont-push \
dotfiles \
editor \
email-admin \
email-self \
email-send-command \
email-send-db \
email-send-dir \
email-send-method \
email-send-relayhost \
email-subname \
email-url \
empty-dirs \
encoding-glob \
exec-rel-paths \
fileedit-glob \
forbid-delta-manifests \
gdiff-command \
gmerge-command \
hash-digits \
http-port \
https-login \
ignore-glob \
keep-glob \
localauth \
lock-timeout \
main-branch \
manifest \
max-loadavg \
max-upload \
mimetypes \
mtime-changes \
pgp-command \
proxy \
redirect-to-https \
relative-paths \
repo-cksum \
repolist-skin \
self-register \
ssh-command \
ssl-ca-location \
ssl-identity \
tclsh \
th1-setup \
th1-uri-regexp \
uv-sync \
web-browser]
fossil test-th-eval "hasfeature legacyMvRm"
|
| ︙ | ︙ | |||
345 346 347 348 349 350 351 |
# 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
| > | > > > > > > > | | | 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 |
# 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
if {$x == $y} {
return 1
} else {
if {$::VERBOSE} {
protOut "NOT_SAME_FILE($a): \{\n$x\n\}"
protOut "NOT_SAME_FILE($b): \{\n$y\n\}"
}
return 0
}
}
# Return true if two strings refer to the
# same uuid. That is, the shorter is a prefix
# of the longer.
#
proc same_uuid {a b} {
set na [string length $a]
set nb [string length $b]
if {$na == $nb} {
return [expr {$a eq $b}]
}
if {$na < $nb} {
return [string match "$a*" $b]
}
return [string match "$b*" $a]
}
# Return a prefix of a uuid, defaulting to 10 chars.
#
proc short_uuid {uuid {len 10}} {
string range $uuid 0 $len-1
}
proc require_no_open_checkout {} {
if {[info exists ::env(FOSSIL_TEST_DANGEROUS_IGNORE_OPEN_CHECKOUT)] && \
$::env(FOSSIL_TEST_DANGEROUS_IGNORE_OPEN_CHECKOUT) eq "YES_DO_IT"} {
return
}
catch {exec $::fossilexe info} res
if {[regexp {local-root:} $res]} {
set projectName <unknown>
set localRoot <unknown>
regexp -line -- {^project-name: (.*)$} $res dummy projectName
set projectName [string trim $projectName]
regexp -line -- {^local-root: (.*)$} $res dummy localRoot
set localRoot [string trim $localRoot]
error "Detected an open checkout of project \"$projectName\",\
|
| ︙ | ︙ | |||
938 939 940 941 942 943 944 |
proc test_fossil_http { repository dataFileName url } {
set suffix [appendArgs [pid] - [getSeqNo] - [clock seconds] .txt]
set inFileName [file join $::tempPath [appendArgs test-http-in- $suffix]]
set outFileName [file join $::tempPath [appendArgs test-http-out- $suffix]]
set data [subst [read_file $dataFileName]]
write_file $inFileName $data
| > > | > | 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 |
proc test_fossil_http { repository dataFileName url } {
set suffix [appendArgs [pid] - [getSeqNo] - [clock seconds] .txt]
set inFileName [file join $::tempPath [appendArgs test-http-in- $suffix]]
set outFileName [file join $::tempPath [appendArgs test-http-out- $suffix]]
set data [subst [read_file $dataFileName]]
write_file $inFileName $data
fossil http --in $inFileName --out $outFileName --ipaddr 127.0.0.1 \
$repository --localauth --th-trace
set result [expr {[file exists $outFileName] ? [read_file $outFileName] : ""}]
if {1} {
catch {file delete $inFileName}
catch {file delete $outFileName}
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
124 125 126 127 128 129 130 | error "unable to locate repository" } set dataFileName [file join $::testdir th1-hooks-input.txt] ############################################################################### | > | | 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
error "unable to locate repository"
}
set dataFileName [file join $::testdir th1-hooks-input.txt]
###############################################################################
set savedTh1Setup [fossil settings th1-setup]
fossil settings th1-setup $testTh1Setup
###############################################################################
fossil timeline custom -expectError; # NOTE: Bad "WHEN" argument.
test th1-cmd-hooks-1a {[normalize_result] eq \
{<h1><b>command_hook timeline CUSTOM TIMELINE</b></h1>
unknown check-in or invalid date: custom}}
|
| ︙ | ︙ | |||
225 226 227 228 229 230 231 |
test th1-custom-web-1a {[next_to_last_data_line] eq $repository}
test th1-custom-web-1b {[last_data_line] eq \
{<h1><b>command_hook http webpage_hook test1 webpage_notify test1</b></h1>}}
###############################################################################
| | | 226 227 228 229 230 231 232 233 234 235 236 237 |
test th1-custom-web-1a {[next_to_last_data_line] eq $repository}
test th1-custom-web-1b {[last_data_line] eq \
{<h1><b>command_hook http webpage_hook test1 webpage_notify test1</b></h1>}}
###############################################################################
fossil settings th1-setup $savedTh1Setup
###############################################################################
test_cleanup
|
| ︙ | ︙ | |||
561 562 563 564 565 566 567 |
puts "Skipping th1-anycap-*-1 perm tests: not in Fossil repo checkout."
} elseif ($::dirty_ckout) {
puts "Skipping th1-anycap-*-1 perm tests: uncommitted changes in Fossil checkout."
} else {
set skip_anycap 0
}
| > > | > > > > < > > | 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 |
puts "Skipping th1-anycap-*-1 perm tests: not in Fossil repo checkout."
} elseif ($::dirty_ckout) {
puts "Skipping th1-anycap-*-1 perm tests: uncommitted changes in Fossil checkout."
} else {
set skip_anycap 0
}
# NOTE: The 'd' permission is no longer used.
foreach perm [list \
a b c e f g h i j k l m n o p q r s t u v w x y z \
A D \
2 3 4 5 6 7 ] {
if {$perm eq "u"} continue; # NOTE: Skip "reader" meta-permission.
if {$perm eq "v"} continue; # NOTE: Skip "developer" meta-permission.
set ::env(TH1_TEST_USER_CAPS) sxy
fossil test-th-eval "anycap $perm"
test th1-anycap-no-$perm-1 {$RESULT eq {0}}
fossil test-th-eval "hascap $perm"
test th1-hascap-no-$perm-1 {$RESULT eq {0}}
fossil test-th-eval "anoncap $perm"
test th1-anoncap-no-$perm-1 {$RESULT eq {0}}
if {$skip_anycap} { continue }
run_in_checkout {
set ::env(TH1_TEST_USER_CAPS) sxy
fossil test-th-eval --set-user-caps "anycap $perm"
test th1-anycap-yes-$perm-1 {$RESULT eq {1}}
set ::env(TH1_TEST_USER_CAPS) 1; # NOTE: Bad permission.
fossil test-th-eval --set-user-caps "anycap $perm"
test th1-anycap-no-$perm-1 {$RESULT eq {0}}
set ::env(TH1_TEST_USER_CAPS) sxy
fossil test-th-eval --set-user-caps "hascap $perm"
test th1-hascap-yes-$perm-1 {$RESULT eq {1}}
set ::env(TH1_TEST_USER_CAPS) 1; # NOTE: Bad permission.
fossil test-th-eval --set-user-caps "hascap $perm"
test th1-hascap-no-$perm-1 {$RESULT eq {0}}
unset ::env(TH1_TEST_USER_CAPS)
set ::env(TH1_TEST_ANON_CAPS) sxy
fossil test-th-eval --set-anon-caps "anoncap $perm"
test th1-anoncap-yes-$perm-1 {$RESULT eq {1}}
set ::env(TH1_TEST_ANON_CAPS) 1; # NOTE: Bad permission.
fossil test-th-eval --set-anon-caps "anoncap $perm"
test th1-anoncap-no-$perm-1 {$RESULT eq {0}}
unset ::env(TH1_TEST_ANON_CAPS)
|
| ︙ | ︙ | |||
784 785 786 787 788 789 790 |
fossil test-th-eval --open-config "styleFooter"
test th1-footer-2 {$RESULT eq {}}
###############################################################################
fossil test-th-eval --open-config --cgi "styleHeader {}; styleFooter"
| | | 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 |
fossil test-th-eval --open-config "styleFooter"
test th1-footer-2 {$RESULT eq {}}
###############################################################################
fossil test-th-eval --open-config --cgi "styleHeader {}; styleFooter"
test th1-footer-3 {[regexp -- {</body>\n</html>} $RESULT]}
###############################################################################
fossil test-th-eval "getParameter"
test th1-get-parameter-1 {$RESULT eq \
{TH_ERROR: wrong # args: should be "getParameter NAME ?DEFAULT?"}}
|
| ︙ | ︙ | |||
1027 1028 1029 1030 1031 1032 1033 |
# moved from Tcl builds to plain or the reverse. Sorting the
# command lists eliminates a dependence on order.
#
fossil test-th-eval "info commands"
set sorted_result [lsort $RESULT]
protOut "Sorted: $sorted_result"
set base_commands {anoncap anycap array artifact break breakpoint catch\
| | | | | | > | | | 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 |
# moved from Tcl builds to plain or the reverse. Sorting the
# command lists eliminates a dependence on order.
#
fossil test-th-eval "info commands"
set sorted_result [lsort $RESULT]
protOut "Sorted: $sorted_result"
set base_commands {anoncap anycap array artifact break breakpoint catch\
cgiHeaderLine checkout combobox continue copybtn date decorate dir \
enable_output encode64 error expr for getParameter glob_match \
globalState hascap hasfeature html htmlize http httpize if info \
insertCsrf lindex linecount list llength lsearch markdown nonce proc \
puts query randhex redirect regexp reinitialize rename render \
repository return searchable set setParameter setting stime string \
styleFooter styleHeader styleScript tclReady trace unset unversioned \
uplevel upvar utime verifyCsrf verifyLogin wiki}
set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
if {$th1Tcl} {
test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
} else {
test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
}
|
| ︙ | ︙ |
| ︙ | ︙ | |||
16 17 18 19 20 21 22 | ############################################################################ # # The "unversioned" command. # set path [file dirname [info script]] | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
############################################################################
#
# The "unversioned" command.
#
set path [file dirname [info script]]
if {[catch {package require sha1}] != 0} {
puts "The \"sha1\" package is not available."
test_cleanup_then_return
}
require_no_open_checkout
test_setup; set rootDir [file normalize [pwd]]
|
| ︙ | ︙ | |||
117 118 119 120 121 122 123 |
fossil unversioned ls --all
test unversioned-13 {[normalize_result] eq {unversioned1.txt}}
###############################################################################
fossil unversioned add "unversioned space.txt" -expectError
test unversioned-14 {[normalize_result] eq \
| | | 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
fossil unversioned ls --all
test unversioned-13 {[normalize_result] eq {unversioned1.txt}}
###############################################################################
fossil unversioned add "unversioned space.txt" -expectError
test unversioned-14 {[normalize_result] eq \
{unversioned filenames may not contain whitespace: 'unversioned space.txt'}}
###############################################################################
fossil unversioned add "unversioned space.txt" --as unversioned3.txt
test unversioned-15 {[normalize_result] eq {}}
###############################################################################
|
| ︙ | ︙ | |||
341 342 343 344 345 346 347 |
fossil_maybe_answer y unversioned sync $remote
test unversioned-46 {[regexp \
{Round-trips: 1 Artifacts sent: 0 received: 0
Round-trips: 1 Artifacts sent: 0 received: 0
Round-trips: 2 Artifacts sent: 0 received: 0
Round-trips: 2 Artifacts sent: 0 received: 2
| | > | 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
fossil_maybe_answer y unversioned sync $remote
test unversioned-46 {[regexp \
{Round-trips: 1 Artifacts sent: 0 received: 0
Round-trips: 1 Artifacts sent: 0 received: 0
Round-trips: 2 Artifacts sent: 0 received: 0
Round-trips: 2 Artifacts sent: 0 received: 2
\n? done, sent: \d+ received: \d+ ip: (?:127\.0\.0\.1|::1)} \
[normalize_result]]}
###############################################################################
fossil unversioned ls
test unversioned-47 {[normalize_result] eq {unversioned2.txt
unversioned5.txt}}
|
| ︙ | ︙ | |||
386 387 388 389 390 391 392 |
fossil_maybe_answer y unversioned revert $remote
test unversioned-52 {[regexp \
{Round-trips: 1 Artifacts sent: 0 received: 0
Round-trips: 1 Artifacts sent: 0 received: 0
Round-trips: 2 Artifacts sent: 0 received: 0
Round-trips: 2 Artifacts sent: 0 received: 2
| | > | 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 |
fossil_maybe_answer y unversioned revert $remote
test unversioned-52 {[regexp \
{Round-trips: 1 Artifacts sent: 0 received: 0
Round-trips: 1 Artifacts sent: 0 received: 0
Round-trips: 2 Artifacts sent: 0 received: 0
Round-trips: 2 Artifacts sent: 0 received: 2
\n? done, sent: \d+ received: \d+ ip: (?:127\.0\.0\.1|::1)} \
[normalize_result]]}
###############################################################################
fossil unversioned list
test unversioned-53 {[regexp \
{^[0-9a-f]{12} 2016-10-01 00:00:00 30 30\
unversioned2\.txt
|
| ︙ | ︙ | |||
410 411 412 413 414 415 416 |
fossil_maybe_answer y unversioned sync $remote
test unversioned-55 {[regexp \
{Round-trips: 1 Artifacts sent: 0 received: 0
Round-trips: 1 Artifacts sent: 0 received: 0
Round-trips: 2 Artifacts sent: 1 received: 0
Round-trips: 2 Artifacts sent: 1 received: 0
| | > | 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 |
fossil_maybe_answer y unversioned sync $remote
test unversioned-55 {[regexp \
{Round-trips: 1 Artifacts sent: 0 received: 0
Round-trips: 1 Artifacts sent: 0 received: 0
Round-trips: 2 Artifacts sent: 1 received: 0
Round-trips: 2 Artifacts sent: 1 received: 0
\n? done, sent: \d+ received: \d+ ip: (?:127\.0\.0\.1|::1)} \
[normalize_result]]}
###############################################################################
fossil close
test unversioned-56 {[normalize_result] eq {}}
###############################################################################
|
| ︙ | ︙ |
| ︙ | ︙ | |||
139 140 141 142 143 144 145 |
73 [appendArgs \x00 AB\x00\n] \
74 [appendArgs \x00 ABC\x00\n] \
75 [appendArgs \x00 ABCD\x00\n] \
76 [appendArgs \x00 A\x00\r\n] \
77 [appendArgs \x00 AB\x00\r\n] \
78 [appendArgs \x00 ABC\x00\r\n] \
79 [appendArgs \x00 ABCD\x00\r\n] \
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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 |
73 [appendArgs \x00 AB\x00\n] \
74 [appendArgs \x00 ABC\x00\n] \
75 [appendArgs \x00 ABCD\x00\n] \
76 [appendArgs \x00 A\x00\r\n] \
77 [appendArgs \x00 AB\x00\r\n] \
78 [appendArgs \x00 ABC\x00\r\n] \
79 [appendArgs \x00 ABCD\x00\r\n] \
80 [string repeat A 32769] \
81 [string repeat A 32769]\r \
82 [string repeat A 32769]\n \
83 [string repeat A 32769]\r\n \
84 [string repeat ABCD 8196] \
85 [string repeat ABCD 8196]\r \
86 [string repeat ABCD 8196]\n \
87 [string repeat ABCD 8196]\r\n \
88 \x00[string repeat A 32769] \
89 \x00[string repeat A 32769]\r \
90 \x00[string repeat A 32769]\n \
91 \x00[string repeat A 32769]\r\n \
92 \x00[string repeat ABCD 8196] \
93 \x00[string repeat ABCD 8196]\r \
94 \x00[string repeat ABCD 8196]\n \
95 \x00[string repeat ABCD 8196]\r\n \
96 [string repeat A 32769]\x00 \
97 [string repeat A 32769]\x00\r \
98 [string repeat A 32769]\x00\n \
99 [string repeat A 32769]\x00\r\n \
100 [string repeat ABCD 8196]\x00 \
101 [string repeat ABCD 8196]\x00\r \
102 [string repeat ABCD 8196]\x00\n \
103 [string repeat ABCD 8196]\x00\r\n \
104 \x00[string repeat A 32769]\x00 \
105 \x00[string repeat A 32769]\x00\r \
106 \x00[string repeat A 32769]\x00\n \
107 \x00[string repeat A 32769]\x00\r\n \
108 \x00[string repeat ABCD 8196]\x00 \
109 \x00[string repeat ABCD 8196]\x00\r \
110 \x00[string repeat ABCD 8196]\x00\n \
111 \x00[string repeat ABCD 8196]\x00\r\n \
112 \u000A\u000D \
113 \u0A00\u0D00 \
114 \u000D\u000A \
115 \u0D00\u0A00 \
116 \x00\u000A\u000D \
117 \x00\u0A00\u0D00 \
118 \x00\u000D\u000A \
|
| ︙ | ︙ | |||
2904 2905 2906 2907 2908 2909 2910 | Has flag LOOK_CRLF: yes Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 260 utf-check-260-0-80-0.jnk \ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 |
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: no
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 260 utf-check-260-0-80-0.jnk \
{File "%TEMP%/utf-check-260-0-80-0.jnk" has 32769 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 261 utf-check-261-0-80-1.jnk \
{File "%TEMP%/utf-check-261-0-80-1.jnk" has 32770 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 262 utf-check-262-0-81-0.jnk \
{File "%TEMP%/utf-check-262-0-81-0.jnk" has 32770 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 263 utf-check-263-0-81-1.jnk \
{File "%TEMP%/utf-check-263-0-81-1.jnk" has 32771 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 264 utf-check-264-0-82-0.jnk \
{File "%TEMP%/utf-check-264-0-82-0.jnk" has 32770 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 265 utf-check-265-0-82-1.jnk \
{File "%TEMP%/utf-check-265-0-82-1.jnk" has 32771 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 266 utf-check-266-0-83-0.jnk \
{File "%TEMP%/utf-check-266-0-83-0.jnk" has 32771 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 267 utf-check-267-0-83-1.jnk \
{File "%TEMP%/utf-check-267-0-83-1.jnk" has 32772 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 268 utf-check-268-0-84-0.jnk \
{File "%TEMP%/utf-check-268-0-84-0.jnk" has 32784 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 269 utf-check-269-0-84-1.jnk \
{File "%TEMP%/utf-check-269-0-84-1.jnk" has 32785 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 270 utf-check-270-0-85-0.jnk \
{File "%TEMP%/utf-check-270-0-85-0.jnk" has 32785 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 271 utf-check-271-0-85-1.jnk \
{File "%TEMP%/utf-check-271-0-85-1.jnk" has 32786 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 272 utf-check-272-0-86-0.jnk \
{File "%TEMP%/utf-check-272-0-86-0.jnk" has 32785 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 273 utf-check-273-0-86-1.jnk \
{File "%TEMP%/utf-check-273-0-86-1.jnk" has 32786 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 274 utf-check-274-0-87-0.jnk \
{File "%TEMP%/utf-check-274-0-87-0.jnk" has 32786 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 275 utf-check-275-0-87-1.jnk \
{File "%TEMP%/utf-check-275-0-87-1.jnk" has 32787 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 276 utf-check-276-0-88-0.jnk \
{File "%TEMP%/utf-check-276-0-88-0.jnk" has 32770 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 277 utf-check-277-0-88-1.jnk \
{File "%TEMP%/utf-check-277-0-88-1.jnk" has 32771 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 278 utf-check-278-0-89-0.jnk \
{File "%TEMP%/utf-check-278-0-89-0.jnk" has 32771 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 279 utf-check-279-0-89-1.jnk \
{File "%TEMP%/utf-check-279-0-89-1.jnk" has 32772 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 280 utf-check-280-0-90-0.jnk \
{File "%TEMP%/utf-check-280-0-90-0.jnk" has 32771 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 281 utf-check-281-0-90-1.jnk \
{File "%TEMP%/utf-check-281-0-90-1.jnk" has 32772 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 282 utf-check-282-0-91-0.jnk \
{File "%TEMP%/utf-check-282-0-91-0.jnk" has 32772 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 283 utf-check-283-0-91-1.jnk \
{File "%TEMP%/utf-check-283-0-91-1.jnk" has 32773 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 284 utf-check-284-0-92-0.jnk \
{File "%TEMP%/utf-check-284-0-92-0.jnk" has 32785 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 285 utf-check-285-0-92-1.jnk \
{File "%TEMP%/utf-check-285-0-92-1.jnk" has 32786 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 286 utf-check-286-0-93-0.jnk \
{File "%TEMP%/utf-check-286-0-93-0.jnk" has 32786 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 287 utf-check-287-0-93-1.jnk \
{File "%TEMP%/utf-check-287-0-93-1.jnk" has 32787 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 288 utf-check-288-0-94-0.jnk \
{File "%TEMP%/utf-check-288-0-94-0.jnk" has 32786 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 289 utf-check-289-0-94-1.jnk \
{File "%TEMP%/utf-check-289-0-94-1.jnk" has 32787 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 290 utf-check-290-0-95-0.jnk \
{File "%TEMP%/utf-check-290-0-95-0.jnk" has 32787 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 291 utf-check-291-0-95-1.jnk \
{File "%TEMP%/utf-check-291-0-95-1.jnk" has 32788 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 292 utf-check-292-0-96-0.jnk \
{File "%TEMP%/utf-check-292-0-96-0.jnk" has 32770 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 293 utf-check-293-0-96-1.jnk \
{File "%TEMP%/utf-check-293-0-96-1.jnk" has 32771 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 294 utf-check-294-0-97-0.jnk \
{File "%TEMP%/utf-check-294-0-97-0.jnk" has 32771 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 295 utf-check-295-0-97-1.jnk \
{File "%TEMP%/utf-check-295-0-97-1.jnk" has 32772 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 296 utf-check-296-0-98-0.jnk \
{File "%TEMP%/utf-check-296-0-98-0.jnk" has 32771 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 297 utf-check-297-0-98-1.jnk \
{File "%TEMP%/utf-check-297-0-98-1.jnk" has 32772 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 298 utf-check-298-0-99-0.jnk \
{File "%TEMP%/utf-check-298-0-99-0.jnk" has 32772 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 299 utf-check-299-0-99-1.jnk \
{File "%TEMP%/utf-check-299-0-99-1.jnk" has 32773 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 300 utf-check-300-0-100-0.jnk \
{File "%TEMP%/utf-check-300-0-100-0.jnk" has 32785 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 301 utf-check-301-0-100-1.jnk \
{File "%TEMP%/utf-check-301-0-100-1.jnk" has 32786 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 302 utf-check-302-0-101-0.jnk \
{File "%TEMP%/utf-check-302-0-101-0.jnk" has 32786 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 303 utf-check-303-0-101-1.jnk \
{File "%TEMP%/utf-check-303-0-101-1.jnk" has 32787 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 304 utf-check-304-0-102-0.jnk \
{File "%TEMP%/utf-check-304-0-102-0.jnk" has 32786 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 305 utf-check-305-0-102-1.jnk \
{File "%TEMP%/utf-check-305-0-102-1.jnk" has 32787 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 306 utf-check-306-0-103-0.jnk \
{File "%TEMP%/utf-check-306-0-103-0.jnk" has 32787 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 307 utf-check-307-0-103-1.jnk \
{File "%TEMP%/utf-check-307-0-103-1.jnk" has 32788 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 308 utf-check-308-0-104-0.jnk \
{File "%TEMP%/utf-check-308-0-104-0.jnk" has 32771 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 309 utf-check-309-0-104-1.jnk \
{File "%TEMP%/utf-check-309-0-104-1.jnk" has 32772 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 310 utf-check-310-0-105-0.jnk \
{File "%TEMP%/utf-check-310-0-105-0.jnk" has 32772 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 311 utf-check-311-0-105-1.jnk \
{File "%TEMP%/utf-check-311-0-105-1.jnk" has 32773 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 312 utf-check-312-0-106-0.jnk \
{File "%TEMP%/utf-check-312-0-106-0.jnk" has 32772 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 313 utf-check-313-0-106-1.jnk \
{File "%TEMP%/utf-check-313-0-106-1.jnk" has 32773 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 314 utf-check-314-0-107-0.jnk \
{File "%TEMP%/utf-check-314-0-107-0.jnk" has 32773 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 315 utf-check-315-0-107-1.jnk \
{File "%TEMP%/utf-check-315-0-107-1.jnk" has 32774 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 316 utf-check-316-0-108-0.jnk \
{File "%TEMP%/utf-check-316-0-108-0.jnk" has 32786 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 317 utf-check-317-0-108-1.jnk \
{File "%TEMP%/utf-check-317-0-108-1.jnk" has 32787 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 318 utf-check-318-0-109-0.jnk \
{File "%TEMP%/utf-check-318-0-109-0.jnk" has 32787 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 319 utf-check-319-0-109-1.jnk \
{File "%TEMP%/utf-check-319-0-109-1.jnk" has 32788 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 320 utf-check-320-0-110-0.jnk \
{File "%TEMP%/utf-check-320-0-110-0.jnk" has 32787 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 321 utf-check-321-0-110-1.jnk \
{File "%TEMP%/utf-check-321-0-110-1.jnk" has 32788 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 322 utf-check-322-0-111-0.jnk \
{File "%TEMP%/utf-check-322-0-111-0.jnk" has 32788 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 323 utf-check-323-0-111-1.jnk \
{File "%TEMP%/utf-check-323-0-111-1.jnk" has 32789 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
|
| ︙ | ︙ | |||
9464 9465 9466 9467 9468 9469 9470 | Has flag LOOK_CRLF: yes Has flag LOOK_LONG: no Has flag LOOK_INVALID: no Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 670 utf-check-670-1-80-0.jnk \ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 9464 9465 9466 9467 9468 9469 9470 9471 9472 9473 9474 9475 9476 9477 9478 9479 9480 9481 9482 9483 9484 9485 9486 9487 9488 9489 9490 9491 9492 9493 9494 9495 9496 9497 9498 9499 9500 9501 9502 9503 9504 9505 9506 9507 9508 9509 9510 9511 9512 9513 9514 9515 9516 9517 9518 9519 9520 9521 9522 9523 9524 9525 9526 9527 9528 9529 9530 9531 9532 9533 9534 9535 9536 9537 9538 9539 9540 9541 9542 9543 9544 9545 9546 9547 9548 9549 9550 9551 9552 9553 9554 9555 9556 9557 9558 9559 9560 9561 9562 9563 9564 9565 9566 9567 9568 9569 9570 9571 9572 9573 9574 9575 9576 9577 9578 9579 9580 9581 9582 9583 9584 9585 9586 9587 9588 9589 9590 9591 9592 9593 9594 9595 9596 9597 9598 9599 9600 9601 9602 9603 9604 9605 9606 9607 9608 9609 9610 9611 9612 9613 9614 9615 9616 9617 9618 9619 9620 9621 9622 9623 9624 9625 9626 9627 9628 9629 9630 9631 9632 9633 9634 9635 9636 9637 9638 9639 9640 9641 9642 9643 9644 9645 9646 9647 9648 9649 9650 9651 9652 9653 9654 9655 9656 9657 9658 9659 9660 9661 9662 9663 9664 9665 9666 9667 9668 9669 9670 9671 9672 9673 9674 9675 9676 9677 9678 9679 9680 9681 9682 9683 9684 9685 9686 9687 9688 9689 9690 9691 9692 9693 9694 9695 9696 9697 9698 9699 9700 9701 9702 9703 9704 9705 9706 9707 9708 9709 9710 9711 9712 9713 9714 9715 9716 9717 9718 9719 9720 9721 9722 9723 9724 9725 9726 9727 9728 9729 9730 9731 9732 9733 9734 9735 9736 9737 9738 9739 9740 9741 9742 9743 9744 9745 9746 9747 9748 9749 9750 9751 9752 9753 9754 9755 9756 9757 9758 9759 9760 9761 9762 9763 9764 9765 9766 9767 9768 9769 9770 9771 9772 9773 9774 9775 9776 9777 9778 9779 9780 9781 9782 9783 9784 9785 9786 9787 9788 9789 9790 9791 9792 9793 9794 9795 9796 9797 9798 9799 9800 9801 9802 9803 9804 9805 9806 9807 9808 9809 9810 9811 9812 9813 9814 9815 9816 9817 9818 9819 9820 9821 9822 9823 9824 9825 9826 9827 9828 9829 9830 9831 9832 9833 9834 9835 9836 9837 9838 9839 9840 9841 9842 9843 9844 9845 9846 9847 9848 9849 9850 9851 9852 9853 9854 9855 9856 9857 9858 9859 9860 9861 9862 9863 9864 9865 9866 9867 9868 9869 9870 9871 9872 9873 9874 9875 9876 9877 9878 9879 9880 9881 9882 9883 9884 9885 9886 9887 9888 9889 9890 9891 9892 9893 9894 9895 9896 9897 9898 9899 9900 9901 9902 9903 9904 9905 9906 9907 9908 9909 9910 9911 9912 9913 9914 9915 9916 9917 9918 9919 9920 9921 9922 9923 9924 9925 9926 9927 9928 9929 9930 9931 9932 9933 9934 9935 9936 9937 9938 9939 9940 9941 9942 9943 9944 9945 9946 9947 9948 9949 9950 9951 9952 9953 9954 9955 9956 9957 9958 9959 9960 9961 9962 9963 9964 9965 9966 9967 9968 9969 9970 9971 9972 9973 9974 9975 9976 9977 9978 9979 9980 9981 9982 9983 9984 9985 9986 9987 9988 9989 9990 9991 9992 9993 9994 9995 9996 9997 9998 9999 10000 10001 10002 10003 10004 10005 10006 10007 10008 10009 10010 10011 10012 10013 10014 10015 10016 10017 10018 10019 10020 10021 10022 10023 10024 10025 10026 10027 10028 10029 10030 10031 10032 10033 10034 10035 10036 10037 10038 10039 10040 10041 10042 10043 10044 10045 10046 10047 10048 10049 10050 10051 10052 10053 10054 10055 10056 10057 10058 10059 10060 10061 10062 10063 10064 10065 10066 10067 10068 10069 10070 10071 10072 10073 10074 10075 10076 10077 10078 10079 10080 10081 10082 10083 10084 10085 10086 10087 10088 10089 10090 10091 10092 10093 10094 10095 10096 10097 10098 10099 10100 10101 10102 10103 10104 10105 10106 10107 10108 10109 10110 10111 10112 10113 10114 10115 10116 10117 10118 10119 10120 10121 10122 10123 10124 10125 10126 10127 10128 10129 10130 10131 10132 10133 10134 10135 10136 10137 10138 10139 10140 10141 10142 10143 10144 10145 10146 10147 10148 10149 10150 10151 10152 10153 10154 10155 10156 10157 10158 10159 10160 10161 10162 10163 10164 10165 10166 10167 10168 10169 10170 10171 10172 10173 10174 10175 10176 10177 10178 10179 10180 10181 10182 10183 10184 10185 10186 10187 10188 10189 10190 10191 10192 10193 10194 10195 10196 10197 10198 10199 10200 10201 10202 10203 10204 10205 10206 10207 10208 10209 10210 10211 10212 10213 10214 10215 10216 10217 10218 10219 10220 10221 10222 10223 10224 10225 10226 10227 10228 10229 10230 10231 10232 10233 10234 10235 10236 10237 10238 10239 10240 10241 10242 10243 10244 10245 10246 10247 10248 10249 10250 10251 10252 10253 10254 10255 10256 10257 10258 10259 10260 10261 10262 10263 10264 10265 10266 10267 10268 10269 10270 10271 10272 10273 10274 10275 10276 10277 10278 10279 10280 10281 10282 10283 10284 10285 10286 10287 10288 10289 10290 10291 10292 10293 10294 10295 10296 10297 10298 10299 10300 10301 10302 10303 10304 10305 10306 10307 10308 10309 10310 10311 10312 10313 10314 10315 10316 10317 10318 10319 10320 10321 10322 10323 10324 10325 10326 10327 10328 10329 10330 10331 10332 10333 10334 10335 10336 10337 10338 10339 10340 10341 10342 10343 10344 10345 10346 10347 10348 10349 10350 10351 10352 10353 10354 10355 10356 10357 10358 10359 10360 10361 10362 10363 10364 10365 10366 10367 10368 10369 10370 10371 10372 10373 10374 10375 10376 10377 10378 10379 10380 10381 10382 10383 10384 10385 10386 10387 10388 10389 10390 10391 10392 10393 10394 10395 10396 10397 10398 10399 10400 10401 10402 10403 10404 10405 10406 10407 10408 10409 10410 10411 10412 10413 10414 10415 10416 10417 10418 10419 10420 10421 10422 10423 10424 10425 10426 10427 10428 10429 10430 10431 10432 10433 10434 10435 10436 10437 10438 10439 10440 10441 10442 10443 10444 10445 10446 10447 10448 10449 10450 10451 10452 10453 10454 10455 10456 10457 10458 10459 10460 10461 10462 10463 10464 10465 10466 10467 10468 10469 10470 10471 10472 10473 10474 10475 10476 10477 10478 10479 10480 10481 10482 10483 10484 10485 10486 |
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: no
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 670 utf-check-670-1-80-0.jnk \
{File "%TEMP%/utf-check-670-1-80-0.jnk" has 32772 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 671 utf-check-671-1-80-1.jnk \
{File "%TEMP%/utf-check-671-1-80-1.jnk" has 32773 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 672 utf-check-672-1-81-0.jnk \
{File "%TEMP%/utf-check-672-1-81-0.jnk" has 32773 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 673 utf-check-673-1-81-1.jnk \
{File "%TEMP%/utf-check-673-1-81-1.jnk" has 32774 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 674 utf-check-674-1-82-0.jnk \
{File "%TEMP%/utf-check-674-1-82-0.jnk" has 32773 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 675 utf-check-675-1-82-1.jnk \
{File "%TEMP%/utf-check-675-1-82-1.jnk" has 32774 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 676 utf-check-676-1-83-0.jnk \
{File "%TEMP%/utf-check-676-1-83-0.jnk" has 32774 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 677 utf-check-677-1-83-1.jnk \
{File "%TEMP%/utf-check-677-1-83-1.jnk" has 32775 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 678 utf-check-678-1-84-0.jnk \
{File "%TEMP%/utf-check-678-1-84-0.jnk" has 32787 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 679 utf-check-679-1-84-1.jnk \
{File "%TEMP%/utf-check-679-1-84-1.jnk" has 32788 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 680 utf-check-680-1-85-0.jnk \
{File "%TEMP%/utf-check-680-1-85-0.jnk" has 32788 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 681 utf-check-681-1-85-1.jnk \
{File "%TEMP%/utf-check-681-1-85-1.jnk" has 32789 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 682 utf-check-682-1-86-0.jnk \
{File "%TEMP%/utf-check-682-1-86-0.jnk" has 32788 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 683 utf-check-683-1-86-1.jnk \
{File "%TEMP%/utf-check-683-1-86-1.jnk" has 32789 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 684 utf-check-684-1-87-0.jnk \
{File "%TEMP%/utf-check-684-1-87-0.jnk" has 32789 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 685 utf-check-685-1-87-1.jnk \
{File "%TEMP%/utf-check-685-1-87-1.jnk" has 32790 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 686 utf-check-686-1-88-0.jnk \
{File "%TEMP%/utf-check-686-1-88-0.jnk" has 32773 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 687 utf-check-687-1-88-1.jnk \
{File "%TEMP%/utf-check-687-1-88-1.jnk" has 32774 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 688 utf-check-688-1-89-0.jnk \
{File "%TEMP%/utf-check-688-1-89-0.jnk" has 32774 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 689 utf-check-689-1-89-1.jnk \
{File "%TEMP%/utf-check-689-1-89-1.jnk" has 32775 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 690 utf-check-690-1-90-0.jnk \
{File "%TEMP%/utf-check-690-1-90-0.jnk" has 32774 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 691 utf-check-691-1-90-1.jnk \
{File "%TEMP%/utf-check-691-1-90-1.jnk" has 32775 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 692 utf-check-692-1-91-0.jnk \
{File "%TEMP%/utf-check-692-1-91-0.jnk" has 32775 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 693 utf-check-693-1-91-1.jnk \
{File "%TEMP%/utf-check-693-1-91-1.jnk" has 32776 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 694 utf-check-694-1-92-0.jnk \
{File "%TEMP%/utf-check-694-1-92-0.jnk" has 32788 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 695 utf-check-695-1-92-1.jnk \
{File "%TEMP%/utf-check-695-1-92-1.jnk" has 32789 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 696 utf-check-696-1-93-0.jnk \
{File "%TEMP%/utf-check-696-1-93-0.jnk" has 32789 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 697 utf-check-697-1-93-1.jnk \
{File "%TEMP%/utf-check-697-1-93-1.jnk" has 32790 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 698 utf-check-698-1-94-0.jnk \
{File "%TEMP%/utf-check-698-1-94-0.jnk" has 32789 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 699 utf-check-699-1-94-1.jnk \
{File "%TEMP%/utf-check-699-1-94-1.jnk" has 32790 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 700 utf-check-700-1-95-0.jnk \
{File "%TEMP%/utf-check-700-1-95-0.jnk" has 32790 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 701 utf-check-701-1-95-1.jnk \
{File "%TEMP%/utf-check-701-1-95-1.jnk" has 32791 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 702 utf-check-702-1-96-0.jnk \
{File "%TEMP%/utf-check-702-1-96-0.jnk" has 32773 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 703 utf-check-703-1-96-1.jnk \
{File "%TEMP%/utf-check-703-1-96-1.jnk" has 32774 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 704 utf-check-704-1-97-0.jnk \
{File "%TEMP%/utf-check-704-1-97-0.jnk" has 32774 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 705 utf-check-705-1-97-1.jnk \
{File "%TEMP%/utf-check-705-1-97-1.jnk" has 32775 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 706 utf-check-706-1-98-0.jnk \
{File "%TEMP%/utf-check-706-1-98-0.jnk" has 32774 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 707 utf-check-707-1-98-1.jnk \
{File "%TEMP%/utf-check-707-1-98-1.jnk" has 32775 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 708 utf-check-708-1-99-0.jnk \
{File "%TEMP%/utf-check-708-1-99-0.jnk" has 32775 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 709 utf-check-709-1-99-1.jnk \
{File "%TEMP%/utf-check-709-1-99-1.jnk" has 32776 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 710 utf-check-710-1-100-0.jnk \
{File "%TEMP%/utf-check-710-1-100-0.jnk" has 32788 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 711 utf-check-711-1-100-1.jnk \
{File "%TEMP%/utf-check-711-1-100-1.jnk" has 32789 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 712 utf-check-712-1-101-0.jnk \
{File "%TEMP%/utf-check-712-1-101-0.jnk" has 32789 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 713 utf-check-713-1-101-1.jnk \
{File "%TEMP%/utf-check-713-1-101-1.jnk" has 32790 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 714 utf-check-714-1-102-0.jnk \
{File "%TEMP%/utf-check-714-1-102-0.jnk" has 32789 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 715 utf-check-715-1-102-1.jnk \
{File "%TEMP%/utf-check-715-1-102-1.jnk" has 32790 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 716 utf-check-716-1-103-0.jnk \
{File "%TEMP%/utf-check-716-1-103-0.jnk" has 32790 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 717 utf-check-717-1-103-1.jnk \
{File "%TEMP%/utf-check-717-1-103-1.jnk" has 32791 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 718 utf-check-718-1-104-0.jnk \
{File "%TEMP%/utf-check-718-1-104-0.jnk" has 32774 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 719 utf-check-719-1-104-1.jnk \
{File "%TEMP%/utf-check-719-1-104-1.jnk" has 32775 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 720 utf-check-720-1-105-0.jnk \
{File "%TEMP%/utf-check-720-1-105-0.jnk" has 32775 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 721 utf-check-721-1-105-1.jnk \
{File "%TEMP%/utf-check-721-1-105-1.jnk" has 32776 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 722 utf-check-722-1-106-0.jnk \
{File "%TEMP%/utf-check-722-1-106-0.jnk" has 32775 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 723 utf-check-723-1-106-1.jnk \
{File "%TEMP%/utf-check-723-1-106-1.jnk" has 32776 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 724 utf-check-724-1-107-0.jnk \
{File "%TEMP%/utf-check-724-1-107-0.jnk" has 32776 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 725 utf-check-725-1-107-1.jnk \
{File "%TEMP%/utf-check-725-1-107-1.jnk" has 32777 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 726 utf-check-726-1-108-0.jnk \
{File "%TEMP%/utf-check-726-1-108-0.jnk" has 32789 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 727 utf-check-727-1-108-1.jnk \
{File "%TEMP%/utf-check-727-1-108-1.jnk" has 32790 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 728 utf-check-728-1-109-0.jnk \
{File "%TEMP%/utf-check-728-1-109-0.jnk" has 32790 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 729 utf-check-729-1-109-1.jnk \
{File "%TEMP%/utf-check-729-1-109-1.jnk" has 32791 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 730 utf-check-730-1-110-0.jnk \
{File "%TEMP%/utf-check-730-1-110-0.jnk" has 32790 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 731 utf-check-731-1-110-1.jnk \
{File "%TEMP%/utf-check-731-1-110-1.jnk" has 32791 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 732 utf-check-732-1-111-0.jnk \
{File "%TEMP%/utf-check-732-1-111-0.jnk" has 32791 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 733 utf-check-733-1-111-1.jnk \
{File "%TEMP%/utf-check-733-1-111-1.jnk" has 32792 bytes.
Starts with UTF-8 BOM: yes
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
|
| ︙ | ︙ | |||
16024 16025 16026 16027 16028 16029 16030 | Has flag LOOK_CRLF: no Has flag LOOK_LONG: no Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 1080 utf-check-1080-2-80-0.jnk \ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 16024 16025 16026 16027 16028 16029 16030 16031 16032 16033 16034 16035 16036 16037 16038 16039 16040 16041 16042 16043 16044 16045 16046 16047 16048 16049 16050 16051 16052 16053 16054 16055 16056 16057 16058 16059 16060 16061 16062 16063 16064 16065 16066 16067 16068 16069 16070 16071 16072 16073 16074 16075 16076 16077 16078 16079 16080 16081 16082 16083 16084 16085 16086 16087 16088 16089 16090 16091 16092 16093 16094 16095 16096 16097 16098 16099 16100 16101 16102 16103 16104 16105 16106 16107 16108 16109 16110 16111 16112 16113 16114 16115 16116 16117 16118 16119 16120 16121 16122 16123 16124 16125 16126 16127 16128 16129 16130 16131 16132 16133 16134 16135 16136 16137 16138 16139 16140 16141 16142 16143 16144 16145 16146 16147 16148 16149 16150 16151 16152 16153 16154 16155 16156 16157 16158 16159 16160 16161 16162 16163 16164 16165 16166 16167 16168 16169 16170 16171 16172 16173 16174 16175 16176 16177 16178 16179 16180 16181 16182 16183 16184 16185 16186 16187 16188 16189 16190 16191 16192 16193 16194 16195 16196 16197 16198 16199 16200 16201 16202 16203 16204 16205 16206 16207 16208 16209 16210 16211 16212 16213 16214 16215 16216 16217 16218 16219 16220 16221 16222 16223 16224 16225 16226 16227 16228 16229 16230 16231 16232 16233 16234 16235 16236 16237 16238 16239 16240 16241 16242 16243 16244 16245 16246 16247 16248 16249 16250 16251 16252 16253 16254 16255 16256 16257 16258 16259 16260 16261 16262 16263 16264 16265 16266 16267 16268 16269 16270 16271 16272 16273 16274 16275 16276 16277 16278 16279 16280 16281 16282 16283 16284 16285 16286 16287 16288 16289 16290 16291 16292 16293 16294 16295 16296 16297 16298 16299 16300 16301 16302 16303 16304 16305 16306 16307 16308 16309 16310 16311 16312 16313 16314 16315 16316 16317 16318 16319 16320 16321 16322 16323 16324 16325 16326 16327 16328 16329 16330 16331 16332 16333 16334 16335 16336 16337 16338 16339 16340 16341 16342 16343 16344 16345 16346 16347 16348 16349 16350 16351 16352 16353 16354 16355 16356 16357 16358 16359 16360 16361 16362 16363 16364 16365 16366 16367 16368 16369 16370 16371 16372 16373 16374 16375 16376 16377 16378 16379 16380 16381 16382 16383 16384 16385 16386 16387 16388 16389 16390 16391 16392 16393 16394 16395 16396 16397 16398 16399 16400 16401 16402 16403 16404 16405 16406 16407 16408 16409 16410 16411 16412 16413 16414 16415 16416 16417 16418 16419 16420 16421 16422 16423 16424 16425 16426 16427 16428 16429 16430 16431 16432 16433 16434 16435 16436 16437 16438 16439 16440 16441 16442 16443 16444 16445 16446 16447 16448 16449 16450 16451 16452 16453 16454 16455 16456 16457 16458 16459 16460 16461 16462 16463 16464 16465 16466 16467 16468 16469 16470 16471 16472 16473 16474 16475 16476 16477 16478 16479 16480 16481 16482 16483 16484 16485 16486 16487 16488 16489 16490 16491 16492 16493 16494 16495 16496 16497 16498 16499 16500 16501 16502 16503 16504 16505 16506 16507 16508 16509 16510 16511 16512 16513 16514 16515 16516 16517 16518 16519 16520 16521 16522 16523 16524 16525 16526 16527 16528 16529 16530 16531 16532 16533 16534 16535 16536 16537 16538 16539 16540 16541 16542 16543 16544 16545 16546 16547 16548 16549 16550 16551 16552 16553 16554 16555 16556 16557 16558 16559 16560 16561 16562 16563 16564 16565 16566 16567 16568 16569 16570 16571 16572 16573 16574 16575 16576 16577 16578 16579 16580 16581 16582 16583 16584 16585 16586 16587 16588 16589 16590 16591 16592 16593 16594 16595 16596 16597 16598 16599 16600 16601 16602 16603 16604 16605 16606 16607 16608 16609 16610 16611 16612 16613 16614 16615 16616 16617 16618 16619 16620 16621 16622 16623 16624 16625 16626 16627 16628 16629 16630 16631 16632 16633 16634 16635 16636 16637 16638 16639 16640 16641 16642 16643 16644 16645 16646 16647 16648 16649 16650 16651 16652 16653 16654 16655 16656 16657 16658 16659 16660 16661 16662 16663 16664 16665 16666 16667 16668 16669 16670 16671 16672 16673 16674 16675 16676 16677 16678 16679 16680 16681 16682 16683 16684 16685 16686 16687 16688 16689 16690 16691 16692 16693 16694 16695 16696 16697 16698 16699 16700 16701 16702 16703 16704 16705 16706 16707 16708 16709 16710 16711 16712 16713 16714 16715 16716 16717 16718 16719 16720 16721 16722 16723 16724 16725 16726 16727 16728 16729 16730 16731 16732 16733 16734 16735 16736 16737 16738 16739 16740 16741 16742 16743 16744 16745 16746 16747 16748 16749 16750 16751 16752 16753 16754 16755 16756 16757 16758 16759 16760 16761 16762 16763 16764 16765 16766 16767 16768 16769 16770 16771 16772 16773 16774 16775 16776 16777 16778 16779 16780 16781 16782 16783 16784 16785 16786 16787 16788 16789 16790 16791 16792 16793 16794 16795 16796 16797 16798 16799 16800 16801 16802 16803 16804 16805 16806 16807 16808 16809 16810 16811 16812 16813 16814 16815 16816 16817 16818 16819 16820 16821 16822 16823 16824 16825 16826 16827 16828 16829 16830 16831 16832 16833 16834 16835 16836 16837 16838 16839 16840 16841 16842 16843 16844 16845 16846 16847 16848 16849 16850 16851 16852 16853 16854 16855 16856 16857 16858 16859 16860 16861 16862 16863 16864 16865 16866 16867 16868 16869 16870 16871 16872 16873 16874 16875 16876 16877 16878 16879 16880 16881 16882 16883 16884 16885 16886 16887 16888 16889 16890 16891 16892 16893 16894 16895 16896 16897 16898 16899 16900 16901 16902 16903 16904 16905 16906 16907 16908 16909 16910 16911 16912 16913 16914 16915 16916 16917 16918 16919 16920 16921 16922 16923 16924 16925 16926 16927 16928 16929 16930 16931 16932 16933 16934 16935 16936 16937 16938 16939 16940 16941 16942 16943 16944 16945 16946 16947 16948 16949 16950 16951 16952 16953 16954 16955 16956 16957 16958 16959 16960 16961 16962 16963 16964 16965 16966 16967 16968 16969 16970 16971 16972 16973 16974 16975 16976 16977 16978 16979 16980 16981 16982 16983 16984 16985 16986 16987 16988 16989 16990 16991 16992 16993 16994 16995 16996 16997 16998 16999 17000 17001 17002 17003 17004 17005 17006 17007 17008 17009 17010 17011 17012 17013 17014 17015 17016 17017 17018 17019 17020 17021 17022 17023 17024 17025 17026 17027 17028 17029 17030 17031 17032 17033 17034 17035 17036 17037 17038 17039 17040 17041 17042 17043 17044 17045 17046 |
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: no
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1080 utf-check-1080-2-80-0.jnk \
{File "%TEMP%/utf-check-1080-2-80-0.jnk" has 65540 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1081 utf-check-1081-2-80-1.jnk \
{File "%TEMP%/utf-check-1081-2-80-1.jnk" has 65541 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1082 utf-check-1082-2-81-0.jnk \
{File "%TEMP%/utf-check-1082-2-81-0.jnk" has 65542 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1083 utf-check-1083-2-81-1.jnk \
{File "%TEMP%/utf-check-1083-2-81-1.jnk" has 65543 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1084 utf-check-1084-2-82-0.jnk \
{File "%TEMP%/utf-check-1084-2-82-0.jnk" has 65542 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1085 utf-check-1085-2-82-1.jnk \
{File "%TEMP%/utf-check-1085-2-82-1.jnk" has 65543 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1086 utf-check-1086-2-83-0.jnk \
{File "%TEMP%/utf-check-1086-2-83-0.jnk" has 65544 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1087 utf-check-1087-2-83-1.jnk \
{File "%TEMP%/utf-check-1087-2-83-1.jnk" has 65545 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1088 utf-check-1088-2-84-0.jnk \
{File "%TEMP%/utf-check-1088-2-84-0.jnk" has 65570 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1089 utf-check-1089-2-84-1.jnk \
{File "%TEMP%/utf-check-1089-2-84-1.jnk" has 65571 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1090 utf-check-1090-2-85-0.jnk \
{File "%TEMP%/utf-check-1090-2-85-0.jnk" has 65572 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1091 utf-check-1091-2-85-1.jnk \
{File "%TEMP%/utf-check-1091-2-85-1.jnk" has 65573 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1092 utf-check-1092-2-86-0.jnk \
{File "%TEMP%/utf-check-1092-2-86-0.jnk" has 65572 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1093 utf-check-1093-2-86-1.jnk \
{File "%TEMP%/utf-check-1093-2-86-1.jnk" has 65573 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1094 utf-check-1094-2-87-0.jnk \
{File "%TEMP%/utf-check-1094-2-87-0.jnk" has 65574 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: no
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1095 utf-check-1095-2-87-1.jnk \
{File "%TEMP%/utf-check-1095-2-87-1.jnk" has 65575 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1096 utf-check-1096-2-88-0.jnk \
{File "%TEMP%/utf-check-1096-2-88-0.jnk" has 65542 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1097 utf-check-1097-2-88-1.jnk \
{File "%TEMP%/utf-check-1097-2-88-1.jnk" has 65543 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1098 utf-check-1098-2-89-0.jnk \
{File "%TEMP%/utf-check-1098-2-89-0.jnk" has 65544 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1099 utf-check-1099-2-89-1.jnk \
{File "%TEMP%/utf-check-1099-2-89-1.jnk" has 65545 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1100 utf-check-1100-2-90-0.jnk \
{File "%TEMP%/utf-check-1100-2-90-0.jnk" has 65544 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1101 utf-check-1101-2-90-1.jnk \
{File "%TEMP%/utf-check-1101-2-90-1.jnk" has 65545 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1102 utf-check-1102-2-91-0.jnk \
{File "%TEMP%/utf-check-1102-2-91-0.jnk" has 65546 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1103 utf-check-1103-2-91-1.jnk \
{File "%TEMP%/utf-check-1103-2-91-1.jnk" has 65547 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1104 utf-check-1104-2-92-0.jnk \
{File "%TEMP%/utf-check-1104-2-92-0.jnk" has 65572 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1105 utf-check-1105-2-92-1.jnk \
{File "%TEMP%/utf-check-1105-2-92-1.jnk" has 65573 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1106 utf-check-1106-2-93-0.jnk \
{File "%TEMP%/utf-check-1106-2-93-0.jnk" has 65574 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1107 utf-check-1107-2-93-1.jnk \
{File "%TEMP%/utf-check-1107-2-93-1.jnk" has 65575 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1108 utf-check-1108-2-94-0.jnk \
{File "%TEMP%/utf-check-1108-2-94-0.jnk" has 65574 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1109 utf-check-1109-2-94-1.jnk \
{File "%TEMP%/utf-check-1109-2-94-1.jnk" has 65575 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1110 utf-check-1110-2-95-0.jnk \
{File "%TEMP%/utf-check-1110-2-95-0.jnk" has 65576 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1111 utf-check-1111-2-95-1.jnk \
{File "%TEMP%/utf-check-1111-2-95-1.jnk" has 65577 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1112 utf-check-1112-2-96-0.jnk \
{File "%TEMP%/utf-check-1112-2-96-0.jnk" has 65542 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1113 utf-check-1113-2-96-1.jnk \
{File "%TEMP%/utf-check-1113-2-96-1.jnk" has 65543 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1114 utf-check-1114-2-97-0.jnk \
{File "%TEMP%/utf-check-1114-2-97-0.jnk" has 65544 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1115 utf-check-1115-2-97-1.jnk \
{File "%TEMP%/utf-check-1115-2-97-1.jnk" has 65545 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1116 utf-check-1116-2-98-0.jnk \
{File "%TEMP%/utf-check-1116-2-98-0.jnk" has 65544 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1117 utf-check-1117-2-98-1.jnk \
{File "%TEMP%/utf-check-1117-2-98-1.jnk" has 65545 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1118 utf-check-1118-2-99-0.jnk \
{File "%TEMP%/utf-check-1118-2-99-0.jnk" has 65546 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1119 utf-check-1119-2-99-1.jnk \
{File "%TEMP%/utf-check-1119-2-99-1.jnk" has 65547 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1120 utf-check-1120-2-100-0.jnk \
{File "%TEMP%/utf-check-1120-2-100-0.jnk" has 65572 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1121 utf-check-1121-2-100-1.jnk \
{File "%TEMP%/utf-check-1121-2-100-1.jnk" has 65573 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1122 utf-check-1122-2-101-0.jnk \
{File "%TEMP%/utf-check-1122-2-101-0.jnk" has 65574 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1123 utf-check-1123-2-101-1.jnk \
{File "%TEMP%/utf-check-1123-2-101-1.jnk" has 65575 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1124 utf-check-1124-2-102-0.jnk \
{File "%TEMP%/utf-check-1124-2-102-0.jnk" has 65574 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1125 utf-check-1125-2-102-1.jnk \
{File "%TEMP%/utf-check-1125-2-102-1.jnk" has 65575 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1126 utf-check-1126-2-103-0.jnk \
{File "%TEMP%/utf-check-1126-2-103-0.jnk" has 65576 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-16: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: yes
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: no
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1127 utf-check-1127-2-103-1.jnk \
{File "%TEMP%/utf-check-1127-2-103-1.jnk" has 65577 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: yes
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1128 utf-check-1128-2-104-0.jnk \
{File "%TEMP%/utf-check-1128-2-104-0.jnk" has 65544 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1129 utf-check-1129-2-104-1.jnk \
{File "%TEMP%/utf-check-1129-2-104-1.jnk" has 65545 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1130 utf-check-1130-2-105-0.jnk \
{File "%TEMP%/utf-check-1130-2-105-0.jnk" has 65546 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1131 utf-check-1131-2-105-1.jnk \
{File "%TEMP%/utf-check-1131-2-105-1.jnk" has 65547 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1132 utf-check-1132-2-106-0.jnk \
{File "%TEMP%/utf-check-1132-2-106-0.jnk" has 65546 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1133 utf-check-1133-2-106-1.jnk \
{File "%TEMP%/utf-check-1133-2-106-1.jnk" has 65547 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1134 utf-check-1134-2-107-0.jnk \
{File "%TEMP%/utf-check-1134-2-107-0.jnk" has 65548 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1135 utf-check-1135-2-107-1.jnk \
{File "%TEMP%/utf-check-1135-2-107-1.jnk" has 65549 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1136 utf-check-1136-2-108-0.jnk \
{File "%TEMP%/utf-check-1136-2-108-0.jnk" has 65574 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1137 utf-check-1137-2-108-1.jnk \
{File "%TEMP%/utf-check-1137-2-108-1.jnk" has 65575 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1138 utf-check-1138-2-109-0.jnk \
{File "%TEMP%/utf-check-1138-2-109-0.jnk" has 65576 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1139 utf-check-1139-2-109-1.jnk \
{File "%TEMP%/utf-check-1139-2-109-1.jnk" has 65577 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1140 utf-check-1140-2-110-0.jnk \
{File "%TEMP%/utf-check-1140-2-110-0.jnk" has 65576 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1141 utf-check-1141-2-110-1.jnk \
{File "%TEMP%/utf-check-1141-2-110-1.jnk" has 65577 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1142 utf-check-1142-2-111-0.jnk \
{File "%TEMP%/utf-check-1142-2-111-0.jnk" has 65578 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: yes
Has flag LOOK_LONE_LF: yes
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1143 utf-check-1143-2-111-1.jnk \
{File "%TEMP%/utf-check-1143-2-111-1.jnk" has 65579 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: yes
Has flag LOOK_LONE_CR: yes
Has flag LOOK_LF: yes
|
| ︙ | ︙ | |||
22584 22585 22586 22587 22588 22589 22590 | Has flag LOOK_CRLF: no Has flag LOOK_LONG: no Has flag LOOK_INVALID: yes Has flag LOOK_ODD: no Has flag LOOK_SHORT: no} utf-check 1490 utf-check-1490-3-80-0.jnk \ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 22584 22585 22586 22587 22588 22589 22590 22591 22592 22593 22594 22595 22596 22597 22598 22599 22600 22601 22602 22603 22604 22605 22606 22607 22608 22609 22610 22611 22612 22613 22614 22615 22616 22617 22618 22619 22620 22621 22622 22623 22624 22625 22626 22627 22628 22629 22630 22631 22632 22633 22634 22635 22636 22637 22638 22639 22640 22641 22642 22643 22644 22645 22646 22647 22648 22649 22650 22651 22652 22653 22654 22655 22656 22657 22658 22659 22660 22661 22662 22663 22664 22665 22666 22667 22668 22669 22670 22671 22672 22673 22674 22675 22676 22677 22678 22679 22680 22681 22682 22683 22684 22685 22686 22687 22688 22689 22690 22691 22692 22693 22694 22695 22696 22697 22698 22699 22700 22701 22702 22703 22704 22705 22706 22707 22708 22709 22710 22711 22712 22713 22714 22715 22716 22717 22718 22719 22720 22721 22722 22723 22724 22725 22726 22727 22728 22729 22730 22731 22732 22733 22734 22735 22736 22737 22738 22739 22740 22741 22742 22743 22744 22745 22746 22747 22748 22749 22750 22751 22752 22753 22754 22755 22756 22757 22758 22759 22760 22761 22762 22763 22764 22765 22766 22767 22768 22769 22770 22771 22772 22773 22774 22775 22776 22777 22778 22779 22780 22781 22782 22783 22784 22785 22786 22787 22788 22789 22790 22791 22792 22793 22794 22795 22796 22797 22798 22799 22800 22801 22802 22803 22804 22805 22806 22807 22808 22809 22810 22811 22812 22813 22814 22815 22816 22817 22818 22819 22820 22821 22822 22823 22824 22825 22826 22827 22828 22829 22830 22831 22832 22833 22834 22835 22836 22837 22838 22839 22840 22841 22842 22843 22844 22845 22846 22847 22848 22849 22850 22851 22852 22853 22854 22855 22856 22857 22858 22859 22860 22861 22862 22863 22864 22865 22866 22867 22868 22869 22870 22871 22872 22873 22874 22875 22876 22877 22878 22879 22880 22881 22882 22883 22884 22885 22886 22887 22888 22889 22890 22891 22892 22893 22894 22895 22896 22897 22898 22899 22900 22901 22902 22903 22904 22905 22906 22907 22908 22909 22910 22911 22912 22913 22914 22915 22916 22917 22918 22919 22920 22921 22922 22923 22924 22925 22926 22927 22928 22929 22930 22931 22932 22933 22934 22935 22936 22937 22938 22939 22940 22941 22942 22943 22944 22945 22946 22947 22948 22949 22950 22951 22952 22953 22954 22955 22956 22957 22958 22959 22960 22961 22962 22963 22964 22965 22966 22967 22968 22969 22970 22971 22972 22973 22974 22975 22976 22977 22978 22979 22980 22981 22982 22983 22984 22985 22986 22987 22988 22989 22990 22991 22992 22993 22994 22995 22996 22997 22998 22999 23000 23001 23002 23003 23004 23005 23006 23007 23008 23009 23010 23011 23012 23013 23014 23015 23016 23017 23018 23019 23020 23021 23022 23023 23024 23025 23026 23027 23028 23029 23030 23031 23032 23033 23034 23035 23036 23037 23038 23039 23040 23041 23042 23043 23044 23045 23046 23047 23048 23049 23050 23051 23052 23053 23054 23055 23056 23057 23058 23059 23060 23061 23062 23063 23064 23065 23066 23067 23068 23069 23070 23071 23072 23073 23074 23075 23076 23077 23078 23079 23080 23081 23082 23083 23084 23085 23086 23087 23088 23089 23090 23091 23092 23093 23094 23095 23096 23097 23098 23099 23100 23101 23102 23103 23104 23105 23106 23107 23108 23109 23110 23111 23112 23113 23114 23115 23116 23117 23118 23119 23120 23121 23122 23123 23124 23125 23126 23127 23128 23129 23130 23131 23132 23133 23134 23135 23136 23137 23138 23139 23140 23141 23142 23143 23144 23145 23146 23147 23148 23149 23150 23151 23152 23153 23154 23155 23156 23157 23158 23159 23160 23161 23162 23163 23164 23165 23166 23167 23168 23169 23170 23171 23172 23173 23174 23175 23176 23177 23178 23179 23180 23181 23182 23183 23184 23185 23186 23187 23188 23189 23190 23191 23192 23193 23194 23195 23196 23197 23198 23199 23200 23201 23202 23203 23204 23205 23206 23207 23208 23209 23210 23211 23212 23213 23214 23215 23216 23217 23218 23219 23220 23221 23222 23223 23224 23225 23226 23227 23228 23229 23230 23231 23232 23233 23234 23235 23236 23237 23238 23239 23240 23241 23242 23243 23244 23245 23246 23247 23248 23249 23250 23251 23252 23253 23254 23255 23256 23257 23258 23259 23260 23261 23262 23263 23264 23265 23266 23267 23268 23269 23270 23271 23272 23273 23274 23275 23276 23277 23278 23279 23280 23281 23282 23283 23284 23285 23286 23287 23288 23289 23290 23291 23292 23293 23294 23295 23296 23297 23298 23299 23300 23301 23302 23303 23304 23305 23306 23307 23308 23309 23310 23311 23312 23313 23314 23315 23316 23317 23318 23319 23320 23321 23322 23323 23324 23325 23326 23327 23328 23329 23330 23331 23332 23333 23334 23335 23336 23337 23338 23339 23340 23341 23342 23343 23344 23345 23346 23347 23348 23349 23350 23351 23352 23353 23354 23355 23356 23357 23358 23359 23360 23361 23362 23363 23364 23365 23366 23367 23368 23369 23370 23371 23372 23373 23374 23375 23376 23377 23378 23379 23380 23381 23382 23383 23384 23385 23386 23387 23388 23389 23390 23391 23392 23393 23394 23395 23396 23397 23398 23399 23400 23401 23402 23403 23404 23405 23406 23407 23408 23409 23410 23411 23412 23413 23414 23415 23416 23417 23418 23419 23420 23421 23422 23423 23424 23425 23426 23427 23428 23429 23430 23431 23432 23433 23434 23435 23436 23437 23438 23439 23440 23441 23442 23443 23444 23445 23446 23447 23448 23449 23450 23451 23452 23453 23454 23455 23456 23457 23458 23459 23460 23461 23462 23463 23464 23465 23466 23467 23468 23469 23470 23471 23472 23473 23474 23475 23476 23477 23478 23479 23480 23481 23482 23483 23484 23485 23486 23487 23488 23489 23490 23491 23492 23493 23494 23495 23496 23497 23498 23499 23500 23501 23502 23503 23504 23505 23506 23507 23508 23509 23510 23511 23512 23513 23514 23515 23516 23517 23518 23519 23520 23521 23522 23523 23524 23525 23526 23527 23528 23529 23530 23531 23532 23533 23534 23535 23536 23537 23538 23539 23540 23541 23542 23543 23544 23545 23546 23547 23548 23549 23550 23551 23552 23553 23554 23555 23556 23557 23558 23559 23560 23561 23562 23563 23564 23565 23566 23567 23568 23569 23570 23571 23572 23573 23574 23575 23576 23577 23578 23579 23580 23581 23582 23583 23584 23585 23586 23587 23588 23589 23590 23591 23592 23593 23594 23595 23596 23597 23598 23599 23600 23601 23602 23603 23604 23605 23606 |
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: no
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1490 utf-check-1490-3-80-0.jnk \
{File "%TEMP%/utf-check-1490-3-80-0.jnk" has 65540 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1491 utf-check-1491-3-80-1.jnk \
{File "%TEMP%/utf-check-1491-3-80-1.jnk" has 65541 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1492 utf-check-1492-3-81-0.jnk \
{File "%TEMP%/utf-check-1492-3-81-0.jnk" has 65542 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1493 utf-check-1493-3-81-1.jnk \
{File "%TEMP%/utf-check-1493-3-81-1.jnk" has 65543 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1494 utf-check-1494-3-82-0.jnk \
{File "%TEMP%/utf-check-1494-3-82-0.jnk" has 65542 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1495 utf-check-1495-3-82-1.jnk \
{File "%TEMP%/utf-check-1495-3-82-1.jnk" has 65543 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1496 utf-check-1496-3-83-0.jnk \
{File "%TEMP%/utf-check-1496-3-83-0.jnk" has 65544 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1497 utf-check-1497-3-83-1.jnk \
{File "%TEMP%/utf-check-1497-3-83-1.jnk" has 65545 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1498 utf-check-1498-3-84-0.jnk \
{File "%TEMP%/utf-check-1498-3-84-0.jnk" has 65570 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1499 utf-check-1499-3-84-1.jnk \
{File "%TEMP%/utf-check-1499-3-84-1.jnk" has 65571 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1500 utf-check-1500-3-85-0.jnk \
{File "%TEMP%/utf-check-1500-3-85-0.jnk" has 65572 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1501 utf-check-1501-3-85-1.jnk \
{File "%TEMP%/utf-check-1501-3-85-1.jnk" has 65573 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1502 utf-check-1502-3-86-0.jnk \
{File "%TEMP%/utf-check-1502-3-86-0.jnk" has 65572 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1503 utf-check-1503-3-86-1.jnk \
{File "%TEMP%/utf-check-1503-3-86-1.jnk" has 65573 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1504 utf-check-1504-3-87-0.jnk \
{File "%TEMP%/utf-check-1504-3-87-0.jnk" has 65574 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1505 utf-check-1505-3-87-1.jnk \
{File "%TEMP%/utf-check-1505-3-87-1.jnk" has 65575 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1506 utf-check-1506-3-88-0.jnk \
{File "%TEMP%/utf-check-1506-3-88-0.jnk" has 65542 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1507 utf-check-1507-3-88-1.jnk \
{File "%TEMP%/utf-check-1507-3-88-1.jnk" has 65543 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1508 utf-check-1508-3-89-0.jnk \
{File "%TEMP%/utf-check-1508-3-89-0.jnk" has 65544 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1509 utf-check-1509-3-89-1.jnk \
{File "%TEMP%/utf-check-1509-3-89-1.jnk" has 65545 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1510 utf-check-1510-3-90-0.jnk \
{File "%TEMP%/utf-check-1510-3-90-0.jnk" has 65544 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1511 utf-check-1511-3-90-1.jnk \
{File "%TEMP%/utf-check-1511-3-90-1.jnk" has 65545 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1512 utf-check-1512-3-91-0.jnk \
{File "%TEMP%/utf-check-1512-3-91-0.jnk" has 65546 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1513 utf-check-1513-3-91-1.jnk \
{File "%TEMP%/utf-check-1513-3-91-1.jnk" has 65547 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1514 utf-check-1514-3-92-0.jnk \
{File "%TEMP%/utf-check-1514-3-92-0.jnk" has 65572 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1515 utf-check-1515-3-92-1.jnk \
{File "%TEMP%/utf-check-1515-3-92-1.jnk" has 65573 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1516 utf-check-1516-3-93-0.jnk \
{File "%TEMP%/utf-check-1516-3-93-0.jnk" has 65574 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1517 utf-check-1517-3-93-1.jnk \
{File "%TEMP%/utf-check-1517-3-93-1.jnk" has 65575 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1518 utf-check-1518-3-94-0.jnk \
{File "%TEMP%/utf-check-1518-3-94-0.jnk" has 65574 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1519 utf-check-1519-3-94-1.jnk \
{File "%TEMP%/utf-check-1519-3-94-1.jnk" has 65575 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1520 utf-check-1520-3-95-0.jnk \
{File "%TEMP%/utf-check-1520-3-95-0.jnk" has 65576 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1521 utf-check-1521-3-95-1.jnk \
{File "%TEMP%/utf-check-1521-3-95-1.jnk" has 65577 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1522 utf-check-1522-3-96-0.jnk \
{File "%TEMP%/utf-check-1522-3-96-0.jnk" has 65542 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1523 utf-check-1523-3-96-1.jnk \
{File "%TEMP%/utf-check-1523-3-96-1.jnk" has 65543 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1524 utf-check-1524-3-97-0.jnk \
{File "%TEMP%/utf-check-1524-3-97-0.jnk" has 65544 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1525 utf-check-1525-3-97-1.jnk \
{File "%TEMP%/utf-check-1525-3-97-1.jnk" has 65545 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1526 utf-check-1526-3-98-0.jnk \
{File "%TEMP%/utf-check-1526-3-98-0.jnk" has 65544 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1527 utf-check-1527-3-98-1.jnk \
{File "%TEMP%/utf-check-1527-3-98-1.jnk" has 65545 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1528 utf-check-1528-3-99-0.jnk \
{File "%TEMP%/utf-check-1528-3-99-0.jnk" has 65546 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1529 utf-check-1529-3-99-1.jnk \
{File "%TEMP%/utf-check-1529-3-99-1.jnk" has 65547 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1530 utf-check-1530-3-100-0.jnk \
{File "%TEMP%/utf-check-1530-3-100-0.jnk" has 65572 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1531 utf-check-1531-3-100-1.jnk \
{File "%TEMP%/utf-check-1531-3-100-1.jnk" has 65573 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1532 utf-check-1532-3-101-0.jnk \
{File "%TEMP%/utf-check-1532-3-101-0.jnk" has 65574 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1533 utf-check-1533-3-101-1.jnk \
{File "%TEMP%/utf-check-1533-3-101-1.jnk" has 65575 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1534 utf-check-1534-3-102-0.jnk \
{File "%TEMP%/utf-check-1534-3-102-0.jnk" has 65574 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1535 utf-check-1535-3-102-1.jnk \
{File "%TEMP%/utf-check-1535-3-102-1.jnk" has 65575 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1536 utf-check-1536-3-103-0.jnk \
{File "%TEMP%/utf-check-1536-3-103-0.jnk" has 65576 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1537 utf-check-1537-3-103-1.jnk \
{File "%TEMP%/utf-check-1537-3-103-1.jnk" has 65577 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1538 utf-check-1538-3-104-0.jnk \
{File "%TEMP%/utf-check-1538-3-104-0.jnk" has 65544 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1539 utf-check-1539-3-104-1.jnk \
{File "%TEMP%/utf-check-1539-3-104-1.jnk" has 65545 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1540 utf-check-1540-3-105-0.jnk \
{File "%TEMP%/utf-check-1540-3-105-0.jnk" has 65546 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1541 utf-check-1541-3-105-1.jnk \
{File "%TEMP%/utf-check-1541-3-105-1.jnk" has 65547 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1542 utf-check-1542-3-106-0.jnk \
{File "%TEMP%/utf-check-1542-3-106-0.jnk" has 65546 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1543 utf-check-1543-3-106-1.jnk \
{File "%TEMP%/utf-check-1543-3-106-1.jnk" has 65547 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1544 utf-check-1544-3-107-0.jnk \
{File "%TEMP%/utf-check-1544-3-107-0.jnk" has 65548 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1545 utf-check-1545-3-107-1.jnk \
{File "%TEMP%/utf-check-1545-3-107-1.jnk" has 65549 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1546 utf-check-1546-3-108-0.jnk \
{File "%TEMP%/utf-check-1546-3-108-0.jnk" has 65574 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1547 utf-check-1547-3-108-1.jnk \
{File "%TEMP%/utf-check-1547-3-108-1.jnk" has 65575 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1548 utf-check-1548-3-109-0.jnk \
{File "%TEMP%/utf-check-1548-3-109-0.jnk" has 65576 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1549 utf-check-1549-3-109-1.jnk \
{File "%TEMP%/utf-check-1549-3-109-1.jnk" has 65577 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1550 utf-check-1550-3-110-0.jnk \
{File "%TEMP%/utf-check-1550-3-110-0.jnk" has 65576 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1551 utf-check-1551-3-110-1.jnk \
{File "%TEMP%/utf-check-1551-3-110-1.jnk" has 65577 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1552 utf-check-1552-3-111-0.jnk \
{File "%TEMP%/utf-check-1552-3-111-0.jnk" has 65578 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
Has flag LOOK_LONE_LF: no
Has flag LOOK_CRLF: no
Has flag LOOK_LONG: yes
Has flag LOOK_INVALID: yes
Has flag LOOK_ODD: no
Has flag LOOK_SHORT: no}
utf-check 1553 utf-check-1553-3-111-1.jnk \
{File "%TEMP%/utf-check-1553-3-111-1.jnk" has 65579 bytes.
Starts with UTF-8 BOM: no
Starts with UTF-16 BOM: no
Looks like UTF-8: no
Has flag LOOK_NUL: yes
Has flag LOOK_CR: no
Has flag LOOK_LONE_CR: no
Has flag LOOK_LF: no
|
| ︙ | ︙ |
| ︙ | ︙ | |||
43 44 45 46 47 48 49 |
# "text/x-fossil-wiki" (the default mimetype for rendering)
# if the N card is omitted in the manifest.
# Note: Makes fossil calls, so $CODE and $RESULT will be corrupted
proc get_mime_type {name} {
global CODE RESULT
fossil http << "GET /wiki?name=$name"
if {$CODE != 0} {
| | | | | | | | | | | | | 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 |
# "text/x-fossil-wiki" (the default mimetype for rendering)
# if the N card is omitted in the manifest.
# Note: Makes fossil calls, so $CODE and $RESULT will be corrupted
proc get_mime_type {name} {
global CODE RESULT
fossil http << "GET /wiki?name=$name"
if {$CODE != 0} {
return "error: /wiki?name=$name $CODE $RESULT"
}
fossil whatis --type w $name
if {$CODE != 0} {
return "error: fossil whatis --type w $name $CODE $RESULT"
}
set CODE [regexp -line {^artifact:\s*([0-9a-f]+)$} $RESULT match info]
if {$CODE == 0} {
return "error: whatis returned no info for wiki page $name"
}
fossil artifact $info
if {$CODE != 0} {
return "error: fossil artifact $info $CODE $RESULT"
}
set CODE [regexp -line {^N (.*)$} $RESULT match mimetype]
if {$CODE == 0} {
return "text/x-fossil-wiki"
}
return $mimetype
}
|
| ︙ | ︙ |
| ︙ | ︙ |
| ︙ | ︙ | |||
19 20 21 22 23 24 25 |
msg TXT
);
}
while {1} {
db transaction immediate {
set n 0
db eval {SELECT msg FROM email} {
| > > > > | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
msg TXT
);
}
while {1} {
db transaction immediate {
set n 0
db eval {SELECT msg FROM email} {
set pipe $PIPE
if {[regexp {\nFrom:[^\n]*<([^>]+)>} $msg all addr]} {
append pipe " -f $addr"
}
set out [open |$pipe w]
puts -nonewline $out $msg
flush $out
close $out
incr n
}
if {$n>0} {
db eval {DELETE FROM email}
|
| ︙ | ︙ |
| ︙ | ︙ |
| ︙ | ︙ |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
#!/usr/bin/env perl
# Fossil emulation of the "git log --patch / -p" feature: emit a stream
# of diffs from one version to the next for each file named on the
# command line.
#
# LIMITATIONS: It does not assume "all files" if you give no args, and
# it cannot take a directory to mean "all files under this parent".
#
# PREREQUISITES: This script needs several CPAN modules to run properly.
# There are multiple methods to install them:
#
# sudo dnf install perl-File-Which perl-IO-Interactive
# sudo apt install libfile-which-perl libio-interactive-perl
# sudo cpanm File::Which IO::Interactive
# ...etc...
use strict;
use warnings;
use Carp;
use File::Which;
use IO::Interactive qw(is_interactive);
die "usage: $0 <files...>\n\n" unless @ARGV;
my $out;
if (is_interactive()) {
my $pager = $ENV{PAGER} || which('less') || which('more');
open $out, '|-', $pager or croak "Cannot pipe to $pager: $!";
}
else {
$out = *STDOUT;
}
open my $bcmd, '-|', 'fossil branch current'
or die "Cannot get branch: $!\n";
my $cbranch = <$bcmd>;
chomp $cbranch;
close $bcmd;
for my $file (@ARGV) {
my $lastckid;
open my $finfo, '-|', "fossil finfo --brief --limit 0 '$file'"
or die "Failed to get file info: $!\n";
my @filines = <$finfo>;
close $finfo;
for my $line (@filines) {
my ($currckid, $date, $user, $branch, @cwords) = split ' ', $line;
next unless $branch eq $cbranch;
if (defined $lastckid and defined $branch) {
my $comment = join ' ', @cwords;
open my $diff, '-|', 'fossil', 'diff', $file,
'--from', $currckid,
'--to', $lastckid,
or die "Failed to diff $currckid -> $lastckid: $!\n";
my @dl = <$diff>;
close $diff;
my $patch = join '', @dl;
print $out <<"OUT"
Checkin ID $currckid to $branch by $user on $date
Comment: $comment
$patch
OUT
}
$lastckid = $currckid;
}
}
|
| ︙ | ︙ |
| ︙ | ︙ |
| ︙ | ︙ | |||
81 82 83 84 85 86 87 | UTILS_OBJ=$(UTILS:.exe=.obj) UTILS_SRC=$(foreach uf,$(UTILS),$(SRCDIR)$(uf:.exe=.c)) # define the SQLite files, which need special flags on compile SQLITESRC=sqlite3.c ORIGSQLITESRC=$(foreach sf,$(SQLITESRC),$(SRCDIR)$(sf)) SQLITEOBJ=$(foreach sf,$(SQLITESRC),$(sf:.c=.obj)) | | | | 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | UTILS_OBJ=$(UTILS:.exe=.obj) UTILS_SRC=$(foreach uf,$(UTILS),$(SRCDIR)$(uf:.exe=.c)) # define the SQLite files, which need special flags on compile SQLITESRC=sqlite3.c ORIGSQLITESRC=$(foreach sf,$(SQLITESRC),$(SRCDIR)$(sf)) SQLITEOBJ=$(foreach sf,$(SQLITESRC),$(sf:.c=.obj)) SQLITEDEFINES=-DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -DSQLITE_WIN32_NO_ANSI # define the SQLite shell files, which need special flags on compile SQLITESHELLSRC=shell.c ORIGSQLITESHELLSRC=$(foreach sf,$(SQLITESHELLSRC),$(SRCDIR)$(sf)) SQLITESHELLOBJ=$(foreach sf,$(SQLITESHELLSRC),$(sf:.c=.obj)) SQLITESHELLDEFINES=-DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen # define the th scripting files, which need special flags on compile THSRC=th.c th_lang.c ORIGTHSRC=$(foreach sf,$(THSRC),$(SRCDIR)$(sf)) THOBJ=$(foreach sf,$(THSRC),$(sf:.c=.obj)) # define the zlib files, needed by this compile |
| ︙ | ︙ | |||
145 146 147 148 149 150 151 | builtin_data.h: $(EXTRA_FILES) mkbuiltin.exe mkbuiltin.exe --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ # extracting version info from manifest VERSION.h: version.exe ..\manifest.uuid ..\manifest ..\VERSION version.exe ..\manifest.uuid ..\manifest ..\VERSION >$@ | < < < | | 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | builtin_data.h: $(EXTRA_FILES) mkbuiltin.exe mkbuiltin.exe --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ # extracting version info from manifest VERSION.h: version.exe ..\manifest.uuid ..\manifest ..\VERSION version.exe ..\manifest.uuid ..\manifest ..\VERSION >$@ # generate the simplified headers headers: makeheaders.exe page_index.h builtin_data.h VERSION.h ../src/sqlite3.h ../src/th.h makeheaders.exe $(foreach ts,$(TRANSLATEDSRC),$(ts):$(ts:_.c=.h)) ../src/sqlite3.h ../src/th.h VERSION.h echo Done >$@ # compile C sources with relevant options $(TRANSLATEDOBJ): %_.obj: %_.c %.h $(CC) $(CCFLAGS) $(INCLUDE) "$<" -Fo"$@" |
| ︙ | ︙ |
| ︙ | ︙ | |||
22 23 24 25 26 27 28 | SSL = CFLAGS = -o BCC = $(DMDIR)\bin\dmc $(CFLAGS) TCC = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL) LIBS = $(DMDIR)\extra\lib\ zlib wsock32 advapi32 dnsapi | | | | | | | 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 | SSL = CFLAGS = -o BCC = $(DMDIR)\bin\dmc $(CFLAGS) TCC = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL) LIBS = $(DMDIR)\extra\lib\ zlib wsock32 advapi32 dnsapi SQLITE_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 SHELL_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c piechart_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c webmail_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c wysiwyg_.c xfer_.c xfersetup_.c zip_.c OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\webmail$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\wysiwyg$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O RC=$(DMDIR)\bin\rcc RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__ APPNAME = $(OBJDIR)\fossil$(E) all: $(APPNAME) $(APPNAME) : translate$E mkindex$E codecheck1$E headers $(OBJ) $(OBJDIR)\link cd $(OBJDIR) codecheck1$E $(SRC) $(DMDIR)\bin\link @link $(OBJDIR)\fossil.res: $B\win\fossil.rc $(RC) $(RCFLAGS) -o$@ $** $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi checkin checkout clearsign clone comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname http http_socket http_ssl http_transport import info json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path piechart pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile webmail wiki wikiformat winfile winhttp wysiwyg xfer xfersetup zip shell sqlite3 th th_lang > $@ +echo fossil >> $@ +echo fossil >> $@ +echo $(LIBS) >> $@ +echo. >> $@ +echo fossil >> $@ translate$E: $(SRCDIR)\translate.c |
| ︙ | ︙ | |||
69 70 71 72 73 74 75 | mkbuiltin$E: $(SRCDIR)\mkbuiltin.c $(BCC) -o$@ $** mkversion$E: $(SRCDIR)\mkversion.c $(BCC) -o$@ $** | < < < | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | mkbuiltin$E: $(SRCDIR)\mkbuiltin.c $(BCC) -o$@ $** mkversion$E: $(SRCDIR)\mkversion.c $(BCC) -o$@ $** codecheck1$E: $(SRCDIR)\codecheck1.c $(BCC) -o$@ $** $(OBJDIR)\shell$O : $(SRCDIR)\shell.c $(TCC) -o$@ -c $(SHELL_OPTIONS) $(SQLITE_OPTIONS) $(SHELL_CFLAGS) $** $(OBJDIR)\sqlite3$O : $(SRCDIR)\sqlite3.c |
| ︙ | ︙ | |||
93 94 95 96 97 98 99 | $(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h cp $@ $@ VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION +$** > $@ | < < < | < > > > > > > > > > > > > | 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 | $(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h cp $@ $@ VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION +$** > $@ page_index.h: mkindex$E $(SRC) +$** > $@ builtin_data.h: mkbuiltin$E $(EXTRA_FILES) mkbuiltin$E --prefix $(SRCDIR)/ $(EXTRA_FILES) > $@ clean: -del $(OBJDIR)\*.obj -del *.obj *_.c *.h *.map realclean: -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E codecheck1$E mkbuiltin$E $(OBJDIR)\json$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_artifact$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_branch$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_config$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_diff$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_dir$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_finfo$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_query$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_report$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_status$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h $(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h $(OBJDIR)\add$O : add_.c add.h $(TCC) -o$@ -c add_.c add_.c : $(SRCDIR)\add.c +translate$E $** > $@ $(OBJDIR)\ajax$O : ajax_.c ajax.h $(TCC) -o$@ -c ajax_.c ajax_.c : $(SRCDIR)\ajax.c +translate$E $** > $@ $(OBJDIR)\alerts$O : alerts_.c alerts.h $(TCC) -o$@ -c alerts_.c alerts_.c : $(SRCDIR)\alerts.c +translate$E $** > $@ $(OBJDIR)\allrepo$O : allrepo_.c allrepo.h $(TCC) -o$@ -c allrepo_.c allrepo_.c : $(SRCDIR)\allrepo.c +translate$E $** > $@ $(OBJDIR)\attach$O : attach_.c attach.h $(TCC) -o$@ -c attach_.c attach_.c : $(SRCDIR)\attach.c +translate$E $** > $@ $(OBJDIR)\backlink$O : backlink_.c backlink.h $(TCC) -o$@ -c backlink_.c backlink_.c : $(SRCDIR)\backlink.c +translate$E $** > $@ $(OBJDIR)\backoffice$O : backoffice_.c backoffice.h $(TCC) -o$@ -c backoffice_.c backoffice_.c : $(SRCDIR)\backoffice.c +translate$E $** > $@ |
| ︙ | ︙ | |||
360 361 362 363 364 365 366 367 368 369 370 371 372 373 | +translate$E $** > $@ $(OBJDIR)\file$O : file_.c file.h $(TCC) -o$@ -c file_.c file_.c : $(SRCDIR)\file.c +translate$E $** > $@ $(OBJDIR)\finfo$O : finfo_.c finfo.h $(TCC) -o$@ -c finfo_.c finfo_.c : $(SRCDIR)\finfo.c +translate$E $** > $@ | > > > > > > | 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 | +translate$E $** > $@ $(OBJDIR)\file$O : file_.c file.h $(TCC) -o$@ -c file_.c file_.c : $(SRCDIR)\file.c +translate$E $** > $@ $(OBJDIR)\fileedit$O : fileedit_.c fileedit.h $(TCC) -o$@ -c fileedit_.c fileedit_.c : $(SRCDIR)\fileedit.c +translate$E $** > $@ $(OBJDIR)\finfo$O : finfo_.c finfo.h $(TCC) -o$@ -c finfo_.c finfo_.c : $(SRCDIR)\finfo.c +translate$E $** > $@ |
| ︙ | ︙ | |||
390 391 392 393 394 395 396 397 398 399 400 401 402 403 | +translate$E $** > $@ $(OBJDIR)\fusefs$O : fusefs_.c fusefs.h $(TCC) -o$@ -c fusefs_.c fusefs_.c : $(SRCDIR)\fusefs.c +translate$E $** > $@ $(OBJDIR)\glob$O : glob_.c glob.h $(TCC) -o$@ -c glob_.c glob_.c : $(SRCDIR)\glob.c +translate$E $** > $@ | > > > > > > | 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 | +translate$E $** > $@ $(OBJDIR)\fusefs$O : fusefs_.c fusefs.h $(TCC) -o$@ -c fusefs_.c fusefs_.c : $(SRCDIR)\fusefs.c +translate$E $** > $@ $(OBJDIR)\fuzz$O : fuzz_.c fuzz.h $(TCC) -o$@ -c fuzz_.c fuzz_.c : $(SRCDIR)\fuzz.c +translate$E $** > $@ $(OBJDIR)\glob$O : glob_.c glob.h $(TCC) -o$@ -c glob_.c glob_.c : $(SRCDIR)\glob.c +translate$E $** > $@ |
| ︙ | ︙ | |||
816 817 818 819 820 821 822 823 824 825 826 827 828 829 | +translate$E $** > $@ $(OBJDIR)\tar$O : tar_.c tar.h $(TCC) -o$@ -c tar_.c tar_.c : $(SRCDIR)\tar.c +translate$E $** > $@ $(OBJDIR)\th_main$O : th_main_.c th_main.h $(TCC) -o$@ -c th_main_.c th_main_.c : $(SRCDIR)\th_main.c +translate$E $** > $@ | > > > > > > | 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 | +translate$E $** > $@ $(OBJDIR)\tar$O : tar_.c tar.h $(TCC) -o$@ -c tar_.c tar_.c : $(SRCDIR)\tar.c +translate$E $** > $@ $(OBJDIR)\terminal$O : terminal_.c terminal.h $(TCC) -o$@ -c terminal_.c terminal_.c : $(SRCDIR)\terminal.c +translate$E $** > $@ $(OBJDIR)\th_main$O : th_main_.c th_main.h $(TCC) -o$@ -c th_main_.c th_main_.c : $(SRCDIR)\th_main.c +translate$E $** > $@ |
| ︙ | ︙ | |||
955 956 957 958 959 960 961 | $(OBJDIR)\zip$O : zip_.c zip.h $(TCC) -o$@ -c zip_.c zip_.c : $(SRCDIR)\zip.c +translate$E $** > $@ | | | | 978 979 980 981 982 983 984 985 986 987 | $(OBJDIR)\zip$O : zip_.c zip.h $(TCC) -o$@ -c zip_.c zip_.c : $(SRCDIR)\zip.c +translate$E $** > $@ headers: makeheaders$E page_index.h builtin_data.h VERSION.h +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h piechart_.c:piechart.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h webmail_.c:webmail.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h wysiwyg_.c:wysiwyg.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h @copy /Y nul: headers |
| ︙ | ︙ | |||
172 173 174 175 176 177 178 | endif #### The directories where the OpenSSL include and library files are located. # The recommended usage here is to use the Sysinternals junction tool # to create a hard link between an "openssl-1.x" sub-directory of the # Fossil source code directory and the target OpenSSL source directory. # | | | 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | endif #### The directories where the OpenSSL include and library files are located. # The recommended usage here is to use the Sysinternals junction tool # to create a hard link between an "openssl-1.x" sub-directory of the # Fossil source code directory and the target OpenSSL source directory. # OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1g OPENSSLINCDIR = $(OPENSSLDIR)/include OPENSSLLIBDIR = $(OPENSSLDIR) #### Either the directory where the Tcl library is installed or the Tcl # source code directory resides (depending on the value of the macro # FOSSIL_TCL_SOURCE). If this points to the Tcl install directory, # this directory must have "include" and "lib" sub-directories. If |
| ︙ | ︙ | |||
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 | # You should not need to change anything below this line #-------------------------------------------------------- XBCC = $(BCC) $(CFLAGS) XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR) SRC = \ $(SRCDIR)/add.c \ $(SRCDIR)/alerts.c \ $(SRCDIR)/allrepo.c \ $(SRCDIR)/attach.c \ $(SRCDIR)/backoffice.c \ $(SRCDIR)/bag.c \ $(SRCDIR)/bisect.c \ $(SRCDIR)/blob.c \ $(SRCDIR)/branch.c \ $(SRCDIR)/browse.c \ $(SRCDIR)/builtin.c \ | > > | 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 | # You should not need to change anything below this line #-------------------------------------------------------- XBCC = $(BCC) $(CFLAGS) XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR) SRC = \ $(SRCDIR)/add.c \ $(SRCDIR)/ajax.c \ $(SRCDIR)/alerts.c \ $(SRCDIR)/allrepo.c \ $(SRCDIR)/attach.c \ $(SRCDIR)/backlink.c \ $(SRCDIR)/backoffice.c \ $(SRCDIR)/bag.c \ $(SRCDIR)/bisect.c \ $(SRCDIR)/blob.c \ $(SRCDIR)/branch.c \ $(SRCDIR)/browse.c \ $(SRCDIR)/builtin.c \ |
| ︙ | ︙ | |||
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 | $(SRCDIR)/doc.c \ $(SRCDIR)/encode.c \ $(SRCDIR)/etag.c \ $(SRCDIR)/event.c \ $(SRCDIR)/export.c \ $(SRCDIR)/extcgi.c \ $(SRCDIR)/file.c \ $(SRCDIR)/finfo.c \ $(SRCDIR)/foci.c \ $(SRCDIR)/forum.c \ $(SRCDIR)/fshell.c \ $(SRCDIR)/fusefs.c \ $(SRCDIR)/glob.c \ $(SRCDIR)/graph.c \ $(SRCDIR)/gzip.c \ $(SRCDIR)/hname.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ $(SRCDIR)/http_ssl.c \ | > > | 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 | $(SRCDIR)/doc.c \ $(SRCDIR)/encode.c \ $(SRCDIR)/etag.c \ $(SRCDIR)/event.c \ $(SRCDIR)/export.c \ $(SRCDIR)/extcgi.c \ $(SRCDIR)/file.c \ $(SRCDIR)/fileedit.c \ $(SRCDIR)/finfo.c \ $(SRCDIR)/foci.c \ $(SRCDIR)/forum.c \ $(SRCDIR)/fshell.c \ $(SRCDIR)/fusefs.c \ $(SRCDIR)/fuzz.c \ $(SRCDIR)/glob.c \ $(SRCDIR)/graph.c \ $(SRCDIR)/gzip.c \ $(SRCDIR)/hname.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ $(SRCDIR)/http_ssl.c \ |
| ︙ | ︙ | |||
549 550 551 552 553 554 555 556 557 558 559 560 561 562 | $(SRCDIR)/stash.c \ $(SRCDIR)/stat.c \ $(SRCDIR)/statrep.c \ $(SRCDIR)/style.c \ $(SRCDIR)/sync.c \ $(SRCDIR)/tag.c \ $(SRCDIR)/tar.c \ $(SRCDIR)/th_main.c \ $(SRCDIR)/timeline.c \ $(SRCDIR)/tkt.c \ $(SRCDIR)/tktsetup.c \ $(SRCDIR)/undo.c \ $(SRCDIR)/unicode.c \ $(SRCDIR)/unversioned.c \ | > | 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 | $(SRCDIR)/stash.c \ $(SRCDIR)/stat.c \ $(SRCDIR)/statrep.c \ $(SRCDIR)/style.c \ $(SRCDIR)/sync.c \ $(SRCDIR)/tag.c \ $(SRCDIR)/tar.c \ $(SRCDIR)/terminal.c \ $(SRCDIR)/th_main.c \ $(SRCDIR)/timeline.c \ $(SRCDIR)/tkt.c \ $(SRCDIR)/tktsetup.c \ $(SRCDIR)/undo.c \ $(SRCDIR)/unicode.c \ $(SRCDIR)/unversioned.c \ |
| ︙ | ︙ | |||
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 | $(SRCDIR)/../skins/rounded1/details.txt \ $(SRCDIR)/../skins/rounded1/footer.txt \ $(SRCDIR)/../skins/rounded1/header.txt \ $(SRCDIR)/../skins/xekri/css.txt \ $(SRCDIR)/../skins/xekri/details.txt \ $(SRCDIR)/../skins/xekri/footer.txt \ $(SRCDIR)/../skins/xekri/header.txt \ $(SRCDIR)/ci_edit.js \ $(SRCDIR)/copybtn.js \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ $(SRCDIR)/graph.js \ $(SRCDIR)/href.js \ $(SRCDIR)/login.js \ $(SRCDIR)/markdown.md \ $(SRCDIR)/menu.js \ $(SRCDIR)/sbsdiff.js \ $(SRCDIR)/scroll.js \ $(SRCDIR)/skin.js \ $(SRCDIR)/sorttable.js \ $(SRCDIR)/tree.js \ $(SRCDIR)/useredit.js \ $(SRCDIR)/wiki.wiki TRANS_SRC = \ $(OBJDIR)/add_.c \ $(OBJDIR)/alerts_.c \ $(OBJDIR)/allrepo_.c \ $(OBJDIR)/attach_.c \ $(OBJDIR)/backoffice_.c \ $(OBJDIR)/bag_.c \ $(OBJDIR)/bisect_.c \ $(OBJDIR)/blob_.c \ $(OBJDIR)/branch_.c \ $(OBJDIR)/browse_.c \ $(OBJDIR)/builtin_.c \ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | $(SRCDIR)/../skins/rounded1/details.txt \ $(SRCDIR)/../skins/rounded1/footer.txt \ $(SRCDIR)/../skins/rounded1/header.txt \ $(SRCDIR)/../skins/xekri/css.txt \ $(SRCDIR)/../skins/xekri/details.txt \ $(SRCDIR)/../skins/xekri/footer.txt \ $(SRCDIR)/../skins/xekri/header.txt \ $(SRCDIR)/accordion.js \ $(SRCDIR)/ci_edit.js \ $(SRCDIR)/copybtn.js \ $(SRCDIR)/default.css \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ $(SRCDIR)/fossil.bootstrap.js \ $(SRCDIR)/fossil.confirmer.js \ $(SRCDIR)/fossil.dom.js \ $(SRCDIR)/fossil.fetch.js \ $(SRCDIR)/fossil.page.fileedit.js \ $(SRCDIR)/fossil.storage.js \ $(SRCDIR)/fossil.tabs.js \ $(SRCDIR)/graph.js \ $(SRCDIR)/href.js \ $(SRCDIR)/login.js \ $(SRCDIR)/markdown.md \ $(SRCDIR)/menu.js \ $(SRCDIR)/sbsdiff.js \ $(SRCDIR)/scroll.js \ $(SRCDIR)/skin.js \ $(SRCDIR)/sorttable.js \ $(SRCDIR)/sounds/0.wav \ $(SRCDIR)/sounds/1.wav \ $(SRCDIR)/sounds/2.wav \ $(SRCDIR)/sounds/3.wav \ $(SRCDIR)/sounds/4.wav \ $(SRCDIR)/sounds/5.wav \ $(SRCDIR)/sounds/6.wav \ $(SRCDIR)/sounds/7.wav \ $(SRCDIR)/sounds/8.wav \ $(SRCDIR)/sounds/9.wav \ $(SRCDIR)/sounds/a.wav \ $(SRCDIR)/sounds/b.wav \ $(SRCDIR)/sounds/c.wav \ $(SRCDIR)/sounds/d.wav \ $(SRCDIR)/sounds/e.wav \ $(SRCDIR)/sounds/f.wav \ $(SRCDIR)/style.admin_log.css \ $(SRCDIR)/style.fileedit.css \ $(SRCDIR)/tree.js \ $(SRCDIR)/useredit.js \ $(SRCDIR)/wiki.wiki TRANS_SRC = \ $(OBJDIR)/add_.c \ $(OBJDIR)/ajax_.c \ $(OBJDIR)/alerts_.c \ $(OBJDIR)/allrepo_.c \ $(OBJDIR)/attach_.c \ $(OBJDIR)/backlink_.c \ $(OBJDIR)/backoffice_.c \ $(OBJDIR)/bag_.c \ $(OBJDIR)/bisect_.c \ $(OBJDIR)/blob_.c \ $(OBJDIR)/branch_.c \ $(OBJDIR)/browse_.c \ $(OBJDIR)/builtin_.c \ |
| ︙ | ︙ | |||
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 | $(OBJDIR)/doc_.c \ $(OBJDIR)/encode_.c \ $(OBJDIR)/etag_.c \ $(OBJDIR)/event_.c \ $(OBJDIR)/export_.c \ $(OBJDIR)/extcgi_.c \ $(OBJDIR)/file_.c \ $(OBJDIR)/finfo_.c \ $(OBJDIR)/foci_.c \ $(OBJDIR)/forum_.c \ $(OBJDIR)/fshell_.c \ $(OBJDIR)/fusefs_.c \ $(OBJDIR)/glob_.c \ $(OBJDIR)/graph_.c \ $(OBJDIR)/gzip_.c \ $(OBJDIR)/hname_.c \ $(OBJDIR)/http_.c \ $(OBJDIR)/http_socket_.c \ $(OBJDIR)/http_ssl_.c \ | > > | 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 | $(OBJDIR)/doc_.c \ $(OBJDIR)/encode_.c \ $(OBJDIR)/etag_.c \ $(OBJDIR)/event_.c \ $(OBJDIR)/export_.c \ $(OBJDIR)/extcgi_.c \ $(OBJDIR)/file_.c \ $(OBJDIR)/fileedit_.c \ $(OBJDIR)/finfo_.c \ $(OBJDIR)/foci_.c \ $(OBJDIR)/forum_.c \ $(OBJDIR)/fshell_.c \ $(OBJDIR)/fusefs_.c \ $(OBJDIR)/fuzz_.c \ $(OBJDIR)/glob_.c \ $(OBJDIR)/graph_.c \ $(OBJDIR)/gzip_.c \ $(OBJDIR)/hname_.c \ $(OBJDIR)/http_.c \ $(OBJDIR)/http_socket_.c \ $(OBJDIR)/http_ssl_.c \ |
| ︙ | ︙ | |||
763 764 765 766 767 768 769 770 771 772 773 774 775 776 | $(OBJDIR)/stash_.c \ $(OBJDIR)/stat_.c \ $(OBJDIR)/statrep_.c \ $(OBJDIR)/style_.c \ $(OBJDIR)/sync_.c \ $(OBJDIR)/tag_.c \ $(OBJDIR)/tar_.c \ $(OBJDIR)/th_main_.c \ $(OBJDIR)/timeline_.c \ $(OBJDIR)/tkt_.c \ $(OBJDIR)/tktsetup_.c \ $(OBJDIR)/undo_.c \ $(OBJDIR)/unicode_.c \ $(OBJDIR)/unversioned_.c \ | > | 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 | $(OBJDIR)/stash_.c \ $(OBJDIR)/stat_.c \ $(OBJDIR)/statrep_.c \ $(OBJDIR)/style_.c \ $(OBJDIR)/sync_.c \ $(OBJDIR)/tag_.c \ $(OBJDIR)/tar_.c \ $(OBJDIR)/terminal_.c \ $(OBJDIR)/th_main_.c \ $(OBJDIR)/timeline_.c \ $(OBJDIR)/tkt_.c \ $(OBJDIR)/tktsetup_.c \ $(OBJDIR)/undo_.c \ $(OBJDIR)/unicode_.c \ $(OBJDIR)/unversioned_.c \ |
| ︙ | ︙ | |||
789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 | $(OBJDIR)/wysiwyg_.c \ $(OBJDIR)/xfer_.c \ $(OBJDIR)/xfersetup_.c \ $(OBJDIR)/zip_.c OBJ = \ $(OBJDIR)/add.o \ $(OBJDIR)/alerts.o \ $(OBJDIR)/allrepo.o \ $(OBJDIR)/attach.o \ $(OBJDIR)/backoffice.o \ $(OBJDIR)/bag.o \ $(OBJDIR)/bisect.o \ $(OBJDIR)/blob.o \ $(OBJDIR)/branch.o \ $(OBJDIR)/browse.o \ $(OBJDIR)/builtin.o \ | > > | 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 | $(OBJDIR)/wysiwyg_.c \ $(OBJDIR)/xfer_.c \ $(OBJDIR)/xfersetup_.c \ $(OBJDIR)/zip_.c OBJ = \ $(OBJDIR)/add.o \ $(OBJDIR)/ajax.o \ $(OBJDIR)/alerts.o \ $(OBJDIR)/allrepo.o \ $(OBJDIR)/attach.o \ $(OBJDIR)/backlink.o \ $(OBJDIR)/backoffice.o \ $(OBJDIR)/bag.o \ $(OBJDIR)/bisect.o \ $(OBJDIR)/blob.o \ $(OBJDIR)/branch.o \ $(OBJDIR)/browse.o \ $(OBJDIR)/builtin.o \ |
| ︙ | ︙ | |||
827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 | $(OBJDIR)/doc.o \ $(OBJDIR)/encode.o \ $(OBJDIR)/etag.o \ $(OBJDIR)/event.o \ $(OBJDIR)/export.o \ $(OBJDIR)/extcgi.o \ $(OBJDIR)/file.o \ $(OBJDIR)/finfo.o \ $(OBJDIR)/foci.o \ $(OBJDIR)/forum.o \ $(OBJDIR)/fshell.o \ $(OBJDIR)/fusefs.o \ $(OBJDIR)/glob.o \ $(OBJDIR)/graph.o \ $(OBJDIR)/gzip.o \ $(OBJDIR)/hname.o \ $(OBJDIR)/http.o \ $(OBJDIR)/http_socket.o \ $(OBJDIR)/http_ssl.o \ | > > | 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 | $(OBJDIR)/doc.o \ $(OBJDIR)/encode.o \ $(OBJDIR)/etag.o \ $(OBJDIR)/event.o \ $(OBJDIR)/export.o \ $(OBJDIR)/extcgi.o \ $(OBJDIR)/file.o \ $(OBJDIR)/fileedit.o \ $(OBJDIR)/finfo.o \ $(OBJDIR)/foci.o \ $(OBJDIR)/forum.o \ $(OBJDIR)/fshell.o \ $(OBJDIR)/fusefs.o \ $(OBJDIR)/fuzz.o \ $(OBJDIR)/glob.o \ $(OBJDIR)/graph.o \ $(OBJDIR)/gzip.o \ $(OBJDIR)/hname.o \ $(OBJDIR)/http.o \ $(OBJDIR)/http_socket.o \ $(OBJDIR)/http_ssl.o \ |
| ︙ | ︙ | |||
903 904 905 906 907 908 909 910 911 912 913 914 915 916 | $(OBJDIR)/stash.o \ $(OBJDIR)/stat.o \ $(OBJDIR)/statrep.o \ $(OBJDIR)/style.o \ $(OBJDIR)/sync.o \ $(OBJDIR)/tag.o \ $(OBJDIR)/tar.o \ $(OBJDIR)/th_main.o \ $(OBJDIR)/timeline.o \ $(OBJDIR)/tkt.o \ $(OBJDIR)/tktsetup.o \ $(OBJDIR)/undo.o \ $(OBJDIR)/unicode.o \ $(OBJDIR)/unversioned.o \ | > | 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 | $(OBJDIR)/stash.o \ $(OBJDIR)/stat.o \ $(OBJDIR)/statrep.o \ $(OBJDIR)/style.o \ $(OBJDIR)/sync.o \ $(OBJDIR)/tag.o \ $(OBJDIR)/tar.o \ $(OBJDIR)/terminal.o \ $(OBJDIR)/th_main.o \ $(OBJDIR)/timeline.o \ $(OBJDIR)/tkt.o \ $(OBJDIR)/tktsetup.o \ $(OBJDIR)/undo.o \ $(OBJDIR)/unicode.o \ $(OBJDIR)/unversioned.o \ |
| ︙ | ︙ | |||
943 944 945 946 947 948 949 | # ifdef USE_WINDOWS TRANSLATE = $(subst /,\,$(OBJDIR)/translate.exe) MAKEHEADERS = $(subst /,\,$(OBJDIR)/makeheaders.exe) MKINDEX = $(subst /,\,$(OBJDIR)/mkindex.exe) MKBUILTIN = $(subst /,\,$(OBJDIR)/mkbuiltin.exe) MKVERSION = $(subst /,\,$(OBJDIR)/mkversion.exe) | < < | | 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 | # ifdef USE_WINDOWS TRANSLATE = $(subst /,\,$(OBJDIR)/translate.exe) MAKEHEADERS = $(subst /,\,$(OBJDIR)/makeheaders.exe) MKINDEX = $(subst /,\,$(OBJDIR)/mkindex.exe) MKBUILTIN = $(subst /,\,$(OBJDIR)/mkbuiltin.exe) MKVERSION = $(subst /,\,$(OBJDIR)/mkversion.exe) CODECHECK1 = $(subst /,\,$(OBJDIR)/codecheck1.exe) CAT = type CP = copy GREP = find MV = copy RM = del /Q MKDIR = -mkdir RMDIR = rmdir /S /Q else TRANSLATE = $(OBJDIR)/translate.exe MAKEHEADERS = $(OBJDIR)/makeheaders.exe MKINDEX = $(OBJDIR)/mkindex.exe MKBUILTIN = $(OBJDIR)/mkbuiltin.exe MKVERSION = $(OBJDIR)/mkversion.exe CODECHECK1 = $(OBJDIR)/codecheck1.exe CAT = cat CP = cp GREP = grep MV = mv RM = rm -f MKDIR = -mkdir -p RMDIR = rm -rf endif all: $(OBJDIR) $(APPNAME) $(OBJDIR)/fossil.o: $(SRCDIR)/../win/fossil.rc $(OBJDIR)/VERSION.h ifdef USE_WINDOWS $(CAT) $(subst /,\,$(SRCDIR)\miniz.c) | $(GREP) "define MZ_VERSION" > $(subst /,\,$(OBJDIR)\minizver.h) $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.rc) $(subst /,\,$(OBJDIR)) $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.ico) $(subst /,\,$(OBJDIR)) $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.exe.manifest) $(subst /,\,$(OBJDIR)) else $(CAT) $(SRCDIR)/miniz.c | $(GREP) "define MZ_VERSION" > $(OBJDIR)/minizver.h |
| ︙ | ︙ | |||
1016 1017 1018 1019 1020 1021 1022 | $(MKBUILTIN): $(SRCDIR)/mkbuiltin.c $(XBCC) -o $@ $(SRCDIR)/mkbuiltin.c $(MKVERSION): $(SRCDIR)/mkversion.c $(XBCC) -o $@ $(SRCDIR)/mkversion.c | < < < < < < | 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 | $(MKBUILTIN): $(SRCDIR)/mkbuiltin.c $(XBCC) -o $@ $(SRCDIR)/mkbuiltin.c $(MKVERSION): $(SRCDIR)/mkversion.c $(XBCC) -o $@ $(SRCDIR)/mkversion.c $(CODECHECK1): $(SRCDIR)/codecheck1.c $(XBCC) -o $@ $(SRCDIR)/codecheck1.c # WARNING. DANGER. Running the test suite 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: $(OBJDIR) $(APPNAME) $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(MKVERSION) $(MKVERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$@ # The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set # to 1. If it is set to 1, then there is no need to build or link # the sqlite3.o object. Instead, the system SQLite will be linked # using -lsqlite3. SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o SQLITE3_OBJ.1 = SQLITE3_OBJ. = $(SQLITE3_OBJ.0) |
| ︙ | ︙ | |||
1147 1148 1149 1150 1151 1152 1153 | $(OBJDIR)/page_index.h: $(TRANS_SRC) $(MKINDEX) $(MKINDEX) $(TRANS_SRC) >$@ $(OBJDIR)/builtin_data.h: $(MKBUILTIN) $(EXTRA_FILES) $(MKBUILTIN) --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ | | > > | 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 | $(OBJDIR)/page_index.h: $(TRANS_SRC) $(MKINDEX) $(MKINDEX) $(TRANS_SRC) >$@ $(OBJDIR)/builtin_data.h: $(MKBUILTIN) $(EXTRA_FILES) $(MKBUILTIN) --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ $(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(MAKEHEADERS) $(OBJDIR)/VERSION.h $(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h \ $(OBJDIR)/ajax_.c:$(OBJDIR)/ajax.h \ $(OBJDIR)/alerts_.c:$(OBJDIR)/alerts.h \ $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \ $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \ $(OBJDIR)/backlink_.c:$(OBJDIR)/backlink.h \ $(OBJDIR)/backoffice_.c:$(OBJDIR)/backoffice.h \ $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \ $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \ $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \ $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \ $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h \ $(OBJDIR)/builtin_.c:$(OBJDIR)/builtin.h \ |
| ︙ | ︙ | |||
1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 | $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \ $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \ $(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \ $(OBJDIR)/event_.c:$(OBJDIR)/event.h \ $(OBJDIR)/export_.c:$(OBJDIR)/export.h \ $(OBJDIR)/extcgi_.c:$(OBJDIR)/extcgi.h \ $(OBJDIR)/file_.c:$(OBJDIR)/file.h \ $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \ $(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \ $(OBJDIR)/forum_.c:$(OBJDIR)/forum.h \ $(OBJDIR)/fshell_.c:$(OBJDIR)/fshell.h \ $(OBJDIR)/fusefs_.c:$(OBJDIR)/fusefs.h \ $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h \ $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h \ $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h \ $(OBJDIR)/hname_.c:$(OBJDIR)/hname.h \ $(OBJDIR)/http_.c:$(OBJDIR)/http.h \ $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \ $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \ | > > | 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 | $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \ $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \ $(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \ $(OBJDIR)/event_.c:$(OBJDIR)/event.h \ $(OBJDIR)/export_.c:$(OBJDIR)/export.h \ $(OBJDIR)/extcgi_.c:$(OBJDIR)/extcgi.h \ $(OBJDIR)/file_.c:$(OBJDIR)/file.h \ $(OBJDIR)/fileedit_.c:$(OBJDIR)/fileedit.h \ $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \ $(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \ $(OBJDIR)/forum_.c:$(OBJDIR)/forum.h \ $(OBJDIR)/fshell_.c:$(OBJDIR)/fshell.h \ $(OBJDIR)/fusefs_.c:$(OBJDIR)/fusefs.h \ $(OBJDIR)/fuzz_.c:$(OBJDIR)/fuzz.h \ $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h \ $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h \ $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h \ $(OBJDIR)/hname_.c:$(OBJDIR)/hname.h \ $(OBJDIR)/http_.c:$(OBJDIR)/http.h \ $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \ $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \ |
| ︙ | ︙ | |||
1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 | $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \ $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \ $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \ $(OBJDIR)/style_.c:$(OBJDIR)/style.h \ $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \ $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h \ $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h \ $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h \ $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h \ $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h \ $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h \ $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h \ $(OBJDIR)/unicode_.c:$(OBJDIR)/unicode.h \ $(OBJDIR)/unversioned_.c:$(OBJDIR)/unversioned.h \ | > | 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 | $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \ $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \ $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \ $(OBJDIR)/style_.c:$(OBJDIR)/style.h \ $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \ $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h \ $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h \ $(OBJDIR)/terminal_.c:$(OBJDIR)/terminal.h \ $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h \ $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h \ $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h \ $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h \ $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h \ $(OBJDIR)/unicode_.c:$(OBJDIR)/unicode.h \ $(OBJDIR)/unversioned_.c:$(OBJDIR)/unversioned.h \ |
| ︙ | ︙ | |||
1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 | $(OBJDIR)/add_.c: $(SRCDIR)/add.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/add.c >$@ $(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c $(OBJDIR)/add.h: $(OBJDIR)/headers $(OBJDIR)/alerts_.c: $(SRCDIR)/alerts.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/alerts.c >$@ $(OBJDIR)/alerts.o: $(OBJDIR)/alerts_.c $(OBJDIR)/alerts.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/alerts.o -c $(OBJDIR)/alerts_.c | > > > > > > > > | 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 | $(OBJDIR)/add_.c: $(SRCDIR)/add.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/add.c >$@ $(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c $(OBJDIR)/add.h: $(OBJDIR)/headers $(OBJDIR)/ajax_.c: $(SRCDIR)/ajax.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/ajax.c >$@ $(OBJDIR)/ajax.o: $(OBJDIR)/ajax_.c $(OBJDIR)/ajax.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/ajax.o -c $(OBJDIR)/ajax_.c $(OBJDIR)/ajax.h: $(OBJDIR)/headers $(OBJDIR)/alerts_.c: $(SRCDIR)/alerts.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/alerts.c >$@ $(OBJDIR)/alerts.o: $(OBJDIR)/alerts_.c $(OBJDIR)/alerts.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/alerts.o -c $(OBJDIR)/alerts_.c |
| ︙ | ︙ | |||
1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 | $(OBJDIR)/attach_.c: $(SRCDIR)/attach.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/attach.c >$@ $(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c $(OBJDIR)/attach.h: $(OBJDIR)/headers $(OBJDIR)/backoffice_.c: $(SRCDIR)/backoffice.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/backoffice.c >$@ $(OBJDIR)/backoffice.o: $(OBJDIR)/backoffice_.c $(OBJDIR)/backoffice.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/backoffice.o -c $(OBJDIR)/backoffice_.c | > > > > > > > > | 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 | $(OBJDIR)/attach_.c: $(SRCDIR)/attach.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/attach.c >$@ $(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c $(OBJDIR)/attach.h: $(OBJDIR)/headers $(OBJDIR)/backlink_.c: $(SRCDIR)/backlink.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/backlink.c >$@ $(OBJDIR)/backlink.o: $(OBJDIR)/backlink_.c $(OBJDIR)/backlink.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/backlink.o -c $(OBJDIR)/backlink_.c $(OBJDIR)/backlink.h: $(OBJDIR)/headers $(OBJDIR)/backoffice_.c: $(SRCDIR)/backoffice.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/backoffice.c >$@ $(OBJDIR)/backoffice.o: $(OBJDIR)/backoffice_.c $(OBJDIR)/backoffice.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/backoffice.o -c $(OBJDIR)/backoffice_.c |
| ︙ | ︙ | |||
1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 | $(OBJDIR)/file_.c: $(SRCDIR)/file.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/file.c >$@ $(OBJDIR)/file.o: $(OBJDIR)/file_.c $(OBJDIR)/file.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/file.o -c $(OBJDIR)/file_.c $(OBJDIR)/file.h: $(OBJDIR)/headers $(OBJDIR)/finfo_.c: $(SRCDIR)/finfo.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/finfo.c >$@ $(OBJDIR)/finfo.o: $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/finfo.o -c $(OBJDIR)/finfo_.c | > > > > > > > > | 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 | $(OBJDIR)/file_.c: $(SRCDIR)/file.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/file.c >$@ $(OBJDIR)/file.o: $(OBJDIR)/file_.c $(OBJDIR)/file.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/file.o -c $(OBJDIR)/file_.c $(OBJDIR)/file.h: $(OBJDIR)/headers $(OBJDIR)/fileedit_.c: $(SRCDIR)/fileedit.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/fileedit.c >$@ $(OBJDIR)/fileedit.o: $(OBJDIR)/fileedit_.c $(OBJDIR)/fileedit.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/fileedit.o -c $(OBJDIR)/fileedit_.c $(OBJDIR)/fileedit.h: $(OBJDIR)/headers $(OBJDIR)/finfo_.c: $(SRCDIR)/finfo.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/finfo.c >$@ $(OBJDIR)/finfo.o: $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/finfo.o -c $(OBJDIR)/finfo_.c |
| ︙ | ︙ | |||
1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 | $(OBJDIR)/fusefs_.c: $(SRCDIR)/fusefs.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/fusefs.c >$@ $(OBJDIR)/fusefs.o: $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/fusefs.o -c $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h: $(OBJDIR)/headers $(OBJDIR)/glob_.c: $(SRCDIR)/glob.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/glob.c >$@ $(OBJDIR)/glob.o: $(OBJDIR)/glob_.c $(OBJDIR)/glob.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/glob.o -c $(OBJDIR)/glob_.c | > > > > > > > > | 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 | $(OBJDIR)/fusefs_.c: $(SRCDIR)/fusefs.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/fusefs.c >$@ $(OBJDIR)/fusefs.o: $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/fusefs.o -c $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h: $(OBJDIR)/headers $(OBJDIR)/fuzz_.c: $(SRCDIR)/fuzz.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/fuzz.c >$@ $(OBJDIR)/fuzz.o: $(OBJDIR)/fuzz_.c $(OBJDIR)/fuzz.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/fuzz.o -c $(OBJDIR)/fuzz_.c $(OBJDIR)/fuzz.h: $(OBJDIR)/headers $(OBJDIR)/glob_.c: $(SRCDIR)/glob.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/glob.c >$@ $(OBJDIR)/glob.o: $(OBJDIR)/glob_.c $(OBJDIR)/glob.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/glob.o -c $(OBJDIR)/glob_.c |
| ︙ | ︙ | |||
2186 2187 2188 2189 2190 2191 2192 | $(XTCC) -o $(OBJDIR)/statrep.o -c $(OBJDIR)/statrep_.c $(OBJDIR)/statrep.h: $(OBJDIR)/headers $(OBJDIR)/style_.c: $(SRCDIR)/style.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/style.c >$@ | | | 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 | $(XTCC) -o $(OBJDIR)/statrep.o -c $(OBJDIR)/statrep_.c $(OBJDIR)/statrep.h: $(OBJDIR)/headers $(OBJDIR)/style_.c: $(SRCDIR)/style.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/style.c >$@ $(OBJDIR)/style.o: $(OBJDIR)/style_.c $(OBJDIR)/style.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/style.o -c $(OBJDIR)/style_.c $(OBJDIR)/style.h: $(OBJDIR)/headers $(OBJDIR)/sync_.c: $(SRCDIR)/sync.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/sync.c >$@ |
| ︙ | ︙ | |||
2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 | $(OBJDIR)/tar_.c: $(SRCDIR)/tar.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/tar.c >$@ $(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tar.o -c $(OBJDIR)/tar_.c $(OBJDIR)/tar.h: $(OBJDIR)/headers $(OBJDIR)/th_main_.c: $(SRCDIR)/th_main.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/th_main.c >$@ $(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/th_main.o -c $(OBJDIR)/th_main_.c | > > > > > > > > | 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 | $(OBJDIR)/tar_.c: $(SRCDIR)/tar.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/tar.c >$@ $(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tar.o -c $(OBJDIR)/tar_.c $(OBJDIR)/tar.h: $(OBJDIR)/headers $(OBJDIR)/terminal_.c: $(SRCDIR)/terminal.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/terminal.c >$@ $(OBJDIR)/terminal.o: $(OBJDIR)/terminal_.c $(OBJDIR)/terminal.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/terminal.o -c $(OBJDIR)/terminal_.c $(OBJDIR)/terminal.h: $(OBJDIR)/headers $(OBJDIR)/th_main_.c: $(SRCDIR)/th_main.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/th_main.c >$@ $(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/th_main.o -c $(OBJDIR)/th_main_.c |
| ︙ | ︙ | |||
2409 2410 2411 2412 2413 2414 2415 |
-DSQLITE_DQS=0 \
-DSQLITE_THREADSAFE=0 \
-DSQLITE_DEFAULT_MEMSTATUS=0 \
-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \
-DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
-DSQLITE_OMIT_DECLTYPE \
-DSQLITE_OMIT_DEPRECATED \
| < > < > | 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 |
-DSQLITE_DQS=0 \
-DSQLITE_THREADSAFE=0 \
-DSQLITE_DEFAULT_MEMSTATUS=0 \
-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \
-DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
-DSQLITE_OMIT_DECLTYPE \
-DSQLITE_OMIT_DEPRECATED \
-DSQLITE_OMIT_PROGRESS_CALLBACK \
-DSQLITE_OMIT_SHARED_CACHE \
-DSQLITE_OMIT_LOAD_EXTENSION \
-DSQLITE_MAX_EXPR_DEPTH=0 \
-DSQLITE_USE_ALLOCA \
-DSQLITE_ENABLE_LOCKING_STYLE=0 \
-DSQLITE_DEFAULT_FILE_FORMAT=4 \
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
-DSQLITE_ENABLE_FTS4 \
-DSQLITE_ENABLE_DBSTAT_VTAB \
-DSQLITE_ENABLE_JSON1 \
-DSQLITE_ENABLE_FTS5 \
-DSQLITE_ENABLE_STMTVTAB \
-DSQLITE_HAVE_ZLIB \
-DSQLITE_INTROSPECTION_PRAGMAS \
-DSQLITE_ENABLE_DBPAGE_VTAB \
-DSQLITE_TRUSTED_SCHEMA=0 \
-DSQLITE_WIN32_NO_ANSI \
$(MINGW_OPTIONS) \
-DSQLITE_USE_MALLOC_H \
-DSQLITE_USE_MSIZE
SHELL_OPTIONS = -DNDEBUG=1 \
-DSQLITE_DQS=0 \
-DSQLITE_THREADSAFE=0 \
-DSQLITE_DEFAULT_MEMSTATUS=0 \
-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \
-DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
-DSQLITE_OMIT_DECLTYPE \
-DSQLITE_OMIT_DEPRECATED \
-DSQLITE_OMIT_PROGRESS_CALLBACK \
-DSQLITE_OMIT_SHARED_CACHE \
-DSQLITE_OMIT_LOAD_EXTENSION \
-DSQLITE_MAX_EXPR_DEPTH=0 \
-DSQLITE_USE_ALLOCA \
-DSQLITE_ENABLE_LOCKING_STYLE=0 \
-DSQLITE_DEFAULT_FILE_FORMAT=4 \
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
-DSQLITE_ENABLE_FTS4 \
-DSQLITE_ENABLE_DBSTAT_VTAB \
-DSQLITE_ENABLE_JSON1 \
-DSQLITE_ENABLE_FTS5 \
-DSQLITE_ENABLE_STMTVTAB \
-DSQLITE_HAVE_ZLIB \
-DSQLITE_INTROSPECTION_PRAGMAS \
-DSQLITE_ENABLE_DBPAGE_VTAB \
-DSQLITE_TRUSTED_SCHEMA=0 \
-Dmain=sqlite3_shell \
-DSQLITE_SHELL_IS_UTF8=1 \
-DSQLITE_OMIT_LOAD_EXTENSION=1 \
-DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \
-DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname \
-DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc \
-Daccess=file_access \
|
| ︙ | ︙ |
| ︙ | ︙ | |||
172 173 174 175 176 177 178 | endif #### The directories where the OpenSSL include and library files are located. # The recommended usage here is to use the Sysinternals junction tool # to create a hard link between an "openssl-1.x" sub-directory of the # Fossil source code directory and the target OpenSSL source directory. # | | | 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | endif #### The directories where the OpenSSL include and library files are located. # The recommended usage here is to use the Sysinternals junction tool # to create a hard link between an "openssl-1.x" sub-directory of the # Fossil source code directory and the target OpenSSL source directory. # OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1g OPENSSLINCDIR = $(OPENSSLDIR)/include OPENSSLLIBDIR = $(OPENSSLDIR) #### Either the directory where the Tcl library is installed or the Tcl # source code directory resides (depending on the value of the macro # FOSSIL_TCL_SOURCE). If this points to the Tcl install directory, # this directory must have "include" and "lib" sub-directories. If |
| ︙ | ︙ | |||
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 | # You should not need to change anything below this line #-------------------------------------------------------- XBCC = $(BCC) $(CFLAGS) XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR) SRC = \ $(SRCDIR)/add.c \ $(SRCDIR)/alerts.c \ $(SRCDIR)/allrepo.c \ $(SRCDIR)/attach.c \ $(SRCDIR)/backoffice.c \ $(SRCDIR)/bag.c \ $(SRCDIR)/bisect.c \ $(SRCDIR)/blob.c \ $(SRCDIR)/branch.c \ $(SRCDIR)/browse.c \ $(SRCDIR)/builtin.c \ | > > | 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 | # You should not need to change anything below this line #-------------------------------------------------------- XBCC = $(BCC) $(CFLAGS) XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR) SRC = \ $(SRCDIR)/add.c \ $(SRCDIR)/ajax.c \ $(SRCDIR)/alerts.c \ $(SRCDIR)/allrepo.c \ $(SRCDIR)/attach.c \ $(SRCDIR)/backlink.c \ $(SRCDIR)/backoffice.c \ $(SRCDIR)/bag.c \ $(SRCDIR)/bisect.c \ $(SRCDIR)/blob.c \ $(SRCDIR)/branch.c \ $(SRCDIR)/browse.c \ $(SRCDIR)/builtin.c \ |
| ︙ | ︙ | |||
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 | $(SRCDIR)/diffcmd.c \ $(SRCDIR)/dispatch.c \ $(SRCDIR)/doc.c \ $(SRCDIR)/encode.c \ $(SRCDIR)/etag.c \ $(SRCDIR)/event.c \ $(SRCDIR)/export.c \ $(SRCDIR)/file.c \ $(SRCDIR)/finfo.c \ $(SRCDIR)/foci.c \ $(SRCDIR)/forum.c \ $(SRCDIR)/fshell.c \ $(SRCDIR)/fusefs.c \ $(SRCDIR)/glob.c \ $(SRCDIR)/graph.c \ $(SRCDIR)/gzip.c \ $(SRCDIR)/hname.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ $(SRCDIR)/http_ssl.c \ | > > > | 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 | $(SRCDIR)/diffcmd.c \ $(SRCDIR)/dispatch.c \ $(SRCDIR)/doc.c \ $(SRCDIR)/encode.c \ $(SRCDIR)/etag.c \ $(SRCDIR)/event.c \ $(SRCDIR)/export.c \ $(SRCDIR)/extcgi.c \ $(SRCDIR)/file.c \ $(SRCDIR)/fileedit.c \ $(SRCDIR)/finfo.c \ $(SRCDIR)/foci.c \ $(SRCDIR)/forum.c \ $(SRCDIR)/fshell.c \ $(SRCDIR)/fusefs.c \ $(SRCDIR)/fuzz.c \ $(SRCDIR)/glob.c \ $(SRCDIR)/graph.c \ $(SRCDIR)/gzip.c \ $(SRCDIR)/hname.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ $(SRCDIR)/http_ssl.c \ |
| ︙ | ︙ | |||
548 549 550 551 552 553 554 555 556 557 558 559 560 561 | $(SRCDIR)/stash.c \ $(SRCDIR)/stat.c \ $(SRCDIR)/statrep.c \ $(SRCDIR)/style.c \ $(SRCDIR)/sync.c \ $(SRCDIR)/tag.c \ $(SRCDIR)/tar.c \ $(SRCDIR)/th_main.c \ $(SRCDIR)/timeline.c \ $(SRCDIR)/tkt.c \ $(SRCDIR)/tktsetup.c \ $(SRCDIR)/undo.c \ $(SRCDIR)/unicode.c \ $(SRCDIR)/unversioned.c \ | > | 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 | $(SRCDIR)/stash.c \ $(SRCDIR)/stat.c \ $(SRCDIR)/statrep.c \ $(SRCDIR)/style.c \ $(SRCDIR)/sync.c \ $(SRCDIR)/tag.c \ $(SRCDIR)/tar.c \ $(SRCDIR)/terminal.c \ $(SRCDIR)/th_main.c \ $(SRCDIR)/timeline.c \ $(SRCDIR)/tkt.c \ $(SRCDIR)/tktsetup.c \ $(SRCDIR)/undo.c \ $(SRCDIR)/unicode.c \ $(SRCDIR)/unversioned.c \ |
| ︙ | ︙ | |||
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 | $(SRCDIR)/../skins/rounded1/details.txt \ $(SRCDIR)/../skins/rounded1/footer.txt \ $(SRCDIR)/../skins/rounded1/header.txt \ $(SRCDIR)/../skins/xekri/css.txt \ $(SRCDIR)/../skins/xekri/details.txt \ $(SRCDIR)/../skins/xekri/footer.txt \ $(SRCDIR)/../skins/xekri/header.txt \ $(SRCDIR)/ci_edit.js \ $(SRCDIR)/copybtn.js \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ $(SRCDIR)/graph.js \ $(SRCDIR)/href.js \ $(SRCDIR)/login.js \ $(SRCDIR)/markdown.md \ $(SRCDIR)/menu.js \ $(SRCDIR)/sbsdiff.js \ $(SRCDIR)/scroll.js \ $(SRCDIR)/skin.js \ $(SRCDIR)/sorttable.js \ $(SRCDIR)/tree.js \ $(SRCDIR)/useredit.js \ $(SRCDIR)/wiki.wiki TRANS_SRC = \ $(OBJDIR)/add_.c \ $(OBJDIR)/alerts_.c \ $(OBJDIR)/allrepo_.c \ $(OBJDIR)/attach_.c \ $(OBJDIR)/backoffice_.c \ $(OBJDIR)/bag_.c \ $(OBJDIR)/bisect_.c \ $(OBJDIR)/blob_.c \ $(OBJDIR)/branch_.c \ $(OBJDIR)/browse_.c \ $(OBJDIR)/builtin_.c \ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | $(SRCDIR)/../skins/rounded1/details.txt \ $(SRCDIR)/../skins/rounded1/footer.txt \ $(SRCDIR)/../skins/rounded1/header.txt \ $(SRCDIR)/../skins/xekri/css.txt \ $(SRCDIR)/../skins/xekri/details.txt \ $(SRCDIR)/../skins/xekri/footer.txt \ $(SRCDIR)/../skins/xekri/header.txt \ $(SRCDIR)/accordion.js \ $(SRCDIR)/ci_edit.js \ $(SRCDIR)/copybtn.js \ $(SRCDIR)/default.css \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ $(SRCDIR)/fossil.bootstrap.js \ $(SRCDIR)/fossil.confirmer.js \ $(SRCDIR)/fossil.dom.js \ $(SRCDIR)/fossil.fetch.js \ $(SRCDIR)/fossil.page.fileedit.js \ $(SRCDIR)/fossil.storage.js \ $(SRCDIR)/fossil.tabs.js \ $(SRCDIR)/graph.js \ $(SRCDIR)/href.js \ $(SRCDIR)/login.js \ $(SRCDIR)/markdown.md \ $(SRCDIR)/menu.js \ $(SRCDIR)/sbsdiff.js \ $(SRCDIR)/scroll.js \ $(SRCDIR)/skin.js \ $(SRCDIR)/sorttable.js \ $(SRCDIR)/sounds/0.wav \ $(SRCDIR)/sounds/1.wav \ $(SRCDIR)/sounds/2.wav \ $(SRCDIR)/sounds/3.wav \ $(SRCDIR)/sounds/4.wav \ $(SRCDIR)/sounds/5.wav \ $(SRCDIR)/sounds/6.wav \ $(SRCDIR)/sounds/7.wav \ $(SRCDIR)/sounds/8.wav \ $(SRCDIR)/sounds/9.wav \ $(SRCDIR)/sounds/a.wav \ $(SRCDIR)/sounds/b.wav \ $(SRCDIR)/sounds/c.wav \ $(SRCDIR)/sounds/d.wav \ $(SRCDIR)/sounds/e.wav \ $(SRCDIR)/sounds/f.wav \ $(SRCDIR)/style.admin_log.css \ $(SRCDIR)/style.fileedit.css \ $(SRCDIR)/tree.js \ $(SRCDIR)/useredit.js \ $(SRCDIR)/wiki.wiki TRANS_SRC = \ $(OBJDIR)/add_.c \ $(OBJDIR)/ajax_.c \ $(OBJDIR)/alerts_.c \ $(OBJDIR)/allrepo_.c \ $(OBJDIR)/attach_.c \ $(OBJDIR)/backlink_.c \ $(OBJDIR)/backoffice_.c \ $(OBJDIR)/bag_.c \ $(OBJDIR)/bisect_.c \ $(OBJDIR)/blob_.c \ $(OBJDIR)/branch_.c \ $(OBJDIR)/browse_.c \ $(OBJDIR)/builtin_.c \ |
| ︙ | ︙ | |||
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 | $(OBJDIR)/diffcmd_.c \ $(OBJDIR)/dispatch_.c \ $(OBJDIR)/doc_.c \ $(OBJDIR)/encode_.c \ $(OBJDIR)/etag_.c \ $(OBJDIR)/event_.c \ $(OBJDIR)/export_.c \ $(OBJDIR)/file_.c \ $(OBJDIR)/finfo_.c \ $(OBJDIR)/foci_.c \ $(OBJDIR)/forum_.c \ $(OBJDIR)/fshell_.c \ $(OBJDIR)/fusefs_.c \ $(OBJDIR)/glob_.c \ $(OBJDIR)/graph_.c \ $(OBJDIR)/gzip_.c \ $(OBJDIR)/hname_.c \ $(OBJDIR)/http_.c \ $(OBJDIR)/http_socket_.c \ $(OBJDIR)/http_ssl_.c \ | > > > | 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 | $(OBJDIR)/diffcmd_.c \ $(OBJDIR)/dispatch_.c \ $(OBJDIR)/doc_.c \ $(OBJDIR)/encode_.c \ $(OBJDIR)/etag_.c \ $(OBJDIR)/event_.c \ $(OBJDIR)/export_.c \ $(OBJDIR)/extcgi_.c \ $(OBJDIR)/file_.c \ $(OBJDIR)/fileedit_.c \ $(OBJDIR)/finfo_.c \ $(OBJDIR)/foci_.c \ $(OBJDIR)/forum_.c \ $(OBJDIR)/fshell_.c \ $(OBJDIR)/fusefs_.c \ $(OBJDIR)/fuzz_.c \ $(OBJDIR)/glob_.c \ $(OBJDIR)/graph_.c \ $(OBJDIR)/gzip_.c \ $(OBJDIR)/hname_.c \ $(OBJDIR)/http_.c \ $(OBJDIR)/http_socket_.c \ $(OBJDIR)/http_ssl_.c \ |
| ︙ | ︙ | |||
761 762 763 764 765 766 767 768 769 770 771 772 773 774 | $(OBJDIR)/stash_.c \ $(OBJDIR)/stat_.c \ $(OBJDIR)/statrep_.c \ $(OBJDIR)/style_.c \ $(OBJDIR)/sync_.c \ $(OBJDIR)/tag_.c \ $(OBJDIR)/tar_.c \ $(OBJDIR)/th_main_.c \ $(OBJDIR)/timeline_.c \ $(OBJDIR)/tkt_.c \ $(OBJDIR)/tktsetup_.c \ $(OBJDIR)/undo_.c \ $(OBJDIR)/unicode_.c \ $(OBJDIR)/unversioned_.c \ | > | 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 | $(OBJDIR)/stash_.c \ $(OBJDIR)/stat_.c \ $(OBJDIR)/statrep_.c \ $(OBJDIR)/style_.c \ $(OBJDIR)/sync_.c \ $(OBJDIR)/tag_.c \ $(OBJDIR)/tar_.c \ $(OBJDIR)/terminal_.c \ $(OBJDIR)/th_main_.c \ $(OBJDIR)/timeline_.c \ $(OBJDIR)/tkt_.c \ $(OBJDIR)/tktsetup_.c \ $(OBJDIR)/undo_.c \ $(OBJDIR)/unicode_.c \ $(OBJDIR)/unversioned_.c \ |
| ︙ | ︙ | |||
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 | $(OBJDIR)/wysiwyg_.c \ $(OBJDIR)/xfer_.c \ $(OBJDIR)/xfersetup_.c \ $(OBJDIR)/zip_.c OBJ = \ $(OBJDIR)/add.o \ $(OBJDIR)/alerts.o \ $(OBJDIR)/allrepo.o \ $(OBJDIR)/attach.o \ $(OBJDIR)/backoffice.o \ $(OBJDIR)/bag.o \ $(OBJDIR)/bisect.o \ $(OBJDIR)/blob.o \ $(OBJDIR)/branch.o \ $(OBJDIR)/browse.o \ $(OBJDIR)/builtin.o \ | > > | 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 | $(OBJDIR)/wysiwyg_.c \ $(OBJDIR)/xfer_.c \ $(OBJDIR)/xfersetup_.c \ $(OBJDIR)/zip_.c OBJ = \ $(OBJDIR)/add.o \ $(OBJDIR)/ajax.o \ $(OBJDIR)/alerts.o \ $(OBJDIR)/allrepo.o \ $(OBJDIR)/attach.o \ $(OBJDIR)/backlink.o \ $(OBJDIR)/backoffice.o \ $(OBJDIR)/bag.o \ $(OBJDIR)/bisect.o \ $(OBJDIR)/blob.o \ $(OBJDIR)/branch.o \ $(OBJDIR)/browse.o \ $(OBJDIR)/builtin.o \ |
| ︙ | ︙ | |||
823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 | $(OBJDIR)/diffcmd.o \ $(OBJDIR)/dispatch.o \ $(OBJDIR)/doc.o \ $(OBJDIR)/encode.o \ $(OBJDIR)/etag.o \ $(OBJDIR)/event.o \ $(OBJDIR)/export.o \ $(OBJDIR)/file.o \ $(OBJDIR)/finfo.o \ $(OBJDIR)/foci.o \ $(OBJDIR)/forum.o \ $(OBJDIR)/fshell.o \ $(OBJDIR)/fusefs.o \ $(OBJDIR)/glob.o \ $(OBJDIR)/graph.o \ $(OBJDIR)/gzip.o \ $(OBJDIR)/hname.o \ $(OBJDIR)/http.o \ $(OBJDIR)/http_socket.o \ $(OBJDIR)/http_ssl.o \ | > > > | 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 | $(OBJDIR)/diffcmd.o \ $(OBJDIR)/dispatch.o \ $(OBJDIR)/doc.o \ $(OBJDIR)/encode.o \ $(OBJDIR)/etag.o \ $(OBJDIR)/event.o \ $(OBJDIR)/export.o \ $(OBJDIR)/extcgi.o \ $(OBJDIR)/file.o \ $(OBJDIR)/fileedit.o \ $(OBJDIR)/finfo.o \ $(OBJDIR)/foci.o \ $(OBJDIR)/forum.o \ $(OBJDIR)/fshell.o \ $(OBJDIR)/fusefs.o \ $(OBJDIR)/fuzz.o \ $(OBJDIR)/glob.o \ $(OBJDIR)/graph.o \ $(OBJDIR)/gzip.o \ $(OBJDIR)/hname.o \ $(OBJDIR)/http.o \ $(OBJDIR)/http_socket.o \ $(OBJDIR)/http_ssl.o \ |
| ︙ | ︙ | |||
900 901 902 903 904 905 906 907 908 909 910 911 912 913 | $(OBJDIR)/stash.o \ $(OBJDIR)/stat.o \ $(OBJDIR)/statrep.o \ $(OBJDIR)/style.o \ $(OBJDIR)/sync.o \ $(OBJDIR)/tag.o \ $(OBJDIR)/tar.o \ $(OBJDIR)/th_main.o \ $(OBJDIR)/timeline.o \ $(OBJDIR)/tkt.o \ $(OBJDIR)/tktsetup.o \ $(OBJDIR)/undo.o \ $(OBJDIR)/unicode.o \ $(OBJDIR)/unversioned.o \ | > | 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 | $(OBJDIR)/stash.o \ $(OBJDIR)/stat.o \ $(OBJDIR)/statrep.o \ $(OBJDIR)/style.o \ $(OBJDIR)/sync.o \ $(OBJDIR)/tag.o \ $(OBJDIR)/tar.o \ $(OBJDIR)/terminal.o \ $(OBJDIR)/th_main.o \ $(OBJDIR)/timeline.o \ $(OBJDIR)/tkt.o \ $(OBJDIR)/tktsetup.o \ $(OBJDIR)/undo.o \ $(OBJDIR)/unicode.o \ $(OBJDIR)/unversioned.o \ |
| ︙ | ︙ | |||
940 941 942 943 944 945 946 | # ifdef USE_WINDOWS TRANSLATE = $(subst /,\,$(OBJDIR)/translate.exe) MAKEHEADERS = $(subst /,\,$(OBJDIR)/makeheaders.exe) MKINDEX = $(subst /,\,$(OBJDIR)/mkindex.exe) MKBUILTIN = $(subst /,\,$(OBJDIR)/mkbuiltin.exe) MKVERSION = $(subst /,\,$(OBJDIR)/mkversion.exe) | < < | | 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 | # ifdef USE_WINDOWS TRANSLATE = $(subst /,\,$(OBJDIR)/translate.exe) MAKEHEADERS = $(subst /,\,$(OBJDIR)/makeheaders.exe) MKINDEX = $(subst /,\,$(OBJDIR)/mkindex.exe) MKBUILTIN = $(subst /,\,$(OBJDIR)/mkbuiltin.exe) MKVERSION = $(subst /,\,$(OBJDIR)/mkversion.exe) CODECHECK1 = $(subst /,\,$(OBJDIR)/codecheck1.exe) CAT = type CP = copy GREP = find MV = copy RM = del /Q MKDIR = -mkdir RMDIR = rmdir /S /Q else TRANSLATE = $(OBJDIR)/translate.exe MAKEHEADERS = $(OBJDIR)/makeheaders.exe MKINDEX = $(OBJDIR)/mkindex.exe MKBUILTIN = $(OBJDIR)/mkbuiltin.exe MKVERSION = $(OBJDIR)/mkversion.exe CODECHECK1 = $(OBJDIR)/codecheck1.exe CAT = cat CP = cp GREP = grep MV = mv RM = rm -f MKDIR = -mkdir -p RMDIR = rm -rf endif all: $(OBJDIR) $(APPNAME) $(OBJDIR)/fossil.o: $(SRCDIR)/../win/fossil.rc $(OBJDIR)/VERSION.h ifdef USE_WINDOWS $(CAT) $(subst /,\,$(SRCDIR)\miniz.c) | $(GREP) "define MZ_VERSION" > $(subst /,\,$(OBJDIR)\minizver.h) $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.rc) $(subst /,\,$(OBJDIR)) $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.ico) $(subst /,\,$(OBJDIR)) $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.exe.manifest) $(subst /,\,$(OBJDIR)) else $(CAT) $(SRCDIR)/miniz.c | $(GREP) "define MZ_VERSION" > $(OBJDIR)/minizver.h |
| ︙ | ︙ | |||
1013 1014 1015 1016 1017 1018 1019 | $(MKBUILTIN): $(SRCDIR)/mkbuiltin.c $(XBCC) -o $@ $(SRCDIR)/mkbuiltin.c $(MKVERSION): $(SRCDIR)/mkversion.c $(XBCC) -o $@ $(SRCDIR)/mkversion.c | < < < < < < | 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 | $(MKBUILTIN): $(SRCDIR)/mkbuiltin.c $(XBCC) -o $@ $(SRCDIR)/mkbuiltin.c $(MKVERSION): $(SRCDIR)/mkversion.c $(XBCC) -o $@ $(SRCDIR)/mkversion.c $(CODECHECK1): $(SRCDIR)/codecheck1.c $(XBCC) -o $@ $(SRCDIR)/codecheck1.c # WARNING. DANGER. Running the test suite 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: $(OBJDIR) $(APPNAME) $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(MKVERSION) $(MKVERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$@ # The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set # to 1. If it is set to 1, then there is no need to build or link # the sqlite3.o object. Instead, the system SQLite will be linked # using -lsqlite3. SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o SQLITE3_OBJ.1 = SQLITE3_OBJ. = $(SQLITE3_OBJ.0) |
| ︙ | ︙ | |||
1144 1145 1146 1147 1148 1149 1150 | $(OBJDIR)/page_index.h: $(TRANS_SRC) $(MKINDEX) $(MKINDEX) $(TRANS_SRC) >$@ $(OBJDIR)/builtin_data.h: $(MKBUILTIN) $(EXTRA_FILES) $(MKBUILTIN) --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ | | > > | 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 | $(OBJDIR)/page_index.h: $(TRANS_SRC) $(MKINDEX) $(MKINDEX) $(TRANS_SRC) >$@ $(OBJDIR)/builtin_data.h: $(MKBUILTIN) $(EXTRA_FILES) $(MKBUILTIN) --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ $(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(MAKEHEADERS) $(OBJDIR)/VERSION.h $(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h \ $(OBJDIR)/ajax_.c:$(OBJDIR)/ajax.h \ $(OBJDIR)/alerts_.c:$(OBJDIR)/alerts.h \ $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \ $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \ $(OBJDIR)/backlink_.c:$(OBJDIR)/backlink.h \ $(OBJDIR)/backoffice_.c:$(OBJDIR)/backoffice.h \ $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \ $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \ $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \ $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \ $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h \ $(OBJDIR)/builtin_.c:$(OBJDIR)/builtin.h \ |
| ︙ | ︙ | |||
1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 | $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h \ $(OBJDIR)/dispatch_.c:$(OBJDIR)/dispatch.h \ $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \ $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \ $(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \ $(OBJDIR)/event_.c:$(OBJDIR)/event.h \ $(OBJDIR)/export_.c:$(OBJDIR)/export.h \ $(OBJDIR)/file_.c:$(OBJDIR)/file.h \ $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \ $(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \ $(OBJDIR)/forum_.c:$(OBJDIR)/forum.h \ $(OBJDIR)/fshell_.c:$(OBJDIR)/fshell.h \ $(OBJDIR)/fusefs_.c:$(OBJDIR)/fusefs.h \ $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h \ $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h \ $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h \ $(OBJDIR)/hname_.c:$(OBJDIR)/hname.h \ $(OBJDIR)/http_.c:$(OBJDIR)/http.h \ $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \ $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \ | > > > | 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 | $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h \ $(OBJDIR)/dispatch_.c:$(OBJDIR)/dispatch.h \ $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \ $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \ $(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \ $(OBJDIR)/event_.c:$(OBJDIR)/event.h \ $(OBJDIR)/export_.c:$(OBJDIR)/export.h \ $(OBJDIR)/extcgi_.c:$(OBJDIR)/extcgi.h \ $(OBJDIR)/file_.c:$(OBJDIR)/file.h \ $(OBJDIR)/fileedit_.c:$(OBJDIR)/fileedit.h \ $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \ $(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \ $(OBJDIR)/forum_.c:$(OBJDIR)/forum.h \ $(OBJDIR)/fshell_.c:$(OBJDIR)/fshell.h \ $(OBJDIR)/fusefs_.c:$(OBJDIR)/fusefs.h \ $(OBJDIR)/fuzz_.c:$(OBJDIR)/fuzz.h \ $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h \ $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h \ $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h \ $(OBJDIR)/hname_.c:$(OBJDIR)/hname.h \ $(OBJDIR)/http_.c:$(OBJDIR)/http.h \ $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \ $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \ |
| ︙ | ︙ | |||
1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 | $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \ $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \ $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \ $(OBJDIR)/style_.c:$(OBJDIR)/style.h \ $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \ $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h \ $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h \ $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h \ $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h \ $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h \ $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h \ $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h \ $(OBJDIR)/unicode_.c:$(OBJDIR)/unicode.h \ $(OBJDIR)/unversioned_.c:$(OBJDIR)/unversioned.h \ | > | 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 | $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \ $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \ $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \ $(OBJDIR)/style_.c:$(OBJDIR)/style.h \ $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \ $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h \ $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h \ $(OBJDIR)/terminal_.c:$(OBJDIR)/terminal.h \ $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h \ $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h \ $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h \ $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h \ $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h \ $(OBJDIR)/unicode_.c:$(OBJDIR)/unicode.h \ $(OBJDIR)/unversioned_.c:$(OBJDIR)/unversioned.h \ |
| ︙ | ︙ | |||
1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 | $(OBJDIR)/add_.c: $(SRCDIR)/add.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/add.c >$@ $(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c $(OBJDIR)/add.h: $(OBJDIR)/headers $(OBJDIR)/alerts_.c: $(SRCDIR)/alerts.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/alerts.c >$@ $(OBJDIR)/alerts.o: $(OBJDIR)/alerts_.c $(OBJDIR)/alerts.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/alerts.o -c $(OBJDIR)/alerts_.c | > > > > > > > > | 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 | $(OBJDIR)/add_.c: $(SRCDIR)/add.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/add.c >$@ $(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c $(OBJDIR)/add.h: $(OBJDIR)/headers $(OBJDIR)/ajax_.c: $(SRCDIR)/ajax.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/ajax.c >$@ $(OBJDIR)/ajax.o: $(OBJDIR)/ajax_.c $(OBJDIR)/ajax.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/ajax.o -c $(OBJDIR)/ajax_.c $(OBJDIR)/ajax.h: $(OBJDIR)/headers $(OBJDIR)/alerts_.c: $(SRCDIR)/alerts.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/alerts.c >$@ $(OBJDIR)/alerts.o: $(OBJDIR)/alerts_.c $(OBJDIR)/alerts.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/alerts.o -c $(OBJDIR)/alerts_.c |
| ︙ | ︙ | |||
1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 | $(OBJDIR)/attach_.c: $(SRCDIR)/attach.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/attach.c >$@ $(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c $(OBJDIR)/attach.h: $(OBJDIR)/headers $(OBJDIR)/backoffice_.c: $(SRCDIR)/backoffice.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/backoffice.c >$@ $(OBJDIR)/backoffice.o: $(OBJDIR)/backoffice_.c $(OBJDIR)/backoffice.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/backoffice.o -c $(OBJDIR)/backoffice_.c | > > > > > > > > | 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 | $(OBJDIR)/attach_.c: $(SRCDIR)/attach.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/attach.c >$@ $(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c $(OBJDIR)/attach.h: $(OBJDIR)/headers $(OBJDIR)/backlink_.c: $(SRCDIR)/backlink.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/backlink.c >$@ $(OBJDIR)/backlink.o: $(OBJDIR)/backlink_.c $(OBJDIR)/backlink.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/backlink.o -c $(OBJDIR)/backlink_.c $(OBJDIR)/backlink.h: $(OBJDIR)/headers $(OBJDIR)/backoffice_.c: $(SRCDIR)/backoffice.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/backoffice.c >$@ $(OBJDIR)/backoffice.o: $(OBJDIR)/backoffice_.c $(OBJDIR)/backoffice.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/backoffice.o -c $(OBJDIR)/backoffice_.c |
| ︙ | ︙ | |||
1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 | $(OBJDIR)/export_.c: $(SRCDIR)/export.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/export.c >$@ $(OBJDIR)/export.o: $(OBJDIR)/export_.c $(OBJDIR)/export.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/export.o -c $(OBJDIR)/export_.c $(OBJDIR)/export.h: $(OBJDIR)/headers $(OBJDIR)/file_.c: $(SRCDIR)/file.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/file.c >$@ $(OBJDIR)/file.o: $(OBJDIR)/file_.c $(OBJDIR)/file.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/file.o -c $(OBJDIR)/file_.c $(OBJDIR)/file.h: $(OBJDIR)/headers $(OBJDIR)/finfo_.c: $(SRCDIR)/finfo.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/finfo.c >$@ $(OBJDIR)/finfo.o: $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/finfo.o -c $(OBJDIR)/finfo_.c | > > > > > > > > > > > > > > > > | 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 | $(OBJDIR)/export_.c: $(SRCDIR)/export.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/export.c >$@ $(OBJDIR)/export.o: $(OBJDIR)/export_.c $(OBJDIR)/export.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/export.o -c $(OBJDIR)/export_.c $(OBJDIR)/export.h: $(OBJDIR)/headers $(OBJDIR)/extcgi_.c: $(SRCDIR)/extcgi.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/extcgi.c >$@ $(OBJDIR)/extcgi.o: $(OBJDIR)/extcgi_.c $(OBJDIR)/extcgi.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/extcgi.o -c $(OBJDIR)/extcgi_.c $(OBJDIR)/extcgi.h: $(OBJDIR)/headers $(OBJDIR)/file_.c: $(SRCDIR)/file.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/file.c >$@ $(OBJDIR)/file.o: $(OBJDIR)/file_.c $(OBJDIR)/file.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/file.o -c $(OBJDIR)/file_.c $(OBJDIR)/file.h: $(OBJDIR)/headers $(OBJDIR)/fileedit_.c: $(SRCDIR)/fileedit.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/fileedit.c >$@ $(OBJDIR)/fileedit.o: $(OBJDIR)/fileedit_.c $(OBJDIR)/fileedit.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/fileedit.o -c $(OBJDIR)/fileedit_.c $(OBJDIR)/fileedit.h: $(OBJDIR)/headers $(OBJDIR)/finfo_.c: $(SRCDIR)/finfo.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/finfo.c >$@ $(OBJDIR)/finfo.o: $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/finfo.o -c $(OBJDIR)/finfo_.c |
| ︙ | ︙ | |||
1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 | $(OBJDIR)/fusefs_.c: $(SRCDIR)/fusefs.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/fusefs.c >$@ $(OBJDIR)/fusefs.o: $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/fusefs.o -c $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h: $(OBJDIR)/headers $(OBJDIR)/glob_.c: $(SRCDIR)/glob.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/glob.c >$@ $(OBJDIR)/glob.o: $(OBJDIR)/glob_.c $(OBJDIR)/glob.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/glob.o -c $(OBJDIR)/glob_.c | > > > > > > > > | 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 | $(OBJDIR)/fusefs_.c: $(SRCDIR)/fusefs.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/fusefs.c >$@ $(OBJDIR)/fusefs.o: $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/fusefs.o -c $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h: $(OBJDIR)/headers $(OBJDIR)/fuzz_.c: $(SRCDIR)/fuzz.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/fuzz.c >$@ $(OBJDIR)/fuzz.o: $(OBJDIR)/fuzz_.c $(OBJDIR)/fuzz.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/fuzz.o -c $(OBJDIR)/fuzz_.c $(OBJDIR)/fuzz.h: $(OBJDIR)/headers $(OBJDIR)/glob_.c: $(SRCDIR)/glob.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/glob.c >$@ $(OBJDIR)/glob.o: $(OBJDIR)/glob_.c $(OBJDIR)/glob.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/glob.o -c $(OBJDIR)/glob_.c |
| ︙ | ︙ | |||
2174 2175 2176 2177 2178 2179 2180 | $(XTCC) -o $(OBJDIR)/statrep.o -c $(OBJDIR)/statrep_.c $(OBJDIR)/statrep.h: $(OBJDIR)/headers $(OBJDIR)/style_.c: $(SRCDIR)/style.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/style.c >$@ | | | 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 | $(XTCC) -o $(OBJDIR)/statrep.o -c $(OBJDIR)/statrep_.c $(OBJDIR)/statrep.h: $(OBJDIR)/headers $(OBJDIR)/style_.c: $(SRCDIR)/style.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/style.c >$@ $(OBJDIR)/style.o: $(OBJDIR)/style_.c $(OBJDIR)/style.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/style.o -c $(OBJDIR)/style_.c $(OBJDIR)/style.h: $(OBJDIR)/headers $(OBJDIR)/sync_.c: $(SRCDIR)/sync.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/sync.c >$@ |
| ︙ | ︙ | |||
2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 | $(OBJDIR)/tar_.c: $(SRCDIR)/tar.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/tar.c >$@ $(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tar.o -c $(OBJDIR)/tar_.c $(OBJDIR)/tar.h: $(OBJDIR)/headers $(OBJDIR)/th_main_.c: $(SRCDIR)/th_main.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/th_main.c >$@ $(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/th_main.o -c $(OBJDIR)/th_main_.c | > > > > > > > > | 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 | $(OBJDIR)/tar_.c: $(SRCDIR)/tar.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/tar.c >$@ $(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tar.o -c $(OBJDIR)/tar_.c $(OBJDIR)/tar.h: $(OBJDIR)/headers $(OBJDIR)/terminal_.c: $(SRCDIR)/terminal.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/terminal.c >$@ $(OBJDIR)/terminal.o: $(OBJDIR)/terminal_.c $(OBJDIR)/terminal.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/terminal.o -c $(OBJDIR)/terminal_.c $(OBJDIR)/terminal.h: $(OBJDIR)/headers $(OBJDIR)/th_main_.c: $(SRCDIR)/th_main.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/th_main.c >$@ $(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/th_main.o -c $(OBJDIR)/th_main_.c |
| ︙ | ︙ | |||
2390 2391 2392 2393 2394 2395 2396 | $(XTCC) -o $(OBJDIR)/zip.o -c $(OBJDIR)/zip_.c $(OBJDIR)/zip.h: $(OBJDIR)/headers MINGW_OPTIONS = -D_HAVE__MINGW_H SQLITE_OPTIONS = -DNDEBUG=1 \ | | | < > | | < > | 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 |
$(XTCC) -o $(OBJDIR)/zip.o -c $(OBJDIR)/zip_.c
$(OBJDIR)/zip.h: $(OBJDIR)/headers
MINGW_OPTIONS = -D_HAVE__MINGW_H
SQLITE_OPTIONS = -DNDEBUG=1 \
-DSQLITE_DQS=0 \
-DSQLITE_THREADSAFE=0 \
-DSQLITE_DEFAULT_MEMSTATUS=0 \
-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \
-DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
-DSQLITE_OMIT_DECLTYPE \
-DSQLITE_OMIT_DEPRECATED \
-DSQLITE_OMIT_PROGRESS_CALLBACK \
-DSQLITE_OMIT_SHARED_CACHE \
-DSQLITE_OMIT_LOAD_EXTENSION \
-DSQLITE_MAX_EXPR_DEPTH=0 \
-DSQLITE_USE_ALLOCA \
-DSQLITE_ENABLE_LOCKING_STYLE=0 \
-DSQLITE_DEFAULT_FILE_FORMAT=4 \
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
-DSQLITE_ENABLE_FTS4 \
-DSQLITE_ENABLE_DBSTAT_VTAB \
-DSQLITE_ENABLE_JSON1 \
-DSQLITE_ENABLE_FTS5 \
-DSQLITE_ENABLE_STMTVTAB \
-DSQLITE_HAVE_ZLIB \
-DSQLITE_INTROSPECTION_PRAGMAS \
-DSQLITE_ENABLE_DBPAGE_VTAB \
-DSQLITE_TRUSTED_SCHEMA=0 \
-DSQLITE_WIN32_NO_ANSI \
$(MINGW_OPTIONS) \
-DSQLITE_USE_MALLOC_H \
-DSQLITE_USE_MSIZE
SHELL_OPTIONS = -DNDEBUG=1 \
-DSQLITE_DQS=0 \
-DSQLITE_THREADSAFE=0 \
-DSQLITE_DEFAULT_MEMSTATUS=0 \
-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \
-DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
-DSQLITE_OMIT_DECLTYPE \
-DSQLITE_OMIT_DEPRECATED \
-DSQLITE_OMIT_PROGRESS_CALLBACK \
-DSQLITE_OMIT_SHARED_CACHE \
-DSQLITE_OMIT_LOAD_EXTENSION \
-DSQLITE_MAX_EXPR_DEPTH=0 \
-DSQLITE_USE_ALLOCA \
-DSQLITE_ENABLE_LOCKING_STYLE=0 \
-DSQLITE_DEFAULT_FILE_FORMAT=4 \
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
-DSQLITE_ENABLE_FTS4 \
-DSQLITE_ENABLE_DBSTAT_VTAB \
-DSQLITE_ENABLE_JSON1 \
-DSQLITE_ENABLE_FTS5 \
-DSQLITE_ENABLE_STMTVTAB \
-DSQLITE_HAVE_ZLIB \
-DSQLITE_INTROSPECTION_PRAGMAS \
-DSQLITE_ENABLE_DBPAGE_VTAB \
-DSQLITE_TRUSTED_SCHEMA=0 \
-Dmain=sqlite3_shell \
-DSQLITE_SHELL_IS_UTF8=1 \
-DSQLITE_OMIT_LOAD_EXTENSION=1 \
-DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \
-DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname \
-DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc \
-Daccess=file_access \
|
| ︙ | ︙ |
1 2 3 4 5 | # ############################################################################## # WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.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 |
#
##############################################################################
# WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl")
##############################################################################
#
#
# This file is automatically generated. Instead of editing this
# file, edit "makemake.tcl" then run "tclsh makemake.tcl"
# to regenerate this file.
#
B = ..
SRCDIR = $(B)\src
T = .
OBJDIR = $(T)
OX = $(OBJDIR)
O = .obj
E = .exe
P = .pdb
INSTALLDIR = .
!ifdef DESTDIR
INSTALLDIR = $(DESTDIR)\$(INSTALLDIR)
!endif
# When building out of source, this Makefile needs to know the path to the base
# top-level directory for this project. Pass it on NMAKE command line via make
# variable B:
# NMAKE /f "path\to\this\Makefile" B="path/to/fossil/root"
#
# NOTE: Make sure B path has no trailing backslash, UNIX-style path is OK too.
#
!if !exist("$(B)\.fossil-settings")
!error Please specify path to project base directory: B="path/to/fossil"
!endif
# Perl is only necessary if OpenSSL support is enabled and it is built from
# source code. The PERLDIR environment variable, if it exists, should point
# to the directory containing the main Perl executable specified here (i.e.
# "perl.exe").
PERL = perl.exe
# Enable debugging symbols?
!ifndef DEBUG
DEBUG = 0
!endif
!ifdef FOSSIL_DEBUG
DEBUG = 1
!endif
# Build the OpenSSL libraries?
!ifndef FOSSIL_BUILD_SSL
FOSSIL_BUILD_SSL = 0
!endif
|
| ︙ | ︙ | |||
96 97 98 99 100 101 102 | # Enable support for the SQLite Encryption Extension? !ifndef USE_SEE USE_SEE = 0 !endif !if $(FOSSIL_ENABLE_SSL)!=0 | | | 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | # Enable support for the SQLite Encryption Extension? !ifndef USE_SEE USE_SEE = 0 !endif !if $(FOSSIL_ENABLE_SSL)!=0 SSLDIR = $(B)\compat\openssl-1.1.1g SSLINCDIR = $(SSLDIR)\include !if $(FOSSIL_DYNAMIC_BUILD)!=0 SSLLIBDIR = $(SSLDIR) !else SSLLIBDIR = $(SSLDIR) !endif SSLLFLAGS = /nologo /opt:ref /debug |
| ︙ | ︙ | |||
148 149 150 151 152 153 154 | !if $(FOSSIL_DYNAMIC_BUILD)!=0 ZLIB = zdll.lib !else ZLIB = zlib.lib !endif | | | | | > > > | 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 | !if $(FOSSIL_DYNAMIC_BUILD)!=0 ZLIB = zdll.lib !else ZLIB = zlib.lib !endif INCL = /I. /I"$(OX)" /I"$(SRCDIR)" /I"$(B)\win\include" !if $(FOSSIL_ENABLE_MINIZ)==0 INCL = $(INCL) /I"$(ZINCDIR)" !endif !if $(FOSSIL_ENABLE_SSL)!=0 INCL = $(INCL) /I"$(SSLINCDIR)" !endif !if $(FOSSIL_ENABLE_TCL)!=0 INCL = $(INCL) /I"$(TCLINCDIR)" !endif CFLAGS = /nologo LDFLAGS = CFLAGS = $(CFLAGS) /D_CRT_SECURE_NO_DEPRECATE /D_CRT_SECURE_NO_WARNINGS CFLAGS = $(CFLAGS) /D_CRT_NONSTDC_NO_DEPRECATE /D_CRT_NONSTDC_NO_WARNINGS !if $(FOSSIL_DYNAMIC_BUILD)!=0 LDFLAGS = $(LDFLAGS) /MANIFEST !else LDFLAGS = $(LDFLAGS) /NODEFAULTLIB:msvcrt /MANIFEST:NO !endif !if $(FOSSIL_ENABLE_WINXP)!=0 |
| ︙ | ︙ | |||
197 198 199 200 201 202 203 | CRTFLAGS = /MTd !else CRTFLAGS = /MT !endif !endif !if $(DEBUG)!=0 | | | | | 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 | CRTFLAGS = /MTd !else CRTFLAGS = /MT !endif !endif !if $(DEBUG)!=0 CFLAGS = $(CFLAGS) /Zi $(CRTFLAGS) /Od /DFOSSIL_DEBUG LDFLAGS = $(LDFLAGS) /DEBUG !else CFLAGS = $(CFLAGS) $(CRTFLAGS) /O2 !endif BCC = $(CC) $(CFLAGS) TCC = $(CC) /c $(CFLAGS) $(MSCDEF) $(INCL) RCC = $(RC) /D_WIN32 /D_MSC_VER $(MSCDEF) $(INCL) MTC = mt LIBS = ws2_32.lib advapi32.lib dnsapi.lib LIBDIR = !if $(FOSSIL_DYNAMIC_BUILD)!=0 TCC = $(TCC) /DFOSSIL_DYNAMIC_BUILD=1 RCC = $(RCC) /DFOSSIL_DYNAMIC_BUILD=1 !endif !if $(FOSSIL_ENABLE_MINIZ)==0 LIBS = $(LIBS) $(ZLIB) LIBDIR = $(LIBDIR) /LIBPATH:"$(ZLIBDIR)" !endif !if $(FOSSIL_ENABLE_MINIZ)!=0 TCC = $(TCC) /DFOSSIL_ENABLE_MINIZ=1 RCC = $(RCC) /DFOSSIL_ENABLE_MINIZ=1 !endif !if $(FOSSIL_ENABLE_JSON)!=0 TCC = $(TCC) /DFOSSIL_ENABLE_JSON=1 RCC = $(RCC) /DFOSSIL_ENABLE_JSON=1 !endif !if $(FOSSIL_ENABLE_SSL)!=0 TCC = $(TCC) /DFOSSIL_ENABLE_SSL=1 RCC = $(RCC) /DFOSSIL_ENABLE_SSL=1 LIBS = $(LIBS) $(SSLLIB) LIBDIR = $(LIBDIR) /LIBPATH:"$(SSLLIBDIR)" !endif !if $(FOSSIL_ENABLE_EXEC_REL_PATHS)!=0 TCC = $(TCC) /DFOSSIL_ENABLE_EXEC_REL_PATHS=1 RCC = $(RCC) /DFOSSIL_ENABLE_EXEC_REL_PATHS=1 !endif |
| ︙ | ︙ | |||
281 282 283 284 285 286 287 |
/DSQLITE_DQS=0 \
/DSQLITE_THREADSAFE=0 \
/DSQLITE_DEFAULT_MEMSTATUS=0 \
/DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \
/DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
/DSQLITE_OMIT_DECLTYPE \
/DSQLITE_OMIT_DEPRECATED \
| < > < > | > | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > | | | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > | | > | | > > > > > > > | | | | | | | | | > > > > > > > > > > > > > > > > > > | | | | < < < < < < < < < < < > > > > > > > > > > > > > | | | | | | | | | | | | | | | | | | | | | | | | | | | | > | | | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > > | > > > > > > > > > > | | > > < > > > | < | | | | | | > | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > | | | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | | < < < | | | | | | | | | | < < < | | | | | | | | | | | | | | | | | < < | < | | | | > | | | | | | | | > | > > > > > > > > > > > > > > > > > < < < < < < < < < < < < < < < < | | | | | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > | | | | | | | | < | | | | > | | | | | | | | | | | | | | | | | > > > > > | | | | | | > > > > > > | | | | | | | | | | | | | | | | < < < > > | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > > | | | | | | | | | | | | | | | | | | | | > > > > > | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > | > > > > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > > | | | | | | | | | | | | | | | | | | | | > > > > > | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > | > > > > | | | > > > > > | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > > | | | | | | | | | | | | | | | | | | | | > > > > > | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > | > > > > | | | | | | | | | | | | | | | | | | | | | | | > > > > > | > | | | | | | | | | | | | | | | | | | | > > | > > > > | > > > > > > | | | | | | | | | | > | | | > | < < < < < < < < < < > > > > > > > > > > | | | | | | | | | | | | | | | | | | | | | | | | > | | | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 |
/DSQLITE_DQS=0 \
/DSQLITE_THREADSAFE=0 \
/DSQLITE_DEFAULT_MEMSTATUS=0 \
/DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \
/DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
/DSQLITE_OMIT_DECLTYPE \
/DSQLITE_OMIT_DEPRECATED \
/DSQLITE_OMIT_PROGRESS_CALLBACK \
/DSQLITE_OMIT_SHARED_CACHE \
/DSQLITE_OMIT_LOAD_EXTENSION \
/DSQLITE_MAX_EXPR_DEPTH=0 \
/DSQLITE_USE_ALLOCA \
/DSQLITE_ENABLE_LOCKING_STYLE=0 \
/DSQLITE_DEFAULT_FILE_FORMAT=4 \
/DSQLITE_ENABLE_EXPLAIN_COMMENTS \
/DSQLITE_ENABLE_FTS4 \
/DSQLITE_ENABLE_DBSTAT_VTAB \
/DSQLITE_ENABLE_JSON1 \
/DSQLITE_ENABLE_FTS5 \
/DSQLITE_ENABLE_STMTVTAB \
/DSQLITE_HAVE_ZLIB \
/DSQLITE_INTROSPECTION_PRAGMAS \
/DSQLITE_ENABLE_DBPAGE_VTAB \
/DSQLITE_TRUSTED_SCHEMA=0 \
/DSQLITE_WIN32_NO_ANSI
SHELL_OPTIONS = /DNDEBUG=1 \
/DSQLITE_DQS=0 \
/DSQLITE_THREADSAFE=0 \
/DSQLITE_DEFAULT_MEMSTATUS=0 \
/DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \
/DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
/DSQLITE_OMIT_DECLTYPE \
/DSQLITE_OMIT_DEPRECATED \
/DSQLITE_OMIT_PROGRESS_CALLBACK \
/DSQLITE_OMIT_SHARED_CACHE \
/DSQLITE_OMIT_LOAD_EXTENSION \
/DSQLITE_MAX_EXPR_DEPTH=0 \
/DSQLITE_USE_ALLOCA \
/DSQLITE_ENABLE_LOCKING_STYLE=0 \
/DSQLITE_DEFAULT_FILE_FORMAT=4 \
/DSQLITE_ENABLE_EXPLAIN_COMMENTS \
/DSQLITE_ENABLE_FTS4 \
/DSQLITE_ENABLE_DBSTAT_VTAB \
/DSQLITE_ENABLE_JSON1 \
/DSQLITE_ENABLE_FTS5 \
/DSQLITE_ENABLE_STMTVTAB \
/DSQLITE_HAVE_ZLIB \
/DSQLITE_INTROSPECTION_PRAGMAS \
/DSQLITE_ENABLE_DBPAGE_VTAB \
/DSQLITE_TRUSTED_SCHEMA=0 \
/Dmain=sqlite3_shell \
/DSQLITE_SHELL_IS_UTF8=1 \
/DSQLITE_OMIT_LOAD_EXTENSION=1 \
/DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \
/DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname \
/DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc \
/Daccess=file_access \
/Dsystem=fossil_system \
/Dgetenv=fossil_getenv \
/Dfopen=fossil_fopen
MINIZ_OPTIONS = /DMINIZ_NO_STDIO \
/DMINIZ_NO_TIME \
/DMINIZ_NO_ARCHIVE_APIS
SRC = "$(OX)\add_.c" \
"$(OX)\ajax_.c" \
"$(OX)\alerts_.c" \
"$(OX)\allrepo_.c" \
"$(OX)\attach_.c" \
"$(OX)\backlink_.c" \
"$(OX)\backoffice_.c" \
"$(OX)\bag_.c" \
"$(OX)\bisect_.c" \
"$(OX)\blob_.c" \
"$(OX)\branch_.c" \
"$(OX)\browse_.c" \
"$(OX)\builtin_.c" \
"$(OX)\bundle_.c" \
"$(OX)\cache_.c" \
"$(OX)\capabilities_.c" \
"$(OX)\captcha_.c" \
"$(OX)\cgi_.c" \
"$(OX)\checkin_.c" \
"$(OX)\checkout_.c" \
"$(OX)\clearsign_.c" \
"$(OX)\clone_.c" \
"$(OX)\comformat_.c" \
"$(OX)\configure_.c" \
"$(OX)\content_.c" \
"$(OX)\cookies_.c" \
"$(OX)\db_.c" \
"$(OX)\delta_.c" \
"$(OX)\deltacmd_.c" \
"$(OX)\deltafunc_.c" \
"$(OX)\descendants_.c" \
"$(OX)\diff_.c" \
"$(OX)\diffcmd_.c" \
"$(OX)\dispatch_.c" \
"$(OX)\doc_.c" \
"$(OX)\encode_.c" \
"$(OX)\etag_.c" \
"$(OX)\event_.c" \
"$(OX)\export_.c" \
"$(OX)\extcgi_.c" \
"$(OX)\file_.c" \
"$(OX)\fileedit_.c" \
"$(OX)\finfo_.c" \
"$(OX)\foci_.c" \
"$(OX)\forum_.c" \
"$(OX)\fshell_.c" \
"$(OX)\fusefs_.c" \
"$(OX)\fuzz_.c" \
"$(OX)\glob_.c" \
"$(OX)\graph_.c" \
"$(OX)\gzip_.c" \
"$(OX)\hname_.c" \
"$(OX)\http_.c" \
"$(OX)\http_socket_.c" \
"$(OX)\http_ssl_.c" \
"$(OX)\http_transport_.c" \
"$(OX)\import_.c" \
"$(OX)\info_.c" \
"$(OX)\json_.c" \
"$(OX)\json_artifact_.c" \
"$(OX)\json_branch_.c" \
"$(OX)\json_config_.c" \
"$(OX)\json_diff_.c" \
"$(OX)\json_dir_.c" \
"$(OX)\json_finfo_.c" \
"$(OX)\json_login_.c" \
"$(OX)\json_query_.c" \
"$(OX)\json_report_.c" \
"$(OX)\json_status_.c" \
"$(OX)\json_tag_.c" \
"$(OX)\json_timeline_.c" \
"$(OX)\json_user_.c" \
"$(OX)\json_wiki_.c" \
"$(OX)\leaf_.c" \
"$(OX)\loadctrl_.c" \
"$(OX)\login_.c" \
"$(OX)\lookslike_.c" \
"$(OX)\main_.c" \
"$(OX)\manifest_.c" \
"$(OX)\markdown_.c" \
"$(OX)\markdown_html_.c" \
"$(OX)\md5_.c" \
"$(OX)\merge_.c" \
"$(OX)\merge3_.c" \
"$(OX)\moderate_.c" \
"$(OX)\name_.c" \
"$(OX)\path_.c" \
"$(OX)\piechart_.c" \
"$(OX)\pivot_.c" \
"$(OX)\popen_.c" \
"$(OX)\pqueue_.c" \
"$(OX)\printf_.c" \
"$(OX)\publish_.c" \
"$(OX)\purge_.c" \
"$(OX)\rebuild_.c" \
"$(OX)\regexp_.c" \
"$(OX)\repolist_.c" \
"$(OX)\report_.c" \
"$(OX)\rss_.c" \
"$(OX)\schema_.c" \
"$(OX)\search_.c" \
"$(OX)\security_audit_.c" \
"$(OX)\setup_.c" \
"$(OX)\setupuser_.c" \
"$(OX)\sha1_.c" \
"$(OX)\sha1hard_.c" \
"$(OX)\sha3_.c" \
"$(OX)\shun_.c" \
"$(OX)\sitemap_.c" \
"$(OX)\skins_.c" \
"$(OX)\smtp_.c" \
"$(OX)\sqlcmd_.c" \
"$(OX)\stash_.c" \
"$(OX)\stat_.c" \
"$(OX)\statrep_.c" \
"$(OX)\style_.c" \
"$(OX)\sync_.c" \
"$(OX)\tag_.c" \
"$(OX)\tar_.c" \
"$(OX)\terminal_.c" \
"$(OX)\th_main_.c" \
"$(OX)\timeline_.c" \
"$(OX)\tkt_.c" \
"$(OX)\tktsetup_.c" \
"$(OX)\undo_.c" \
"$(OX)\unicode_.c" \
"$(OX)\unversioned_.c" \
"$(OX)\update_.c" \
"$(OX)\url_.c" \
"$(OX)\user_.c" \
"$(OX)\utf8_.c" \
"$(OX)\util_.c" \
"$(OX)\verify_.c" \
"$(OX)\vfile_.c" \
"$(OX)\webmail_.c" \
"$(OX)\wiki_.c" \
"$(OX)\wikiformat_.c" \
"$(OX)\winfile_.c" \
"$(OX)\winhttp_.c" \
"$(OX)\wysiwyg_.c" \
"$(OX)\xfer_.c" \
"$(OX)\xfersetup_.c" \
"$(OX)\zip_.c"
EXTRA_FILES = "$(SRCDIR)\..\skins\aht\details.txt" \
"$(SRCDIR)\..\skins\ardoise\css.txt" \
"$(SRCDIR)\..\skins\ardoise\details.txt" \
"$(SRCDIR)\..\skins\ardoise\footer.txt" \
"$(SRCDIR)\..\skins\ardoise\header.txt" \
"$(SRCDIR)\..\skins\black_and_white\css.txt" \
"$(SRCDIR)\..\skins\black_and_white\details.txt" \
"$(SRCDIR)\..\skins\black_and_white\footer.txt" \
"$(SRCDIR)\..\skins\black_and_white\header.txt" \
"$(SRCDIR)\..\skins\blitz\css.txt" \
"$(SRCDIR)\..\skins\blitz\details.txt" \
"$(SRCDIR)\..\skins\blitz\footer.txt" \
"$(SRCDIR)\..\skins\blitz\header.txt" \
"$(SRCDIR)\..\skins\blitz\ticket.txt" \
"$(SRCDIR)\..\skins\blitz_no_logo\css.txt" \
"$(SRCDIR)\..\skins\blitz_no_logo\details.txt" \
"$(SRCDIR)\..\skins\blitz_no_logo\footer.txt" \
"$(SRCDIR)\..\skins\blitz_no_logo\header.txt" \
"$(SRCDIR)\..\skins\blitz_no_logo\ticket.txt" \
"$(SRCDIR)\..\skins\bootstrap\css.txt" \
"$(SRCDIR)\..\skins\bootstrap\details.txt" \
"$(SRCDIR)\..\skins\bootstrap\footer.txt" \
"$(SRCDIR)\..\skins\bootstrap\header.txt" \
"$(SRCDIR)\..\skins\default\css.txt" \
"$(SRCDIR)\..\skins\default\details.txt" \
"$(SRCDIR)\..\skins\default\footer.txt" \
"$(SRCDIR)\..\skins\default\header.txt" \
"$(SRCDIR)\..\skins\default\js.txt" \
"$(SRCDIR)\..\skins\eagle\css.txt" \
"$(SRCDIR)\..\skins\eagle\details.txt" \
"$(SRCDIR)\..\skins\eagle\footer.txt" \
"$(SRCDIR)\..\skins\eagle\header.txt" \
"$(SRCDIR)\..\skins\enhanced1\css.txt" \
"$(SRCDIR)\..\skins\enhanced1\details.txt" \
"$(SRCDIR)\..\skins\enhanced1\footer.txt" \
"$(SRCDIR)\..\skins\enhanced1\header.txt" \
"$(SRCDIR)\..\skins\khaki\css.txt" \
"$(SRCDIR)\..\skins\khaki\details.txt" \
"$(SRCDIR)\..\skins\khaki\footer.txt" \
"$(SRCDIR)\..\skins\khaki\header.txt" \
"$(SRCDIR)\..\skins\original\css.txt" \
"$(SRCDIR)\..\skins\original\details.txt" \
"$(SRCDIR)\..\skins\original\footer.txt" \
"$(SRCDIR)\..\skins\original\header.txt" \
"$(SRCDIR)\..\skins\plain_gray\css.txt" \
"$(SRCDIR)\..\skins\plain_gray\details.txt" \
"$(SRCDIR)\..\skins\plain_gray\footer.txt" \
"$(SRCDIR)\..\skins\plain_gray\header.txt" \
"$(SRCDIR)\..\skins\rounded1\css.txt" \
"$(SRCDIR)\..\skins\rounded1\details.txt" \
"$(SRCDIR)\..\skins\rounded1\footer.txt" \
"$(SRCDIR)\..\skins\rounded1\header.txt" \
"$(SRCDIR)\..\skins\xekri\css.txt" \
"$(SRCDIR)\..\skins\xekri\details.txt" \
"$(SRCDIR)\..\skins\xekri\footer.txt" \
"$(SRCDIR)\..\skins\xekri\header.txt" \
"$(SRCDIR)\accordion.js" \
"$(SRCDIR)\ci_edit.js" \
"$(SRCDIR)\copybtn.js" \
"$(SRCDIR)\default.css" \
"$(SRCDIR)\diff.tcl" \
"$(SRCDIR)\forum.js" \
"$(SRCDIR)\fossil.bootstrap.js" \
"$(SRCDIR)\fossil.confirmer.js" \
"$(SRCDIR)\fossil.dom.js" \
"$(SRCDIR)\fossil.fetch.js" \
"$(SRCDIR)\fossil.page.fileedit.js" \
"$(SRCDIR)\fossil.storage.js" \
"$(SRCDIR)\fossil.tabs.js" \
"$(SRCDIR)\graph.js" \
"$(SRCDIR)\href.js" \
"$(SRCDIR)\login.js" \
"$(SRCDIR)\markdown.md" \
"$(SRCDIR)\menu.js" \
"$(SRCDIR)\sbsdiff.js" \
"$(SRCDIR)\scroll.js" \
"$(SRCDIR)\skin.js" \
"$(SRCDIR)\sorttable.js" \
"$(SRCDIR)\sounds\0.wav" \
"$(SRCDIR)\sounds\1.wav" \
"$(SRCDIR)\sounds\2.wav" \
"$(SRCDIR)\sounds\3.wav" \
"$(SRCDIR)\sounds\4.wav" \
"$(SRCDIR)\sounds\5.wav" \
"$(SRCDIR)\sounds\6.wav" \
"$(SRCDIR)\sounds\7.wav" \
"$(SRCDIR)\sounds\8.wav" \
"$(SRCDIR)\sounds\9.wav" \
"$(SRCDIR)\sounds\a.wav" \
"$(SRCDIR)\sounds\b.wav" \
"$(SRCDIR)\sounds\c.wav" \
"$(SRCDIR)\sounds\d.wav" \
"$(SRCDIR)\sounds\e.wav" \
"$(SRCDIR)\sounds\f.wav" \
"$(SRCDIR)\style.admin_log.css" \
"$(SRCDIR)\style.fileedit.css" \
"$(SRCDIR)\tree.js" \
"$(SRCDIR)\useredit.js" \
"$(SRCDIR)\wiki.wiki"
OBJ = "$(OX)\add$O" \
"$(OX)\ajax$O" \
"$(OX)\alerts$O" \
"$(OX)\allrepo$O" \
"$(OX)\attach$O" \
"$(OX)\backlink$O" \
"$(OX)\backoffice$O" \
"$(OX)\bag$O" \
"$(OX)\bisect$O" \
"$(OX)\blob$O" \
"$(OX)\branch$O" \
"$(OX)\browse$O" \
"$(OX)\builtin$O" \
"$(OX)\bundle$O" \
"$(OX)\cache$O" \
"$(OX)\capabilities$O" \
"$(OX)\captcha$O" \
"$(OX)\cgi$O" \
"$(OX)\checkin$O" \
"$(OX)\checkout$O" \
"$(OX)\clearsign$O" \
"$(OX)\clone$O" \
"$(OX)\comformat$O" \
"$(OX)\configure$O" \
"$(OX)\content$O" \
"$(OX)\cookies$O" \
"$(OX)\cson_amalgamation$O" \
"$(OX)\db$O" \
"$(OX)\delta$O" \
"$(OX)\deltacmd$O" \
"$(OX)\deltafunc$O" \
"$(OX)\descendants$O" \
"$(OX)\diff$O" \
"$(OX)\diffcmd$O" \
"$(OX)\dispatch$O" \
"$(OX)\doc$O" \
"$(OX)\encode$O" \
"$(OX)\etag$O" \
"$(OX)\event$O" \
"$(OX)\export$O" \
"$(OX)\extcgi$O" \
"$(OX)\file$O" \
"$(OX)\fileedit$O" \
"$(OX)\finfo$O" \
"$(OX)\foci$O" \
"$(OX)\forum$O" \
"$(OX)\fshell$O" \
"$(OX)\fusefs$O" \
"$(OX)\fuzz$O" \
"$(OX)\glob$O" \
"$(OX)\graph$O" \
"$(OX)\gzip$O" \
"$(OX)\hname$O" \
"$(OX)\http$O" \
"$(OX)\http_socket$O" \
"$(OX)\http_ssl$O" \
"$(OX)\http_transport$O" \
"$(OX)\import$O" \
"$(OX)\info$O" \
"$(OX)\json$O" \
"$(OX)\json_artifact$O" \
"$(OX)\json_branch$O" \
"$(OX)\json_config$O" \
"$(OX)\json_diff$O" \
"$(OX)\json_dir$O" \
"$(OX)\json_finfo$O" \
"$(OX)\json_login$O" \
"$(OX)\json_query$O" \
"$(OX)\json_report$O" \
"$(OX)\json_status$O" \
"$(OX)\json_tag$O" \
"$(OX)\json_timeline$O" \
"$(OX)\json_user$O" \
"$(OX)\json_wiki$O" \
"$(OX)\leaf$O" \
"$(OX)\loadctrl$O" \
"$(OX)\login$O" \
"$(OX)\lookslike$O" \
"$(OX)\main$O" \
"$(OX)\manifest$O" \
"$(OX)\markdown$O" \
"$(OX)\markdown_html$O" \
"$(OX)\md5$O" \
"$(OX)\merge$O" \
"$(OX)\merge3$O" \
"$(OX)\moderate$O" \
"$(OX)\name$O" \
"$(OX)\path$O" \
"$(OX)\piechart$O" \
"$(OX)\pivot$O" \
"$(OX)\popen$O" \
"$(OX)\pqueue$O" \
"$(OX)\printf$O" \
"$(OX)\publish$O" \
"$(OX)\purge$O" \
"$(OX)\rebuild$O" \
"$(OX)\regexp$O" \
"$(OX)\repolist$O" \
"$(OX)\report$O" \
"$(OX)\rss$O" \
"$(OX)\schema$O" \
"$(OX)\search$O" \
"$(OX)\security_audit$O" \
"$(OX)\setup$O" \
"$(OX)\setupuser$O" \
"$(OX)\sha1$O" \
"$(OX)\sha1hard$O" \
"$(OX)\sha3$O" \
"$(OX)\shell$O" \
"$(OX)\shun$O" \
"$(OX)\sitemap$O" \
"$(OX)\skins$O" \
"$(OX)\smtp$O" \
"$(OX)\sqlcmd$O" \
"$(OX)\sqlite3$O" \
"$(OX)\stash$O" \
"$(OX)\stat$O" \
"$(OX)\statrep$O" \
"$(OX)\style$O" \
"$(OX)\sync$O" \
"$(OX)\tag$O" \
"$(OX)\tar$O" \
"$(OX)\terminal$O" \
"$(OX)\th$O" \
"$(OX)\th_lang$O" \
"$(OX)\th_main$O" \
"$(OX)\th_tcl$O" \
"$(OX)\timeline$O" \
"$(OX)\tkt$O" \
"$(OX)\tktsetup$O" \
"$(OX)\undo$O" \
"$(OX)\unicode$O" \
"$(OX)\unversioned$O" \
"$(OX)\update$O" \
"$(OX)\url$O" \
"$(OX)\user$O" \
"$(OX)\utf8$O" \
"$(OX)\util$O" \
"$(OX)\verify$O" \
"$(OX)\vfile$O" \
"$(OX)\webmail$O" \
"$(OX)\wiki$O" \
"$(OX)\wikiformat$O" \
"$(OX)\winfile$O" \
"$(OX)\winhttp$O" \
"$(OX)\wysiwyg$O" \
"$(OX)\xfer$O" \
"$(OX)\xfersetup$O" \
"$(OX)\zip$O" \
!if $(FOSSIL_ENABLE_MINIZ)!=0
"$(OX)\miniz$O" \
!endif
"$(OX)\fossil.res"
!ifndef BASEAPPNAME
BASEAPPNAME = fossil
!endif
APPNAME = $(OX)\$(BASEAPPNAME)$(E)
PDBNAME = $(OX)\$(BASEAPPNAME)$(P)
APPTARGETS =
all: "$(OX)" "$(APPNAME)"
$(BASEAPPNAME): "$(APPNAME)"
$(BASEAPPNAME)$(E): "$(APPNAME)"
install: "$(APPNAME)"
echo F | xcopy /Y "$(APPNAME)" "$(INSTALLDIR)"\*
!if $(DEBUG)!=0
echo F | xcopy /Y "$(PDBNAME)" "$(INSTALLDIR)"\*
!endif
$(OX):
@-mkdir $@
zlib:
@echo Building zlib from "$(ZLIBDIR)"...
!if $(FOSSIL_ENABLE_WINXP)!=0
@pushd "$(ZLIBDIR)" && $(MAKE) /f win32\Makefile.msc $(ZLIB) "CC=cl $(XPCFLAGS)" "LD=link $(XPLDFLAGS)" && popd
!else
@pushd "$(ZLIBDIR)" && $(MAKE) /f win32\Makefile.msc $(ZLIB) && popd
!endif
clean-zlib:
@pushd "$(ZLIBDIR)" && $(MAKE) /f win32\Makefile.msc clean && popd
!if $(FOSSIL_ENABLE_SSL)!=0
openssl:
@echo Building OpenSSL from "$(SSLDIR)"...
!ifdef PERLDIR
@pushd "$(SSLDIR)" && "$(PERLDIR)\$(PERL)" Configure $(SSLCONFIG) && popd
!else
@pushd "$(SSLDIR)" && "$(PERL)" Configure $(SSLCONFIG) && popd
!endif
!if $(FOSSIL_ENABLE_WINXP)!=0
@pushd "$(SSLDIR)" && $(MAKE) "CC=cl $(XPCFLAGS)" "LFLAGS=$(XPLDFLAGS)" && popd
!else
@pushd "$(SSLDIR)" && $(MAKE) && popd
!endif
clean-openssl:
@pushd "$(SSLDIR)" && $(MAKE) clean && popd
!endif
!if $(FOSSIL_ENABLE_MINIZ)==0
!if $(FOSSIL_BUILD_ZLIB)!=0
APPTARGETS = $(APPTARGETS) zlib
!endif
!endif
!if $(FOSSIL_ENABLE_SSL)!=0
!if $(FOSSIL_BUILD_SSL)!=0
APPTARGETS = $(APPTARGETS) openssl
!endif
!endif
"$(APPNAME)" : $(APPTARGETS) "$(OBJDIR)\translate$E" "$(OBJDIR)\mkindex$E" "$(OBJDIR)\codecheck1$E" "$(OX)\headers" $(OBJ) "$(OX)\linkopts"
"$(OBJDIR)\codecheck1$E" $(SRC)
link $(LDFLAGS) /OUT:$@ /PDB:$(@D)\ $(LIBDIR) Wsetargv.obj "$(OX)\fossil.res" @"$(OX)\linkopts"
if exist "$(B)\win\fossil.exe.manifest" \
$(MTC) -nologo -manifest "$(B)\win\fossil.exe.manifest" -outputresource:$@;1
"$(OX)\linkopts": "$(B)\win\Makefile.msc"
echo "$(OX)\add.obj" > $@
echo "$(OX)\ajax.obj" >> $@
echo "$(OX)\alerts.obj" >> $@
echo "$(OX)\allrepo.obj" >> $@
echo "$(OX)\attach.obj" >> $@
echo "$(OX)\backlink.obj" >> $@
echo "$(OX)\backoffice.obj" >> $@
echo "$(OX)\bag.obj" >> $@
echo "$(OX)\bisect.obj" >> $@
echo "$(OX)\blob.obj" >> $@
echo "$(OX)\branch.obj" >> $@
echo "$(OX)\browse.obj" >> $@
echo "$(OX)\builtin.obj" >> $@
echo "$(OX)\bundle.obj" >> $@
echo "$(OX)\cache.obj" >> $@
echo "$(OX)\capabilities.obj" >> $@
echo "$(OX)\captcha.obj" >> $@
echo "$(OX)\cgi.obj" >> $@
echo "$(OX)\checkin.obj" >> $@
echo "$(OX)\checkout.obj" >> $@
echo "$(OX)\clearsign.obj" >> $@
echo "$(OX)\clone.obj" >> $@
echo "$(OX)\comformat.obj" >> $@
echo "$(OX)\configure.obj" >> $@
echo "$(OX)\content.obj" >> $@
echo "$(OX)\cookies.obj" >> $@
echo "$(OX)\cson_amalgamation.obj" >> $@
echo "$(OX)\db.obj" >> $@
echo "$(OX)\delta.obj" >> $@
echo "$(OX)\deltacmd.obj" >> $@
echo "$(OX)\deltafunc.obj" >> $@
echo "$(OX)\descendants.obj" >> $@
echo "$(OX)\diff.obj" >> $@
echo "$(OX)\diffcmd.obj" >> $@
echo "$(OX)\dispatch.obj" >> $@
echo "$(OX)\doc.obj" >> $@
echo "$(OX)\encode.obj" >> $@
echo "$(OX)\etag.obj" >> $@
echo "$(OX)\event.obj" >> $@
echo "$(OX)\export.obj" >> $@
echo "$(OX)\extcgi.obj" >> $@
echo "$(OX)\file.obj" >> $@
echo "$(OX)\fileedit.obj" >> $@
echo "$(OX)\finfo.obj" >> $@
echo "$(OX)\foci.obj" >> $@
echo "$(OX)\forum.obj" >> $@
echo "$(OX)\fshell.obj" >> $@
echo "$(OX)\fusefs.obj" >> $@
echo "$(OX)\fuzz.obj" >> $@
echo "$(OX)\glob.obj" >> $@
echo "$(OX)\graph.obj" >> $@
echo "$(OX)\gzip.obj" >> $@
echo "$(OX)\hname.obj" >> $@
echo "$(OX)\http.obj" >> $@
echo "$(OX)\http_socket.obj" >> $@
echo "$(OX)\http_ssl.obj" >> $@
echo "$(OX)\http_transport.obj" >> $@
echo "$(OX)\import.obj" >> $@
echo "$(OX)\info.obj" >> $@
echo "$(OX)\json.obj" >> $@
echo "$(OX)\json_artifact.obj" >> $@
echo "$(OX)\json_branch.obj" >> $@
echo "$(OX)\json_config.obj" >> $@
echo "$(OX)\json_diff.obj" >> $@
echo "$(OX)\json_dir.obj" >> $@
echo "$(OX)\json_finfo.obj" >> $@
echo "$(OX)\json_login.obj" >> $@
echo "$(OX)\json_query.obj" >> $@
echo "$(OX)\json_report.obj" >> $@
echo "$(OX)\json_status.obj" >> $@
echo "$(OX)\json_tag.obj" >> $@
echo "$(OX)\json_timeline.obj" >> $@
echo "$(OX)\json_user.obj" >> $@
echo "$(OX)\json_wiki.obj" >> $@
echo "$(OX)\leaf.obj" >> $@
echo "$(OX)\loadctrl.obj" >> $@
echo "$(OX)\login.obj" >> $@
echo "$(OX)\lookslike.obj" >> $@
echo "$(OX)\main.obj" >> $@
echo "$(OX)\manifest.obj" >> $@
echo "$(OX)\markdown.obj" >> $@
echo "$(OX)\markdown_html.obj" >> $@
echo "$(OX)\md5.obj" >> $@
echo "$(OX)\merge.obj" >> $@
echo "$(OX)\merge3.obj" >> $@
echo "$(OX)\moderate.obj" >> $@
echo "$(OX)\name.obj" >> $@
echo "$(OX)\path.obj" >> $@
echo "$(OX)\piechart.obj" >> $@
echo "$(OX)\pivot.obj" >> $@
echo "$(OX)\popen.obj" >> $@
echo "$(OX)\pqueue.obj" >> $@
echo "$(OX)\printf.obj" >> $@
echo "$(OX)\publish.obj" >> $@
echo "$(OX)\purge.obj" >> $@
echo "$(OX)\rebuild.obj" >> $@
echo "$(OX)\regexp.obj" >> $@
echo "$(OX)\repolist.obj" >> $@
echo "$(OX)\report.obj" >> $@
echo "$(OX)\rss.obj" >> $@
echo "$(OX)\schema.obj" >> $@
echo "$(OX)\search.obj" >> $@
echo "$(OX)\security_audit.obj" >> $@
echo "$(OX)\setup.obj" >> $@
echo "$(OX)\setupuser.obj" >> $@
echo "$(OX)\sha1.obj" >> $@
echo "$(OX)\sha1hard.obj" >> $@
echo "$(OX)\sha3.obj" >> $@
echo "$(OX)\shell.obj" >> $@
echo "$(OX)\shun.obj" >> $@
echo "$(OX)\sitemap.obj" >> $@
echo "$(OX)\skins.obj" >> $@
echo "$(OX)\smtp.obj" >> $@
echo "$(OX)\sqlcmd.obj" >> $@
echo "$(OX)\sqlite3.obj" >> $@
echo "$(OX)\stash.obj" >> $@
echo "$(OX)\stat.obj" >> $@
echo "$(OX)\statrep.obj" >> $@
echo "$(OX)\style.obj" >> $@
echo "$(OX)\sync.obj" >> $@
echo "$(OX)\tag.obj" >> $@
echo "$(OX)\tar.obj" >> $@
echo "$(OX)\terminal.obj" >> $@
echo "$(OX)\th.obj" >> $@
echo "$(OX)\th_lang.obj" >> $@
echo "$(OX)\th_main.obj" >> $@
echo "$(OX)\th_tcl.obj" >> $@
echo "$(OX)\timeline.obj" >> $@
echo "$(OX)\tkt.obj" >> $@
echo "$(OX)\tktsetup.obj" >> $@
echo "$(OX)\undo.obj" >> $@
echo "$(OX)\unicode.obj" >> $@
echo "$(OX)\unversioned.obj" >> $@
echo "$(OX)\update.obj" >> $@
echo "$(OX)\url.obj" >> $@
echo "$(OX)\user.obj" >> $@
echo "$(OX)\utf8.obj" >> $@
echo "$(OX)\util.obj" >> $@
echo "$(OX)\verify.obj" >> $@
echo "$(OX)\vfile.obj" >> $@
echo "$(OX)\webmail.obj" >> $@
echo "$(OX)\wiki.obj" >> $@
echo "$(OX)\wikiformat.obj" >> $@
echo "$(OX)\winfile.obj" >> $@
echo "$(OX)\winhttp.obj" >> $@
echo "$(OX)\wysiwyg.obj" >> $@
echo "$(OX)\xfer.obj" >> $@
echo "$(OX)\xfersetup.obj" >> $@
echo "$(OX)\zip.obj" >> $@
!if $(FOSSIL_ENABLE_MINIZ)!=0
echo "$(OX)\miniz.obj" >> $@
!endif
echo $(LIBS) >> $@
"$(OBJDIR)\translate$E": "$(SRCDIR)\translate.c"
$(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $**
"$(OBJDIR)\makeheaders$E": "$(SRCDIR)\makeheaders.c"
$(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $**
"$(OBJDIR)\mkindex$E": "$(SRCDIR)\mkindex.c"
$(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $**
"$(OBJDIR)\mkbuiltin$E": "$(SRCDIR)\mkbuiltin.c"
$(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $**
"$(OBJDIR)\mkversion$E": "$(SRCDIR)\mkversion.c"
$(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $**
"$(OBJDIR)\codecheck1$E": "$(SRCDIR)\codecheck1.c"
$(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $**
!if $(USE_SEE)!=0
SEE_FLAGS = /DSQLITE_HAS_CODEC=1 /DSQLITE_SHELL_DBKEY_PROC=fossil_key
SQLITE3_SHELL_SRC = $(SRCDIR)\shell-see.c
SQLITE3_SRC = $(SRCDIR)\sqlite3-see.c
!else
SEE_FLAGS =
SQLITE3_SHELL_SRC = $(SRCDIR)\shell.c
SQLITE3_SRC = $(SRCDIR)\sqlite3.c
!endif
"$(OX)\shell$O" : "$(SQLITE3_SHELL_SRC)" "$(B)\win\Makefile.msc"
$(TCC) /Fo$@ /Fd$(@D)\ $(SHELL_OPTIONS) $(SQLITE_OPTIONS) $(SHELL_CFLAGS) $(SEE_FLAGS) -c "$(SQLITE3_SHELL_SRC)"
"$(OX)\sqlite3$O" : "$(SQLITE3_SRC)" "$(B)\win\Makefile.msc"
$(TCC) /Fo$@ /Fd$(@D)\ -c $(SQLITE_OPTIONS) $(SQLITE_CFLAGS) $(SEE_FLAGS) "$(SQLITE3_SRC)"
"$(OX)\th$O" : "$(SRCDIR)\th.c"
$(TCC) /Fo$@ /Fd$(@D)\ -c $**
"$(OX)\th_lang$O" : "$(SRCDIR)\th_lang.c"
$(TCC) /Fo$@ /Fd$(@D)\ -c $**
"$(OX)\th_tcl$O" : "$(SRCDIR)\th_tcl.c"
$(TCC) /Fo$@ /Fd$(@D)\ -c $**
"$(OX)\miniz$O" : "$(SRCDIR)\miniz.c"
$(TCC) /Fo$@ /Fd$(@D)\ -c $(MINIZ_OPTIONS) $**
"$(OX)\VERSION.h" : "$(OBJDIR)\mkversion$E" "$(B)\manifest.uuid" "$(B)\manifest" "$(B)\VERSION"
$** > $@
"$(OX)\cson_amalgamation$O" : "$(SRCDIR)\cson_amalgamation.c"
$(TCC) /Fo$@ /Fd$(@D)\ -c $**
"$(OX)\page_index.h": "$(OBJDIR)\mkindex$E" $(SRC)
$** > $@
"$(OX)\builtin_data.h": "$(OBJDIR)\mkbuiltin$E" "$(OX)\builtin_data.reslist"
"$(OBJDIR)\mkbuiltin$E" --prefix "$(SRCDIR)/" --reslist "$(OX)\builtin_data.reslist" > $@
cleanx:
-del "$(OX)\*.obj" 2>NUL
-del "$(OBJDIR)\*.obj" 2>NUL
-del "$(OX)\*_.c" 2>NUL
-del "$(OX)\*.h" 2>NUL
-del "$(OX)\*.ilk" 2>NUL
-del "$(OX)\*.map" 2>NUL
-del "$(OX)\*.res" 2>NUL
-del "$(OX)\*.reslist" 2>NUL
-del "$(OX)\headers" 2>NUL
-del "$(OX)\linkopts" 2>NUL
-del "$(OX)\vc*.pdb" 2>NUL
clean: cleanx
-del "$(APPNAME)" 2>NUL
-del "$(PDBNAME)" 2>NUL
-del "$(OBJDIR)\translate$E" 2>NUL
-del "$(OBJDIR)\translate$P" 2>NUL
-del "$(OBJDIR)\mkindex$E" 2>NUL
-del "$(OBJDIR)\mkindex$P" 2>NUL
-del "$(OBJDIR)\makeheaders$E" 2>NUL
-del "$(OBJDIR)\makeheaders$P" 2>NUL
-del "$(OBJDIR)\mkversion$E" 2>NUL
-del "$(OBJDIR)\mkversion$P" 2>NUL
-del "$(OBJDIR)\mkcss$E" 2>NUL
-del "$(OBJDIR)\mkcss$P" 2>NUL
-del "$(OBJDIR)\codecheck1$E" 2>NUL
-del "$(OBJDIR)\codecheck1$P" 2>NUL
-del "$(OBJDIR)\mkbuiltin$E" 2>NUL
-del "$(OBJDIR)\mkbuiltin$P" 2>NUL
realclean: clean
"$(OBJDIR)\json$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_artifact$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_branch$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_config$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_diff$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_dir$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_finfo$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_login$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_query$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_report$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_status$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_tag$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_timeline$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_user$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_wiki$O" : "$(SRCDIR)\json_detail.h"
"$(OX)\builtin_data.reslist": $(EXTRA_FILES) "$(B)\win\Makefile.msc"
echo "$(SRCDIR)\../skins/aht/details.txt" > $@
echo "$(SRCDIR)\../skins/ardoise/css.txt" >> $@
echo "$(SRCDIR)\../skins/ardoise/details.txt" >> $@
echo "$(SRCDIR)\../skins/ardoise/footer.txt" >> $@
echo "$(SRCDIR)\../skins/ardoise/header.txt" >> $@
echo "$(SRCDIR)\../skins/black_and_white/css.txt" >> $@
echo "$(SRCDIR)\../skins/black_and_white/details.txt" >> $@
echo "$(SRCDIR)\../skins/black_and_white/footer.txt" >> $@
echo "$(SRCDIR)\../skins/black_and_white/header.txt" >> $@
echo "$(SRCDIR)\../skins/blitz/css.txt" >> $@
echo "$(SRCDIR)\../skins/blitz/details.txt" >> $@
echo "$(SRCDIR)\../skins/blitz/footer.txt" >> $@
echo "$(SRCDIR)\../skins/blitz/header.txt" >> $@
echo "$(SRCDIR)\../skins/blitz/ticket.txt" >> $@
echo "$(SRCDIR)\../skins/blitz_no_logo/css.txt" >> $@
echo "$(SRCDIR)\../skins/blitz_no_logo/details.txt" >> $@
echo "$(SRCDIR)\../skins/blitz_no_logo/footer.txt" >> $@
echo "$(SRCDIR)\../skins/blitz_no_logo/header.txt" >> $@
echo "$(SRCDIR)\../skins/blitz_no_logo/ticket.txt" >> $@
echo "$(SRCDIR)\../skins/bootstrap/css.txt" >> $@
echo "$(SRCDIR)\../skins/bootstrap/details.txt" >> $@
echo "$(SRCDIR)\../skins/bootstrap/footer.txt" >> $@
echo "$(SRCDIR)\../skins/bootstrap/header.txt" >> $@
echo "$(SRCDIR)\../skins/default/css.txt" >> $@
echo "$(SRCDIR)\../skins/default/details.txt" >> $@
echo "$(SRCDIR)\../skins/default/footer.txt" >> $@
echo "$(SRCDIR)\../skins/default/header.txt" >> $@
echo "$(SRCDIR)\../skins/default/js.txt" >> $@
echo "$(SRCDIR)\../skins/eagle/css.txt" >> $@
echo "$(SRCDIR)\../skins/eagle/details.txt" >> $@
echo "$(SRCDIR)\../skins/eagle/footer.txt" >> $@
echo "$(SRCDIR)\../skins/eagle/header.txt" >> $@
echo "$(SRCDIR)\../skins/enhanced1/css.txt" >> $@
echo "$(SRCDIR)\../skins/enhanced1/details.txt" >> $@
echo "$(SRCDIR)\../skins/enhanced1/footer.txt" >> $@
echo "$(SRCDIR)\../skins/enhanced1/header.txt" >> $@
echo "$(SRCDIR)\../skins/khaki/css.txt" >> $@
echo "$(SRCDIR)\../skins/khaki/details.txt" >> $@
echo "$(SRCDIR)\../skins/khaki/footer.txt" >> $@
echo "$(SRCDIR)\../skins/khaki/header.txt" >> $@
echo "$(SRCDIR)\../skins/original/css.txt" >> $@
echo "$(SRCDIR)\../skins/original/details.txt" >> $@
echo "$(SRCDIR)\../skins/original/footer.txt" >> $@
echo "$(SRCDIR)\../skins/original/header.txt" >> $@
echo "$(SRCDIR)\../skins/plain_gray/css.txt" >> $@
echo "$(SRCDIR)\../skins/plain_gray/details.txt" >> $@
echo "$(SRCDIR)\../skins/plain_gray/footer.txt" >> $@
echo "$(SRCDIR)\../skins/plain_gray/header.txt" >> $@
echo "$(SRCDIR)\../skins/rounded1/css.txt" >> $@
echo "$(SRCDIR)\../skins/rounded1/details.txt" >> $@
echo "$(SRCDIR)\../skins/rounded1/footer.txt" >> $@
echo "$(SRCDIR)\../skins/rounded1/header.txt" >> $@
echo "$(SRCDIR)\../skins/xekri/css.txt" >> $@
echo "$(SRCDIR)\../skins/xekri/details.txt" >> $@
echo "$(SRCDIR)\../skins/xekri/footer.txt" >> $@
echo "$(SRCDIR)\../skins/xekri/header.txt" >> $@
echo "$(SRCDIR)\accordion.js" >> $@
echo "$(SRCDIR)\ci_edit.js" >> $@
echo "$(SRCDIR)\copybtn.js" >> $@
echo "$(SRCDIR)\default.css" >> $@
echo "$(SRCDIR)\diff.tcl" >> $@
echo "$(SRCDIR)\forum.js" >> $@
echo "$(SRCDIR)\fossil.bootstrap.js" >> $@
echo "$(SRCDIR)\fossil.confirmer.js" >> $@
echo "$(SRCDIR)\fossil.dom.js" >> $@
echo "$(SRCDIR)\fossil.fetch.js" >> $@
echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@
echo "$(SRCDIR)\fossil.storage.js" >> $@
echo "$(SRCDIR)\fossil.tabs.js" >> $@
echo "$(SRCDIR)\graph.js" >> $@
echo "$(SRCDIR)\href.js" >> $@
echo "$(SRCDIR)\login.js" >> $@
echo "$(SRCDIR)\markdown.md" >> $@
echo "$(SRCDIR)\menu.js" >> $@
echo "$(SRCDIR)\sbsdiff.js" >> $@
echo "$(SRCDIR)\scroll.js" >> $@
echo "$(SRCDIR)\skin.js" >> $@
echo "$(SRCDIR)\sorttable.js" >> $@
echo "$(SRCDIR)\sounds/0.wav" >> $@
echo "$(SRCDIR)\sounds/1.wav" >> $@
echo "$(SRCDIR)\sounds/2.wav" >> $@
echo "$(SRCDIR)\sounds/3.wav" >> $@
echo "$(SRCDIR)\sounds/4.wav" >> $@
echo "$(SRCDIR)\sounds/5.wav" >> $@
echo "$(SRCDIR)\sounds/6.wav" >> $@
echo "$(SRCDIR)\sounds/7.wav" >> $@
echo "$(SRCDIR)\sounds/8.wav" >> $@
echo "$(SRCDIR)\sounds/9.wav" >> $@
echo "$(SRCDIR)\sounds/a.wav" >> $@
echo "$(SRCDIR)\sounds/b.wav" >> $@
echo "$(SRCDIR)\sounds/c.wav" >> $@
echo "$(SRCDIR)\sounds/d.wav" >> $@
echo "$(SRCDIR)\sounds/e.wav" >> $@
echo "$(SRCDIR)\sounds/f.wav" >> $@
echo "$(SRCDIR)\style.admin_log.css" >> $@
echo "$(SRCDIR)\style.fileedit.css" >> $@
echo "$(SRCDIR)\tree.js" >> $@
echo "$(SRCDIR)\useredit.js" >> $@
echo "$(SRCDIR)\wiki.wiki" >> $@
"$(OX)\add$O" : "$(OX)\add_.c" "$(OX)\add.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\add_.c"
"$(OX)\add_.c" : "$(SRCDIR)\add.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\ajax$O" : "$(OX)\ajax_.c" "$(OX)\ajax.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\ajax_.c"
"$(OX)\ajax_.c" : "$(SRCDIR)\ajax.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\alerts$O" : "$(OX)\alerts_.c" "$(OX)\alerts.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\alerts_.c"
"$(OX)\alerts_.c" : "$(SRCDIR)\alerts.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\allrepo$O" : "$(OX)\allrepo_.c" "$(OX)\allrepo.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\allrepo_.c"
"$(OX)\allrepo_.c" : "$(SRCDIR)\allrepo.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\attach$O" : "$(OX)\attach_.c" "$(OX)\attach.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\attach_.c"
"$(OX)\attach_.c" : "$(SRCDIR)\attach.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\backlink$O" : "$(OX)\backlink_.c" "$(OX)\backlink.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\backlink_.c"
"$(OX)\backlink_.c" : "$(SRCDIR)\backlink.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\backoffice$O" : "$(OX)\backoffice_.c" "$(OX)\backoffice.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\backoffice_.c"
"$(OX)\backoffice_.c" : "$(SRCDIR)\backoffice.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\bag$O" : "$(OX)\bag_.c" "$(OX)\bag.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\bag_.c"
"$(OX)\bag_.c" : "$(SRCDIR)\bag.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\bisect$O" : "$(OX)\bisect_.c" "$(OX)\bisect.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\bisect_.c"
"$(OX)\bisect_.c" : "$(SRCDIR)\bisect.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\blob$O" : "$(OX)\blob_.c" "$(OX)\blob.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\blob_.c"
"$(OX)\blob_.c" : "$(SRCDIR)\blob.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\branch$O" : "$(OX)\branch_.c" "$(OX)\branch.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\branch_.c"
"$(OX)\branch_.c" : "$(SRCDIR)\branch.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\browse$O" : "$(OX)\browse_.c" "$(OX)\browse.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\browse_.c"
"$(OX)\browse_.c" : "$(SRCDIR)\browse.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\builtin$O" : "$(OX)\builtin_.c" "$(OX)\builtin.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\builtin_.c"
"$(OX)\builtin_.c" : "$(SRCDIR)\builtin.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\bundle$O" : "$(OX)\bundle_.c" "$(OX)\bundle.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\bundle_.c"
"$(OX)\bundle_.c" : "$(SRCDIR)\bundle.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\cache$O" : "$(OX)\cache_.c" "$(OX)\cache.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\cache_.c"
"$(OX)\cache_.c" : "$(SRCDIR)\cache.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\capabilities$O" : "$(OX)\capabilities_.c" "$(OX)\capabilities.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\capabilities_.c"
"$(OX)\capabilities_.c" : "$(SRCDIR)\capabilities.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\captcha$O" : "$(OX)\captcha_.c" "$(OX)\captcha.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\captcha_.c"
"$(OX)\captcha_.c" : "$(SRCDIR)\captcha.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\cgi$O" : "$(OX)\cgi_.c" "$(OX)\cgi.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\cgi_.c"
"$(OX)\cgi_.c" : "$(SRCDIR)\cgi.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\checkin$O" : "$(OX)\checkin_.c" "$(OX)\checkin.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\checkin_.c"
"$(OX)\checkin_.c" : "$(SRCDIR)\checkin.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\checkout$O" : "$(OX)\checkout_.c" "$(OX)\checkout.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\checkout_.c"
"$(OX)\checkout_.c" : "$(SRCDIR)\checkout.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\clearsign$O" : "$(OX)\clearsign_.c" "$(OX)\clearsign.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\clearsign_.c"
"$(OX)\clearsign_.c" : "$(SRCDIR)\clearsign.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\clone$O" : "$(OX)\clone_.c" "$(OX)\clone.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\clone_.c"
"$(OX)\clone_.c" : "$(SRCDIR)\clone.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\comformat$O" : "$(OX)\comformat_.c" "$(OX)\comformat.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\comformat_.c"
"$(OX)\comformat_.c" : "$(SRCDIR)\comformat.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\configure$O" : "$(OX)\configure_.c" "$(OX)\configure.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\configure_.c"
"$(OX)\configure_.c" : "$(SRCDIR)\configure.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\content$O" : "$(OX)\content_.c" "$(OX)\content.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\content_.c"
"$(OX)\content_.c" : "$(SRCDIR)\content.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\cookies$O" : "$(OX)\cookies_.c" "$(OX)\cookies.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\cookies_.c"
"$(OX)\cookies_.c" : "$(SRCDIR)\cookies.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\db$O" : "$(OX)\db_.c" "$(OX)\db.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\db_.c"
"$(OX)\db_.c" : "$(SRCDIR)\db.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\delta$O" : "$(OX)\delta_.c" "$(OX)\delta.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\delta_.c"
"$(OX)\delta_.c" : "$(SRCDIR)\delta.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\deltacmd$O" : "$(OX)\deltacmd_.c" "$(OX)\deltacmd.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\deltacmd_.c"
"$(OX)\deltacmd_.c" : "$(SRCDIR)\deltacmd.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\deltafunc$O" : "$(OX)\deltafunc_.c" "$(OX)\deltafunc.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\deltafunc_.c"
"$(OX)\deltafunc_.c" : "$(SRCDIR)\deltafunc.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\descendants$O" : "$(OX)\descendants_.c" "$(OX)\descendants.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\descendants_.c"
"$(OX)\descendants_.c" : "$(SRCDIR)\descendants.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\diff$O" : "$(OX)\diff_.c" "$(OX)\diff.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\diff_.c"
"$(OX)\diff_.c" : "$(SRCDIR)\diff.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\diffcmd$O" : "$(OX)\diffcmd_.c" "$(OX)\diffcmd.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\diffcmd_.c"
"$(OX)\diffcmd_.c" : "$(SRCDIR)\diffcmd.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\dispatch$O" : "$(OX)\dispatch_.c" "$(OX)\dispatch.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\dispatch_.c"
"$(OX)\dispatch_.c" : "$(SRCDIR)\dispatch.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\doc$O" : "$(OX)\doc_.c" "$(OX)\doc.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\doc_.c"
"$(OX)\doc_.c" : "$(SRCDIR)\doc.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\encode$O" : "$(OX)\encode_.c" "$(OX)\encode.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\encode_.c"
"$(OX)\encode_.c" : "$(SRCDIR)\encode.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\etag$O" : "$(OX)\etag_.c" "$(OX)\etag.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\etag_.c"
"$(OX)\etag_.c" : "$(SRCDIR)\etag.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\event$O" : "$(OX)\event_.c" "$(OX)\event.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\event_.c"
"$(OX)\event_.c" : "$(SRCDIR)\event.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\export$O" : "$(OX)\export_.c" "$(OX)\export.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\export_.c"
"$(OX)\export_.c" : "$(SRCDIR)\export.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\extcgi$O" : "$(OX)\extcgi_.c" "$(OX)\extcgi.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\extcgi_.c"
"$(OX)\extcgi_.c" : "$(SRCDIR)\extcgi.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\file$O" : "$(OX)\file_.c" "$(OX)\file.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\file_.c"
"$(OX)\file_.c" : "$(SRCDIR)\file.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\fileedit$O" : "$(OX)\fileedit_.c" "$(OX)\fileedit.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\fileedit_.c"
"$(OX)\fileedit_.c" : "$(SRCDIR)\fileedit.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\finfo$O" : "$(OX)\finfo_.c" "$(OX)\finfo.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\finfo_.c"
"$(OX)\finfo_.c" : "$(SRCDIR)\finfo.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\foci$O" : "$(OX)\foci_.c" "$(OX)\foci.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\foci_.c"
"$(OX)\foci_.c" : "$(SRCDIR)\foci.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\forum$O" : "$(OX)\forum_.c" "$(OX)\forum.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\forum_.c"
"$(OX)\forum_.c" : "$(SRCDIR)\forum.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\fshell$O" : "$(OX)\fshell_.c" "$(OX)\fshell.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\fshell_.c"
"$(OX)\fshell_.c" : "$(SRCDIR)\fshell.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\fusefs$O" : "$(OX)\fusefs_.c" "$(OX)\fusefs.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\fusefs_.c"
"$(OX)\fusefs_.c" : "$(SRCDIR)\fusefs.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\fuzz$O" : "$(OX)\fuzz_.c" "$(OX)\fuzz.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\fuzz_.c"
"$(OX)\fuzz_.c" : "$(SRCDIR)\fuzz.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\glob$O" : "$(OX)\glob_.c" "$(OX)\glob.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\glob_.c"
"$(OX)\glob_.c" : "$(SRCDIR)\glob.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\graph$O" : "$(OX)\graph_.c" "$(OX)\graph.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\graph_.c"
"$(OX)\graph_.c" : "$(SRCDIR)\graph.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\gzip$O" : "$(OX)\gzip_.c" "$(OX)\gzip.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\gzip_.c"
"$(OX)\gzip_.c" : "$(SRCDIR)\gzip.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\hname$O" : "$(OX)\hname_.c" "$(OX)\hname.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\hname_.c"
"$(OX)\hname_.c" : "$(SRCDIR)\hname.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\http$O" : "$(OX)\http_.c" "$(OX)\http.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\http_.c"
"$(OX)\http_.c" : "$(SRCDIR)\http.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\http_socket$O" : "$(OX)\http_socket_.c" "$(OX)\http_socket.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\http_socket_.c"
"$(OX)\http_socket_.c" : "$(SRCDIR)\http_socket.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\http_ssl$O" : "$(OX)\http_ssl_.c" "$(OX)\http_ssl.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\http_ssl_.c"
"$(OX)\http_ssl_.c" : "$(SRCDIR)\http_ssl.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\http_transport$O" : "$(OX)\http_transport_.c" "$(OX)\http_transport.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\http_transport_.c"
"$(OX)\http_transport_.c" : "$(SRCDIR)\http_transport.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\import$O" : "$(OX)\import_.c" "$(OX)\import.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\import_.c"
"$(OX)\import_.c" : "$(SRCDIR)\import.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\info$O" : "$(OX)\info_.c" "$(OX)\info.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\info_.c"
"$(OX)\info_.c" : "$(SRCDIR)\info.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\json$O" : "$(OX)\json_.c" "$(OX)\json.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_.c"
"$(OX)\json_.c" : "$(SRCDIR)\json.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\json_artifact$O" : "$(OX)\json_artifact_.c" "$(OX)\json_artifact.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_artifact_.c"
"$(OX)\json_artifact_.c" : "$(SRCDIR)\json_artifact.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\json_branch$O" : "$(OX)\json_branch_.c" "$(OX)\json_branch.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_branch_.c"
"$(OX)\json_branch_.c" : "$(SRCDIR)\json_branch.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\json_config$O" : "$(OX)\json_config_.c" "$(OX)\json_config.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_config_.c"
"$(OX)\json_config_.c" : "$(SRCDIR)\json_config.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\json_diff$O" : "$(OX)\json_diff_.c" "$(OX)\json_diff.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_diff_.c"
"$(OX)\json_diff_.c" : "$(SRCDIR)\json_diff.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\json_dir$O" : "$(OX)\json_dir_.c" "$(OX)\json_dir.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_dir_.c"
"$(OX)\json_dir_.c" : "$(SRCDIR)\json_dir.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\json_finfo$O" : "$(OX)\json_finfo_.c" "$(OX)\json_finfo.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_finfo_.c"
"$(OX)\json_finfo_.c" : "$(SRCDIR)\json_finfo.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\json_login$O" : "$(OX)\json_login_.c" "$(OX)\json_login.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_login_.c"
"$(OX)\json_login_.c" : "$(SRCDIR)\json_login.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\json_query$O" : "$(OX)\json_query_.c" "$(OX)\json_query.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_query_.c"
"$(OX)\json_query_.c" : "$(SRCDIR)\json_query.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\json_report$O" : "$(OX)\json_report_.c" "$(OX)\json_report.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_report_.c"
"$(OX)\json_report_.c" : "$(SRCDIR)\json_report.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\json_status$O" : "$(OX)\json_status_.c" "$(OX)\json_status.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_status_.c"
"$(OX)\json_status_.c" : "$(SRCDIR)\json_status.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\json_tag$O" : "$(OX)\json_tag_.c" "$(OX)\json_tag.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_tag_.c"
"$(OX)\json_tag_.c" : "$(SRCDIR)\json_tag.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\json_timeline$O" : "$(OX)\json_timeline_.c" "$(OX)\json_timeline.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_timeline_.c"
"$(OX)\json_timeline_.c" : "$(SRCDIR)\json_timeline.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\json_user$O" : "$(OX)\json_user_.c" "$(OX)\json_user.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_user_.c"
"$(OX)\json_user_.c" : "$(SRCDIR)\json_user.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\json_wiki$O" : "$(OX)\json_wiki_.c" "$(OX)\json_wiki.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\json_wiki_.c"
"$(OX)\json_wiki_.c" : "$(SRCDIR)\json_wiki.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\leaf$O" : "$(OX)\leaf_.c" "$(OX)\leaf.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\leaf_.c"
"$(OX)\leaf_.c" : "$(SRCDIR)\leaf.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\loadctrl$O" : "$(OX)\loadctrl_.c" "$(OX)\loadctrl.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\loadctrl_.c"
"$(OX)\loadctrl_.c" : "$(SRCDIR)\loadctrl.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\login$O" : "$(OX)\login_.c" "$(OX)\login.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\login_.c"
"$(OX)\login_.c" : "$(SRCDIR)\login.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\lookslike$O" : "$(OX)\lookslike_.c" "$(OX)\lookslike.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\lookslike_.c"
"$(OX)\lookslike_.c" : "$(SRCDIR)\lookslike.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\main$O" : "$(OX)\main_.c" "$(OX)\main.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\main_.c"
"$(OX)\main_.c" : "$(SRCDIR)\main.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\manifest$O" : "$(OX)\manifest_.c" "$(OX)\manifest.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\manifest_.c"
"$(OX)\manifest_.c" : "$(SRCDIR)\manifest.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\markdown$O" : "$(OX)\markdown_.c" "$(OX)\markdown.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\markdown_.c"
"$(OX)\markdown_.c" : "$(SRCDIR)\markdown.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\markdown_html$O" : "$(OX)\markdown_html_.c" "$(OX)\markdown_html.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\markdown_html_.c"
"$(OX)\markdown_html_.c" : "$(SRCDIR)\markdown_html.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\md5$O" : "$(OX)\md5_.c" "$(OX)\md5.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\md5_.c"
"$(OX)\md5_.c" : "$(SRCDIR)\md5.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\merge$O" : "$(OX)\merge_.c" "$(OX)\merge.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\merge_.c"
"$(OX)\merge_.c" : "$(SRCDIR)\merge.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\merge3$O" : "$(OX)\merge3_.c" "$(OX)\merge3.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\merge3_.c"
"$(OX)\merge3_.c" : "$(SRCDIR)\merge3.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\moderate$O" : "$(OX)\moderate_.c" "$(OX)\moderate.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\moderate_.c"
"$(OX)\moderate_.c" : "$(SRCDIR)\moderate.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\name$O" : "$(OX)\name_.c" "$(OX)\name.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\name_.c"
"$(OX)\name_.c" : "$(SRCDIR)\name.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\path$O" : "$(OX)\path_.c" "$(OX)\path.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\path_.c"
"$(OX)\path_.c" : "$(SRCDIR)\path.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\piechart$O" : "$(OX)\piechart_.c" "$(OX)\piechart.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\piechart_.c"
"$(OX)\piechart_.c" : "$(SRCDIR)\piechart.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\pivot$O" : "$(OX)\pivot_.c" "$(OX)\pivot.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\pivot_.c"
"$(OX)\pivot_.c" : "$(SRCDIR)\pivot.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\popen$O" : "$(OX)\popen_.c" "$(OX)\popen.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\popen_.c"
"$(OX)\popen_.c" : "$(SRCDIR)\popen.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\pqueue$O" : "$(OX)\pqueue_.c" "$(OX)\pqueue.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\pqueue_.c"
"$(OX)\pqueue_.c" : "$(SRCDIR)\pqueue.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\printf$O" : "$(OX)\printf_.c" "$(OX)\printf.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\printf_.c"
"$(OX)\printf_.c" : "$(SRCDIR)\printf.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\publish$O" : "$(OX)\publish_.c" "$(OX)\publish.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\publish_.c"
"$(OX)\publish_.c" : "$(SRCDIR)\publish.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\purge$O" : "$(OX)\purge_.c" "$(OX)\purge.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\purge_.c"
"$(OX)\purge_.c" : "$(SRCDIR)\purge.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\rebuild$O" : "$(OX)\rebuild_.c" "$(OX)\rebuild.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\rebuild_.c"
"$(OX)\rebuild_.c" : "$(SRCDIR)\rebuild.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\regexp$O" : "$(OX)\regexp_.c" "$(OX)\regexp.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\regexp_.c"
"$(OX)\regexp_.c" : "$(SRCDIR)\regexp.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\repolist$O" : "$(OX)\repolist_.c" "$(OX)\repolist.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\repolist_.c"
"$(OX)\repolist_.c" : "$(SRCDIR)\repolist.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\report$O" : "$(OX)\report_.c" "$(OX)\report.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\report_.c"
"$(OX)\report_.c" : "$(SRCDIR)\report.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\rss$O" : "$(OX)\rss_.c" "$(OX)\rss.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\rss_.c"
"$(OX)\rss_.c" : "$(SRCDIR)\rss.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\schema$O" : "$(OX)\schema_.c" "$(OX)\schema.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\schema_.c"
"$(OX)\schema_.c" : "$(SRCDIR)\schema.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\search$O" : "$(OX)\search_.c" "$(OX)\search.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\search_.c"
"$(OX)\search_.c" : "$(SRCDIR)\search.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\security_audit$O" : "$(OX)\security_audit_.c" "$(OX)\security_audit.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\security_audit_.c"
"$(OX)\security_audit_.c" : "$(SRCDIR)\security_audit.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\setup$O" : "$(OX)\setup_.c" "$(OX)\setup.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\setup_.c"
"$(OX)\setup_.c" : "$(SRCDIR)\setup.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\setupuser$O" : "$(OX)\setupuser_.c" "$(OX)\setupuser.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\setupuser_.c"
"$(OX)\setupuser_.c" : "$(SRCDIR)\setupuser.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\sha1$O" : "$(OX)\sha1_.c" "$(OX)\sha1.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\sha1_.c"
"$(OX)\sha1_.c" : "$(SRCDIR)\sha1.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\sha1hard$O" : "$(OX)\sha1hard_.c" "$(OX)\sha1hard.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\sha1hard_.c"
"$(OX)\sha1hard_.c" : "$(SRCDIR)\sha1hard.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\sha3$O" : "$(OX)\sha3_.c" "$(OX)\sha3.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\sha3_.c"
"$(OX)\sha3_.c" : "$(SRCDIR)\sha3.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\shun$O" : "$(OX)\shun_.c" "$(OX)\shun.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\shun_.c"
"$(OX)\shun_.c" : "$(SRCDIR)\shun.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\sitemap$O" : "$(OX)\sitemap_.c" "$(OX)\sitemap.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\sitemap_.c"
"$(OX)\sitemap_.c" : "$(SRCDIR)\sitemap.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\skins$O" : "$(OX)\skins_.c" "$(OX)\skins.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\skins_.c"
"$(OX)\skins_.c" : "$(SRCDIR)\skins.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\smtp$O" : "$(OX)\smtp_.c" "$(OX)\smtp.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\smtp_.c"
"$(OX)\smtp_.c" : "$(SRCDIR)\smtp.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\sqlcmd$O" : "$(OX)\sqlcmd_.c" "$(OX)\sqlcmd.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\sqlcmd_.c"
"$(OX)\sqlcmd_.c" : "$(SRCDIR)\sqlcmd.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\stash$O" : "$(OX)\stash_.c" "$(OX)\stash.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\stash_.c"
"$(OX)\stash_.c" : "$(SRCDIR)\stash.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\stat$O" : "$(OX)\stat_.c" "$(OX)\stat.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\stat_.c"
"$(OX)\stat_.c" : "$(SRCDIR)\stat.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\statrep$O" : "$(OX)\statrep_.c" "$(OX)\statrep.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\statrep_.c"
"$(OX)\statrep_.c" : "$(SRCDIR)\statrep.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\style$O" : "$(OX)\style_.c" "$(OX)\style.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\style_.c"
"$(OX)\style_.c" : "$(SRCDIR)\style.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\sync$O" : "$(OX)\sync_.c" "$(OX)\sync.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\sync_.c"
"$(OX)\sync_.c" : "$(SRCDIR)\sync.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\tag$O" : "$(OX)\tag_.c" "$(OX)\tag.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\tag_.c"
"$(OX)\tag_.c" : "$(SRCDIR)\tag.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\tar$O" : "$(OX)\tar_.c" "$(OX)\tar.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\tar_.c"
"$(OX)\tar_.c" : "$(SRCDIR)\tar.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\terminal$O" : "$(OX)\terminal_.c" "$(OX)\terminal.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\terminal_.c"
"$(OX)\terminal_.c" : "$(SRCDIR)\terminal.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\th_main$O" : "$(OX)\th_main_.c" "$(OX)\th_main.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\th_main_.c"
"$(OX)\th_main_.c" : "$(SRCDIR)\th_main.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\timeline$O" : "$(OX)\timeline_.c" "$(OX)\timeline.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\timeline_.c"
"$(OX)\timeline_.c" : "$(SRCDIR)\timeline.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\tkt$O" : "$(OX)\tkt_.c" "$(OX)\tkt.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\tkt_.c"
"$(OX)\tkt_.c" : "$(SRCDIR)\tkt.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\tktsetup$O" : "$(OX)\tktsetup_.c" "$(OX)\tktsetup.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\tktsetup_.c"
"$(OX)\tktsetup_.c" : "$(SRCDIR)\tktsetup.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\undo$O" : "$(OX)\undo_.c" "$(OX)\undo.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\undo_.c"
"$(OX)\undo_.c" : "$(SRCDIR)\undo.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\unicode$O" : "$(OX)\unicode_.c" "$(OX)\unicode.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\unicode_.c"
"$(OX)\unicode_.c" : "$(SRCDIR)\unicode.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\unversioned$O" : "$(OX)\unversioned_.c" "$(OX)\unversioned.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\unversioned_.c"
"$(OX)\unversioned_.c" : "$(SRCDIR)\unversioned.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\update$O" : "$(OX)\update_.c" "$(OX)\update.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\update_.c"
"$(OX)\update_.c" : "$(SRCDIR)\update.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\url$O" : "$(OX)\url_.c" "$(OX)\url.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\url_.c"
"$(OX)\url_.c" : "$(SRCDIR)\url.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\user$O" : "$(OX)\user_.c" "$(OX)\user.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\user_.c"
"$(OX)\user_.c" : "$(SRCDIR)\user.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\utf8$O" : "$(OX)\utf8_.c" "$(OX)\utf8.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\utf8_.c"
"$(OX)\utf8_.c" : "$(SRCDIR)\utf8.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\util$O" : "$(OX)\util_.c" "$(OX)\util.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\util_.c"
"$(OX)\util_.c" : "$(SRCDIR)\util.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\verify$O" : "$(OX)\verify_.c" "$(OX)\verify.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\verify_.c"
"$(OX)\verify_.c" : "$(SRCDIR)\verify.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\vfile$O" : "$(OX)\vfile_.c" "$(OX)\vfile.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\vfile_.c"
"$(OX)\vfile_.c" : "$(SRCDIR)\vfile.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\webmail$O" : "$(OX)\webmail_.c" "$(OX)\webmail.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\webmail_.c"
"$(OX)\webmail_.c" : "$(SRCDIR)\webmail.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\wiki$O" : "$(OX)\wiki_.c" "$(OX)\wiki.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\wiki_.c"
"$(OX)\wiki_.c" : "$(SRCDIR)\wiki.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\wikiformat$O" : "$(OX)\wikiformat_.c" "$(OX)\wikiformat.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\wikiformat_.c"
"$(OX)\wikiformat_.c" : "$(SRCDIR)\wikiformat.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\winfile$O" : "$(OX)\winfile_.c" "$(OX)\winfile.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\winfile_.c"
"$(OX)\winfile_.c" : "$(SRCDIR)\winfile.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\winhttp$O" : "$(OX)\winhttp_.c" "$(OX)\winhttp.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\winhttp_.c"
"$(OX)\winhttp_.c" : "$(SRCDIR)\winhttp.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\wysiwyg$O" : "$(OX)\wysiwyg_.c" "$(OX)\wysiwyg.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\wysiwyg_.c"
"$(OX)\wysiwyg_.c" : "$(SRCDIR)\wysiwyg.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\xfer$O" : "$(OX)\xfer_.c" "$(OX)\xfer.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\xfer_.c"
"$(OX)\xfer_.c" : "$(SRCDIR)\xfer.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\xfersetup$O" : "$(OX)\xfersetup_.c" "$(OX)\xfersetup.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\xfersetup_.c"
"$(OX)\xfersetup_.c" : "$(SRCDIR)\xfersetup.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\zip$O" : "$(OX)\zip_.c" "$(OX)\zip.h"
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\zip_.c"
"$(OX)\zip_.c" : "$(SRCDIR)\zip.c"
"$(OBJDIR)\translate$E" $** > $@
"$(OX)\fossil.res" : "$(B)\win\fossil.rc"
$(RCC) /fo $@ $**
"$(OX)\headers": "$(OBJDIR)\makeheaders$E" "$(OX)\page_index.h" "$(OX)\builtin_data.h" "$(OX)\VERSION.h"
"$(OBJDIR)\makeheaders$E" "$(OX)\add_.c":"$(OX)\add.h" \
"$(OX)\ajax_.c":"$(OX)\ajax.h" \
"$(OX)\alerts_.c":"$(OX)\alerts.h" \
"$(OX)\allrepo_.c":"$(OX)\allrepo.h" \
"$(OX)\attach_.c":"$(OX)\attach.h" \
"$(OX)\backlink_.c":"$(OX)\backlink.h" \
"$(OX)\backoffice_.c":"$(OX)\backoffice.h" \
"$(OX)\bag_.c":"$(OX)\bag.h" \
"$(OX)\bisect_.c":"$(OX)\bisect.h" \
"$(OX)\blob_.c":"$(OX)\blob.h" \
"$(OX)\branch_.c":"$(OX)\branch.h" \
"$(OX)\browse_.c":"$(OX)\browse.h" \
"$(OX)\builtin_.c":"$(OX)\builtin.h" \
"$(OX)\bundle_.c":"$(OX)\bundle.h" \
"$(OX)\cache_.c":"$(OX)\cache.h" \
"$(OX)\capabilities_.c":"$(OX)\capabilities.h" \
"$(OX)\captcha_.c":"$(OX)\captcha.h" \
"$(OX)\cgi_.c":"$(OX)\cgi.h" \
"$(OX)\checkin_.c":"$(OX)\checkin.h" \
"$(OX)\checkout_.c":"$(OX)\checkout.h" \
"$(OX)\clearsign_.c":"$(OX)\clearsign.h" \
"$(OX)\clone_.c":"$(OX)\clone.h" \
"$(OX)\comformat_.c":"$(OX)\comformat.h" \
"$(OX)\configure_.c":"$(OX)\configure.h" \
"$(OX)\content_.c":"$(OX)\content.h" \
"$(OX)\cookies_.c":"$(OX)\cookies.h" \
"$(OX)\db_.c":"$(OX)\db.h" \
"$(OX)\delta_.c":"$(OX)\delta.h" \
"$(OX)\deltacmd_.c":"$(OX)\deltacmd.h" \
"$(OX)\deltafunc_.c":"$(OX)\deltafunc.h" \
"$(OX)\descendants_.c":"$(OX)\descendants.h" \
"$(OX)\diff_.c":"$(OX)\diff.h" \
"$(OX)\diffcmd_.c":"$(OX)\diffcmd.h" \
"$(OX)\dispatch_.c":"$(OX)\dispatch.h" \
"$(OX)\doc_.c":"$(OX)\doc.h" \
"$(OX)\encode_.c":"$(OX)\encode.h" \
"$(OX)\etag_.c":"$(OX)\etag.h" \
"$(OX)\event_.c":"$(OX)\event.h" \
"$(OX)\export_.c":"$(OX)\export.h" \
"$(OX)\extcgi_.c":"$(OX)\extcgi.h" \
"$(OX)\file_.c":"$(OX)\file.h" \
"$(OX)\fileedit_.c":"$(OX)\fileedit.h" \
"$(OX)\finfo_.c":"$(OX)\finfo.h" \
"$(OX)\foci_.c":"$(OX)\foci.h" \
"$(OX)\forum_.c":"$(OX)\forum.h" \
"$(OX)\fshell_.c":"$(OX)\fshell.h" \
"$(OX)\fusefs_.c":"$(OX)\fusefs.h" \
"$(OX)\fuzz_.c":"$(OX)\fuzz.h" \
"$(OX)\glob_.c":"$(OX)\glob.h" \
"$(OX)\graph_.c":"$(OX)\graph.h" \
"$(OX)\gzip_.c":"$(OX)\gzip.h" \
"$(OX)\hname_.c":"$(OX)\hname.h" \
"$(OX)\http_.c":"$(OX)\http.h" \
"$(OX)\http_socket_.c":"$(OX)\http_socket.h" \
"$(OX)\http_ssl_.c":"$(OX)\http_ssl.h" \
"$(OX)\http_transport_.c":"$(OX)\http_transport.h" \
"$(OX)\import_.c":"$(OX)\import.h" \
"$(OX)\info_.c":"$(OX)\info.h" \
"$(OX)\json_.c":"$(OX)\json.h" \
"$(OX)\json_artifact_.c":"$(OX)\json_artifact.h" \
"$(OX)\json_branch_.c":"$(OX)\json_branch.h" \
"$(OX)\json_config_.c":"$(OX)\json_config.h" \
"$(OX)\json_diff_.c":"$(OX)\json_diff.h" \
"$(OX)\json_dir_.c":"$(OX)\json_dir.h" \
"$(OX)\json_finfo_.c":"$(OX)\json_finfo.h" \
"$(OX)\json_login_.c":"$(OX)\json_login.h" \
"$(OX)\json_query_.c":"$(OX)\json_query.h" \
"$(OX)\json_report_.c":"$(OX)\json_report.h" \
"$(OX)\json_status_.c":"$(OX)\json_status.h" \
"$(OX)\json_tag_.c":"$(OX)\json_tag.h" \
"$(OX)\json_timeline_.c":"$(OX)\json_timeline.h" \
"$(OX)\json_user_.c":"$(OX)\json_user.h" \
"$(OX)\json_wiki_.c":"$(OX)\json_wiki.h" \
"$(OX)\leaf_.c":"$(OX)\leaf.h" \
"$(OX)\loadctrl_.c":"$(OX)\loadctrl.h" \
"$(OX)\login_.c":"$(OX)\login.h" \
"$(OX)\lookslike_.c":"$(OX)\lookslike.h" \
"$(OX)\main_.c":"$(OX)\main.h" \
"$(OX)\manifest_.c":"$(OX)\manifest.h" \
"$(OX)\markdown_.c":"$(OX)\markdown.h" \
"$(OX)\markdown_html_.c":"$(OX)\markdown_html.h" \
"$(OX)\md5_.c":"$(OX)\md5.h" \
"$(OX)\merge_.c":"$(OX)\merge.h" \
"$(OX)\merge3_.c":"$(OX)\merge3.h" \
"$(OX)\moderate_.c":"$(OX)\moderate.h" \
"$(OX)\name_.c":"$(OX)\name.h" \
"$(OX)\path_.c":"$(OX)\path.h" \
"$(OX)\piechart_.c":"$(OX)\piechart.h" \
"$(OX)\pivot_.c":"$(OX)\pivot.h" \
"$(OX)\popen_.c":"$(OX)\popen.h" \
"$(OX)\pqueue_.c":"$(OX)\pqueue.h" \
"$(OX)\printf_.c":"$(OX)\printf.h" \
"$(OX)\publish_.c":"$(OX)\publish.h" \
"$(OX)\purge_.c":"$(OX)\purge.h" \
"$(OX)\rebuild_.c":"$(OX)\rebuild.h" \
"$(OX)\regexp_.c":"$(OX)\regexp.h" \
"$(OX)\repolist_.c":"$(OX)\repolist.h" \
"$(OX)\report_.c":"$(OX)\report.h" \
"$(OX)\rss_.c":"$(OX)\rss.h" \
"$(OX)\schema_.c":"$(OX)\schema.h" \
"$(OX)\search_.c":"$(OX)\search.h" \
"$(OX)\security_audit_.c":"$(OX)\security_audit.h" \
"$(OX)\setup_.c":"$(OX)\setup.h" \
"$(OX)\setupuser_.c":"$(OX)\setupuser.h" \
"$(OX)\sha1_.c":"$(OX)\sha1.h" \
"$(OX)\sha1hard_.c":"$(OX)\sha1hard.h" \
"$(OX)\sha3_.c":"$(OX)\sha3.h" \
"$(OX)\shun_.c":"$(OX)\shun.h" \
"$(OX)\sitemap_.c":"$(OX)\sitemap.h" \
"$(OX)\skins_.c":"$(OX)\skins.h" \
"$(OX)\smtp_.c":"$(OX)\smtp.h" \
"$(OX)\sqlcmd_.c":"$(OX)\sqlcmd.h" \
"$(OX)\stash_.c":"$(OX)\stash.h" \
"$(OX)\stat_.c":"$(OX)\stat.h" \
"$(OX)\statrep_.c":"$(OX)\statrep.h" \
"$(OX)\style_.c":"$(OX)\style.h" \
"$(OX)\sync_.c":"$(OX)\sync.h" \
"$(OX)\tag_.c":"$(OX)\tag.h" \
"$(OX)\tar_.c":"$(OX)\tar.h" \
"$(OX)\terminal_.c":"$(OX)\terminal.h" \
"$(OX)\th_main_.c":"$(OX)\th_main.h" \
"$(OX)\timeline_.c":"$(OX)\timeline.h" \
"$(OX)\tkt_.c":"$(OX)\tkt.h" \
"$(OX)\tktsetup_.c":"$(OX)\tktsetup.h" \
"$(OX)\undo_.c":"$(OX)\undo.h" \
"$(OX)\unicode_.c":"$(OX)\unicode.h" \
"$(OX)\unversioned_.c":"$(OX)\unversioned.h" \
"$(OX)\update_.c":"$(OX)\update.h" \
"$(OX)\url_.c":"$(OX)\url.h" \
"$(OX)\user_.c":"$(OX)\user.h" \
"$(OX)\utf8_.c":"$(OX)\utf8.h" \
"$(OX)\util_.c":"$(OX)\util.h" \
"$(OX)\verify_.c":"$(OX)\verify.h" \
"$(OX)\vfile_.c":"$(OX)\vfile.h" \
"$(OX)\webmail_.c":"$(OX)\webmail.h" \
"$(OX)\wiki_.c":"$(OX)\wiki.h" \
"$(OX)\wikiformat_.c":"$(OX)\wikiformat.h" \
"$(OX)\winfile_.c":"$(OX)\winfile.h" \
"$(OX)\winhttp_.c":"$(OX)\winhttp.h" \
"$(OX)\wysiwyg_.c":"$(OX)\wysiwyg.h" \
"$(OX)\xfer_.c":"$(OX)\xfer.h" \
"$(OX)\xfersetup_.c":"$(OX)\xfersetup.h" \
"$(OX)\zip_.c":"$(OX)\zip.h" \
"$(SRCDIR)\sqlite3.h" \
"$(SRCDIR)\th.h" \
"$(OX)\VERSION.h" \
"$(SRCDIR)\cson_amalgamation.h"
@copy /Y nul: $@
|
| ︙ | ︙ | |||
181 182 183 184 185 186 187 | :skip_setupVisualStudio %_VECHO% VcInstallDir = '%VCINSTALLDIR%' REM REM NOTE: Attempt to create the build output directory, if necessary. REM | | > > > > > > > > > > > > > > > > > | > > > > > > | | > | | | | > > > | | | > | > > > > | > > > > | > > | > > > > > > > | 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 |
:skip_setupVisualStudio
%_VECHO% VcInstallDir = '%VCINSTALLDIR%'
REM
REM NOTE: Attempt to create the build output directory, if necessary.
REM
IF DEFINED BUILDDIR (
IF DEFINED BUILDSUFFIX (
CALL :fn_FindVarInVar BUILDSUFFIX BUILDDIR
IF ERRORLEVEL 1 (
REM
REM NOTE: The build suffix is already present, do nothing.
REM
) ELSE (
REM
REM NOTE: The build suffix is not present, add it now.
REM
SET BUILDDIR=%BUILDDIR%%BUILDSUFFIX%
)
CALL :fn_ResetErrorLevel
)
) ELSE (
SET BUILDDIR=%ROOT%\msvcbld%BUILDSUFFIX%
)
%_VECHO% BuildSuffix = '%BUILDSUFFIX%'
%_VECHO% BuildDir = '%BUILDDIR%'
IF NOT EXIST "%BUILDDIR%" (
%__ECHO% MKDIR "%BUILDDIR%"
IF ERRORLEVEL 1 (
ECHO Could not make directory "%BUILDDIR%".
GOTO errors
)
)
REM
REM NOTE: Attempt to change to the created build output directory so that
REM the generated files will be placed there, if needed.
REM
%__ECHO2% PUSHD "%BUILDDIR%"
IF ERRORLEVEL 1 (
ECHO Could not change to directory "%BUILDDIR%".
GOTO errors
)
SET NEED_POPD=1
REM
REM NOTE: If requested, setup the build environment to refer to the Windows
REM SDK v7.1A, which is required if the binaries are being built with
REM Visual Studio 201x and need to work on Windows XP.
REM
IF DEFINED USE_V110SDK71A (
%_AECHO% Forcing use of the Windows SDK v7.1A...
CALL :fn_UseV110Sdk71A
)
%_VECHO% Path = '%PATH%'
%_VECHO% Include = '%INCLUDE%'
%_VECHO% Lib = '%LIB%'
%_VECHO% Tools = '%TOOLS%'
%_VECHO% Root = '%ROOT%'
%_VECHO% NmakeArgs = '%NMAKE_ARGS%'
REM
REM NOTE: Attempt to execute NMAKE for the Fossil MSVC makefile, passing
REM anything extra from our command line along (e.g. extra options).
REM Also, pass the base directory of the Fossil source tree as this
REM allows an out-of-source-tree build.
REM
%__ECHO% nmake /f "%TOOLS%\Makefile.msc" B="%ROOT%" %NMAKE_ARGS% %*
IF ERRORLEVEL 1 (
GOTO errors
)
REM
REM NOTE: Attempt to restore the previously saved directory, if needed.
REM
IF DEFINED NEED_POPD (
%__ECHO2% POPD
IF ERRORLEVEL 1 (
ECHO Could not restore directory.
GOTO errors
)
CALL :fn_UnsetVariable NEED_POPD
)
GOTO no_errors
:fn_UseV110Sdk71A
IF "%PROCESSOR_ARCHITECTURE%" == "x86" GOTO set_v110Sdk71A_x86
SET PFILES_SDK71A=%ProgramFiles(x86)%
GOTO set_v110Sdk71A_done
:set_v110Sdk71A_x86
SET PFILES_SDK71A=%ProgramFiles%
:set_v110Sdk71A_done
SET PATH=%PFILES_SDK71A%\Microsoft SDKs\Windows\7.1A\Bin;%PATH%
SET INCLUDE=%PFILES_SDK71A%\Microsoft SDKs\Windows\7.1A\Include;%INCLUDE%
IF "%PLATFORM%" == "x64" GOTO set_v110Sdk71A_lib_x64
SET LIB=%PFILES_SDK71A%\Microsoft SDKs\Windows\7.1A\Lib;%LIB%
GOTO set_v110Sdk71A_lib_done
:set_v110Sdk71A_lib_x64
SET LIB=%PFILES_SDK71A%\Microsoft SDKs\Windows\7.1A\Lib\x64;%LIB%
:set_v110Sdk71A_lib_done
CALL :fn_UnsetVariable PFILES_SDK71A
SET NMAKE_ARGS=%NMAKE_ARGS% FOSSIL_ENABLE_WINXP=1
GOTO :EOF
:fn_FindVarInVar
IF NOT DEFINED %1 GOTO :EOF
IF NOT DEFINED %2 GOTO :EOF
SETLOCAL
CALL :fn_UnsetVariable VALUE
SET __ECHO_CMD=ECHO %%%2%% ^^^| FIND /I "%%%1%%"
FOR /F "delims=" %%V IN ('%__ECHO_CMD%') DO (
SET VALUE=%%V
)
IF DEFINED VALUE (
CALL :fn_SetErrorLevel
) ELSE (
CALL :fn_ResetErrorLevel
)
ENDLOCAL
GOTO :EOF
:fn_UnsetVariable
SETLOCAL
SET VALUE=%1
IF DEFINED VALUE (
SET VALUE=
ENDLOCAL
|
| ︙ | ︙ |
1 2 3 4 5 6 | <title>How CGI Works In Fossil</title> <h2>Introduction</h2><blockquote> <p>CGI or "Common Gateway Interface" is a venerable yet reliable technique for generating dynamic web content. This article gives a quick background on how CGI works and describes how Fossil can act as a CGI service. <p>This is a "how it works" guide. If you just want to set up Fossil | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <title>How CGI Works In Fossil</title> <h2>Introduction</h2><blockquote> <p>CGI or "Common Gateway Interface" is a venerable yet reliable technique for generating dynamic web content. This article gives a quick background on how CGI works and describes how Fossil can act as a CGI service. <p>This is a "how it works" guide. If you just want to set up Fossil as a CGI server, see the [./server/ | Fossil Server Setup] page. </blockquote> <h2>A Quick Review Of CGI</h2><blockquote> <p> An HTTP request is a block of text that is sent by a client application (usually a web browser) and arrives at the web server over a network connection. The HTTP request contains a URL that describes the information being requested. The URL in the HTTP request is typically the same URL |
| ︙ | ︙ | |||
178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
at "/home/www/resps/subdir.fossil" but there is no such repository.
So then it looks at "/home/www/repos/subdir/three.fossil" and finds
a repository. The PATH_INFO is shortened by removing
"subdir/three/" leaving it at just "timeline".
<li> Fossil looks at the rest of PATH_INFO to see that the webpage
requested is "timeline".
</ol>
</blockquote>
<h2>Additional Observations</h2>
<blockquote><ol type="I">
<li><p>
Fossil does not distinguish between the various HTTP methods (GET, PUT,
DELETE, etc). Fossil figures out what it needs to do purely from the
webpage term of the URI.
| > > > > > > > > | 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
at "/home/www/resps/subdir.fossil" but there is no such repository.
So then it looks at "/home/www/repos/subdir/three.fossil" and finds
a repository. The PATH_INFO is shortened by removing
"subdir/three/" leaving it at just "timeline".
<li> Fossil looks at the rest of PATH_INFO to see that the webpage
requested is "timeline".
</ol>
</blockquote>
<h2>Additional CGI Script Options</h2>
<blockquote>
<p>
The CGI script can have additional options used to fine-tune
Fossil's behavior. See the [./cgi.wiki|CGI script documentation]
for details.
</p>
</blockquote>
<h2>Additional Observations</h2>
<blockquote><ol type="I">
<li><p>
Fossil does not distinguish between the various HTTP methods (GET, PUT,
DELETE, etc). Fossil figures out what it needs to do purely from the
webpage term of the URI.
|
| ︙ | ︙ | |||
206 207 208 209 210 211 212 213 214 | separate child Fossil process to handle each request. In other words, CGI is used internally to implement "fossil ui/server". <p> SCGI is processed using the same built-in web server, just modified to parse SCGI requests instead of HTTP requests. Each SCGI request is converted into CGI, then Fossil creates a separate child Fossil process to handle each CGI request. </ol> </blockquote> | > > > > | 214 215 216 217 218 219 220 221 222 223 224 225 226 | separate child Fossil process to handle each request. In other words, CGI is used internally to implement "fossil ui/server". <p> SCGI is processed using the same built-in web server, just modified to parse SCGI requests instead of HTTP requests. Each SCGI request is converted into CGI, then Fossil creates a separate child Fossil process to handle each CGI request. <li><p> Fossil is itself often launched using CGI. But Fossil can also then turn around and launch [./serverext.wiki|sub-CGI scripts to implement extensions]. </ol> </blockquote> |
| ︙ | ︙ | |||
49 50 51 52 53 54 55 | how <div class='fossil-doc'> this works. With each new release, the "releases" variable in the javascript on the [/uv/download.js?mimetype=text/plain|download.js] page is edited (using "[/help?cmd=uv|fossil uv edit download.js]") to add details of the release. | | | 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | how <div class='fossil-doc'> this works. With each new release, the "releases" variable in the javascript on the [/uv/download.js?mimetype=text/plain|download.js] page is edited (using "[/help?cmd=uv|fossil uv edit download.js]") to add details of the release. When the JavaScript in the "download.js" file runs, it requests a listing of all unversioned content using the /juvlist URL. ([/juvlist|sample /juvlist output]). The content of the download page is constructed by matching unversioned files against regular expressions in the "releases" variable. Build products need to be constructed on different machines. The precompiled binary for Linux is compiled on Linux, the precompiled binary for Windows |
| ︙ | ︙ |
1 2 3 4 | <title>Adding Features To Fossil</title> <h2>1.0 Introduction</h2> | | | > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <title>Adding Features To Fossil</title> <h2>1.0 Introduction</h2> This article provides a brief overview of how to write new C-code code that extends or enhances the core Fossil binary. New features can be added to a Fossil server using [./serverext.wiki|external CGI programs], but that is not what this article is about. This article focuses on how to make changes to Fossil itself. <h2>2.0 Programming Language</h2> Fossil is written in C-89. There are specific [./style.wiki | style guidelines] that are required for any new code that will be accepted into the Fossil core. But, of course, if you are writing an extension just for yourself, you can use any programming style you want. |
| ︙ | ︙ | |||
94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
the makefiles, you should be able to recompile Fossil and have it include
your new source file, even before you source file contains any code.
It is recommended that you try this.
Be sure to [/help/add|fossil add] your new source file to the self-hosting
Fossil repository and then [/help/commit|commit] your changes!
<h2>4.0 Creating A New Command</h2>
By "commands" we mean the keywords that follow "fossil" when invoking
Fossil from the command-line. So, for example, in
<b>fossil diff xyzzy.c</b>
| > | 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
the makefiles, you should be able to recompile Fossil and have it include
your new source file, even before you source file contains any code.
It is recommended that you try this.
Be sure to [/help/add|fossil add] your new source file to the self-hosting
Fossil repository and then [/help/commit|commit] your changes!
<a name="newcmd"></a>
<h2>4.0 Creating A New Command</h2>
By "commands" we mean the keywords that follow "fossil" when invoking
Fossil from the command-line. So, for example, in
<b>fossil diff xyzzy.c</b>
|
| ︙ | ︙ | |||
159 160 161 162 163 164 165 166 167 168 169 170 171 172 | Fossil for parsing command-line options and for opening and accessing and manipulating the repository and the working check-out. Study implementations of existing commands to get an idea of how things are done. You can easily find the implementations of existing commands by searching for "COMMAND: <i>name</i>" in the files of the "src/" directory. <h2>5.0 Creating A New Web Page</h2> As with commands, new webpages can be added simply by inserting a function that generates the webpage together with a special header comment. A template follows: <blockquote><verbatim> | > | 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | Fossil for parsing command-line options and for opening and accessing and manipulating the repository and the working check-out. Study implementations of existing commands to get an idea of how things are done. You can easily find the implementations of existing commands by searching for "COMMAND: <i>name</i>" in the files of the "src/" directory. <a name="newpage"></a> <h2>5.0 Creating A New Web Page</h2> As with commands, new webpages can be added simply by inserting a function that generates the webpage together with a special header comment. A template follows: <blockquote><verbatim> |
| ︙ | ︙ | |||
209 210 211 212 213 214 215 | works. <h2>6.0 See Also</h2> * [./makefile.wiki|The Fossil Build Process] * [./tech_overview.wiki|A Technical Overview Of Fossil] * [./contribute.wiki|Contributing To The Fossil Project] | > | 217 218 219 220 221 222 223 224 | works. <h2>6.0 See Also</h2> * [./makefile.wiki|The Fossil Build Process] * [./tech_overview.wiki|A Technical Overview Of Fossil] * [./contribute.wiki|Contributing To The Fossil Project] * [./serverext.wiki|Adding CGI Extensions To A Fossil Server] |
| ︙ | ︙ | |||
10 11 12 13 14 15 16 | * [Wiki](./wikitheory.wiki) page changes * New and edited [forum](./forum.wiki) posts * Announcements Subscribers can elect to receive emails as soon as these events happen, or they can receive a daily digest of the events instead. | | | | | 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 | * [Wiki](./wikitheory.wiki) page changes * New and edited [forum](./forum.wiki) posts * Announcements Subscribers can elect to receive emails as soon as these events happen, or they can receive a daily digest of the events instead. Email alerts are sent by a [Fossil server](./server/), which must be [set up](#quick) by the Fossil administrator to send email. Email alerts do not currently work if you are only using Fossil from the command line. A bit of terminology: Fossil uses the terms "email alerts" and "notifications" interchangeably. We stick to the former term in this document except when referring to parts of the Fossil UI still using the latter term. ## Setup Prerequisites Much of this document describes how to set up Fossil's email alert system. To follow this guide, you will need a Fossil UI browser window open to the [Admin → Notification](/setup_notification) Fossil UI screen on the Fossil server that will be sending these email alerts, logged in as a user with [**Admin** capability](./caps/ref.html#a). It is not possible to work on a clone of the server's repository and push the configuration changes up to that repo as an Admin user, [on purpose](#backup). **Important:** Do not confuse that screen with Admin → Email-Server, which sets up a different subsystem within Fossil. That feature is related to this document's topic, but it is currently incomplete, so we do not cover it at this time. |
| ︙ | ︙ | |||
62 63 64 65 66 67 68 | `chroot` feature to wall Fossil off from the rest of the machine, it's fairly simple to set up email alerts. (Otherwise, skip [ahead](#advanced) to the sections on advanced email service setup.) This is our "quick setup" option even though setting up an SMTP mail | | | 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
`chroot` feature to wall Fossil off from the rest of the machine, it's
fairly simple to set up email alerts.
(Otherwise, skip [ahead](#advanced) to the sections on advanced email
service setup.)
This is our "quick setup" option even though setting up an SMTP mail
server is not trivial, because there are many other reasons to have such
a server set up already: internal project email service, `cron`
notifications, server status monitoring notifications...
With that out of the way, the Fossil-specific steps are easy:
1. Go to [Admin → Notification](/setup_notification) and fill out all
of the **Required** fields:
|
| ︙ | ︙ | |||
158 159 160 161 162 163 164 | If you are seeing the following complaint from Fossil: <blockquote> Use a different login with greater privilege than FOO to access /subscribe </blockquote> | | > | 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | If you are seeing the following complaint from Fossil: <blockquote> Use a different login with greater privilege than FOO to access /subscribe </blockquote> ...then the repository's administrator forgot to give the [**EmailAlert** capability][cap7] to that user or to a user category that the user is a member of. After a subscriber signs up for alerts for the first time, a single verification email is sent to that subscriber's given email address. The new subscriber must click a link in that email in order to activate the subscription. |
| ︙ | ︙ | |||
187 188 189 190 191 192 193 194 195 196 197 198 | Those with Fossil repository logins can adjust their email alert settings by visiting the `/alerts` page on the repository. With the default skin, you can get there by clicking the "Logout" link in the upper right corner of any Fossil UI page then clicking the "Email Alerts" link. That link is also available via the Sitemap (`/sitemap`) and via the default skin's hamburger menu (☰). <a id="unsub" name="unsubscribe"></a> ### Unsubscribing To unsubscribe from alerts, visit the `/alerts` page on the repository, | > > | | 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | Those with Fossil repository logins can adjust their email alert settings by visiting the `/alerts` page on the repository. With the default skin, you can get there by clicking the "Logout" link in the upper right corner of any Fossil UI page then clicking the "Email Alerts" link. That link is also available via the Sitemap (`/sitemap`) and via the default skin's hamburger menu (☰). [cap7]: ./caps/ref.html#7 <a id="unsub" name="unsubscribe"></a> ### Unsubscribing To unsubscribe from alerts, visit the `/alerts` page on the repository, click the "Unsubscribe" button, then check the "Unsubscribe" checkbox to verify your action and press the "Unsubscribe" button a second time. This interlock is intended to prevent accidental unsubscription. <a id="test"></a> ### Test Email Service |
| ︙ | ︙ | |||
221 222 223 224 225 226 227 | That command assumes that your project contains a "readme" file, but of course it does, because you have followed the [Programming Style Guide Checklist][cl], right? Right. [cl]: https://sendgrid.com/blog/programming-style-guide-checklist/ | | | | | < | | > > > > | 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 | That command assumes that your project contains a "readme" file, but of course it does, because you have followed the [Programming Style Guide Checklist][cl], right? Right. [cl]: https://sendgrid.com/blog/programming-style-guide-checklist/ <a id="cap7" name="ucap"></a> ### User Capabilities Once email alerts are working, you may need to [adjust the default user capabilities](./caps/) to give "[Email Alerts][cap7]" capability to any [user category](./caps/#ucat) or [individual user](./caps/#ucap) that needs to use the subscription setup pages, `/subscribe` and `/alerts`. [**Admin**][capa] and [**Setup**][caps] users always have this capability. To allow any passer-by on the Internet to subscribe, give the "Email Alerts" capability to the "nobody" user category. To require that a person solve a simple CAPTCHA first, give that capability to the "anonymous" user category instead. [capa]: ./caps/ref.html#a [caps]: ./caps/ref.html#s <a id="first" name="frist"></a> ### First Post I suggest taking the time to compose a suitable introductory message especially for your project's forum, one which a new user would find |
| ︙ | ︙ | |||
566 567 568 569 570 571 572 | * The [`/subscribers`](/help?cmd=/subscribers) page <a id="design"></a> ## Design of Email Alerts This section describes the low-level design of the email alert system in | | | 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 | * The [`/subscribers`](/help?cmd=/subscribers) page <a id="design"></a> ## Design of Email Alerts This section describes the low-level design of the email alert system in Fossil. This expands on the high-level administration focused material above with minimal repetition. This section assumes expert-level systems knowledge. If the material above sufficed for your purposes, feel free to skip this section, which runs to the end of this document. |
| ︙ | ︙ | |||
710 711 712 713 714 715 716 | ### Internal Processing Flow Almost all of the email alert code is found in the [`src/alerts.c`](/file/src/alerts.c) source file. When email alerts are enabled, a trigger is created in the schema (`email_trigger1`) that adds a new entry to the `PENDING_ALERT` table | | | | 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 | ### Internal Processing Flow Almost all of the email alert code is found in the [`src/alerts.c`](/file/src/alerts.c) source file. When email alerts are enabled, a trigger is created in the schema (`email_trigger1`) that adds a new entry to the `PENDING_ALERT` table every time a row is added to the `EVENT` table. During a `fossil rebuild`, the `EVENT` table is rebuilt from scratch; since we do not want users to get alerts for every historical check-in, the trigger is disabled during `rebuild`. Email alerts are sent out by the `alert_send_alerts()` function, which is normally called automatically due to the `email-autoexec` setting, which defaults to enabled. If that setting is disabled or if the user simply wants to force email alerts to be sent immediately, they can give |
| ︙ | ︙ |
1 2 3 4 5 | <title>Defense Against Spiders</title> The website presented by a Fossil server has many hyperlinks. Even a modest project can have millions of pages in its tree, and many of those pages (for example diffs and annotations | | | | | < | > | | | < | > < | | < < | | | | | | | | | | | | | | | | | | | | | | 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 |
<title>Defense Against Spiders</title>
The website presented by a Fossil server has many hyperlinks.
Even a modest project can have millions of pages in its
tree, and many of those pages (for example diffs and annotations
and ZIP archives of older check-ins) can be expensive to compute.
If a spider or bot tries to walk a website implemented by
Fossil, it can present a crippling bandwidth and CPU load.
The website presented by a Fossil server is intended to be used
interactively by humans, not walked by spiders. This article
describes the techniques used by Fossil to try to welcome human
users while keeping out spiders.
<h2>The Hyperlink User Capability</h2>
Every Fossil web session has a "user". For random passers-by on the internet
(and for spiders) that user is "nobody". The "anonymous" user is also
available for humans who do not wish to identify themselves. The difference
is that "anonymous" requires a login (using a password supplied via
a CAPTCHA) whereas "nobody" does not require a login.
The site administrator can also create logins with
passwords for specific individuals.
Users without the <b>[./caps/ref.html#h | Hyperlink]</b> capability
do not see most Fossil-generated hyperlinks. This is
a simple defense against spiders, since [./caps/#ucat | the "nobody"
user category] does not have this capability by default.
Users must log in (perhaps as
"anonymous") before they can see any of the hyperlinks. A spider
that cannot log into your Fossil repository will be unable to walk
its historical check-ins, create diffs between versions, pull zip
archives, etc. by visiting links, because they aren't there.
A text message appears at the top of each page in this situation to
invite humans to log in as anonymous in order to activate hyperlinks.
Because this required login step is annoying to some,
Fossil provides other techniques for blocking spiders which
are less cumbersome to humans.
<h2>Automatic Hyperlinks Based on UserAgent</h2>
Fossil has the ability to selectively enable hyperlinks for users
that lack the <b>Hyperlink</b> capability based on their UserAgent string in the
HTTP request header and on the browsers ability to run Javascript.
The UserAgent string is a text identifier that is included in the header
of most HTTP requests that identifies the specific maker and version of
the browser (or spider) that generated the request. Typical UserAgent
strings look like this:
<ul>
<li> Mozilla/5.0 (Windows NT 6.1; rv:19.0) Gecko/20100101 Firefox/19.0
<li> Mozilla/4.0 (compatible; MSIE 8.0; Windows_NT 5.1; Trident/4.0)
<li> Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
<li> Wget/1.12 (openbsd4.9)
</ul>
The first two UserAgent strings above identify Firefox 19 and
Internet Explorer 8.0, both running on Windows NT. The third
example is the spider used by Google to index the internet.
The fourth example is the "wget" utility running on OpenBSD.
Thus the first two UserAgent strings above identify the requester
as human whereas the second two identify the requester as a spider.
Note that the UserAgent string is completely under the control
of the requester and so a malicious spider can forge a UserAgent
string that makes it look like a human. But most spiders truly
seem to desire to "play nicely" on the internet and are quite open
about the fact that they are a spider. And so the UserAgent string
provides a good first-guess about whether or not a request originates
from a human or a spider.
In Fossil, under the Admin/Access menu, there is a setting entitled
"<b>Enable hyperlinks for "nobody" based on User-Agent and Javascript</b>".
If this setting is enabled, and if the UserAgent string looks like a
human and not a spider, then Fossil will enable hyperlinks even if
the <b>Hyperlink</b> capability is omitted from the user permissions. This setting
gives humans easy access to the hyperlinks while preventing spiders
from walking the millions of pages on a typical Fossil site.
But the hyperlinks are not enabled directly with the setting above.
Instead, the HTML code that is generated contains anchor tags ("<a>")
without "href=" attributes. Then, JavaScript code is added to the
end of the page that goes back and fills in the "href=" attributes of
the anchor tags with the hyperlink targets, thus enabling the hyperlinks.
This extra step of using JavaScript to enable the hyperlink targets
is a security measure against spiders that forge a human-looking
UserAgent string. Most spiders do not bother to run JavaScript and
so to the spider the empty anchor tag will be useless. But all modern
web browsers implement JavaScript, so hyperlinks will show up
normally for human users.
<h2>Further Defenses</h2>
Recently (as of this writing, in the spring of 2013) the Fossil server
on the SQLite website ([http://www.sqlite.org/src/]) has been hit repeatedly
by Chinese spiders that use forged UserAgent strings to make them look
like normal web browsers and which interpret JavaScript. We do not
believe these attacks to be nefarious since SQLite is public domain
and the attackers could obtain all information they ever wanted to
know about SQLite simply by cloning the repository. Instead, we
believe these "attacks" are coming from "script kiddies". But regardless
of whether or not malice is involved, these attacks do present
an unnecessary load on the server which reduces the responsiveness of
the SQLite website for well-behaved and socially responsible users.
For this reason, additional defenses against
spiders have been put in place.
On the Admin/Access page of Fossil, just below the
"<b>Enable hyperlinks for "nobody" based on User-Agent and Javascript</b>"
setting, there are now two additional sub-settings that can be optionally
enabled to control hyperlinks.
The first sub-setting waits to run the
JavaScript that sets the "href=" attributes on anchor tags until after
at least one "mouseover" event has been detected on the <body>
element of the page. The thinking here is that spiders will not be
simulating mouse motion and so no mouseover events will ever occur and
hence the hyperlinks will never become enabled for spiders.
The second new sub-setting is a delay (in milliseconds) before setting
the "href=" attributes on anchor tags. The default value for this
delay is 10 milliseconds. The idea here is that a spider will try to
render the page immediately, and will not wait for delayed scripts
to be run, thus will never enable the hyperlinks.
These two sub-settings can be used separately or together. If used together,
then the delay timer does not start until after the first mouse movement
is detected.
See also [./loadmgmt.md|Managing Server Load] for a description
of how expensive pages can be disabled when the server is under heavy
load.
<h2>The Ongoing Struggle</h2>
Fossil currently does a very good job of providing easy access to humans
while keeping out troublesome robots and spiders. However, spiders and
bots continue to grow more sophisticated, requiring ever more advanced
defenses. This "arms race" is unlikely to ever end. The developers of
Fossil will continue to try improve the spider defenses of Fossil so
check back from time to time for the latest releases and updates.
Readers of this page who have suggestions on how to improve the spider
defenses in Fossil are invited to submit your ideas to the Fossil Users
forum:
[https://fossil-scm.org/forum].
|
1 2 3 4 5 6 7 8 9 10 11 12 13 | Backoffice ========== This is technical documentation about the internal workings of Fossil. Ordinary Fossil users do not need to know about anything covered by this document. The information here is intended for people who want to enhance or extend the Fossil code, or who just want a deeper understanding of the internal workings of Fossil. What Is The Backoffice ---------------------- The backoffice is a mechanism used by a | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | Backoffice ========== This is technical documentation about the internal workings of Fossil. Ordinary Fossil users do not need to know about anything covered by this document. The information here is intended for people who want to enhance or extend the Fossil code, or who just want a deeper understanding of the internal workings of Fossil. What Is The Backoffice ---------------------- The backoffice is a mechanism used by a [Fossil server](./server/) to do low-priority background work that is not directly related to the user interface. Here are some examples of the kinds of work that backoffice performs: 1. Sending email alerts and notifications 2. Sending out daily digests of email notifications 3. Other background email handling chores 4. Automatic syncing of peer repositories |
| ︙ | ︙ | |||
37 38 39 40 41 42 43 | This happens for every webpage, regardless of how that webpage is launched, and regardless of the purpose of the webpage. This also happens on the server for "[fossil sync](/help?cmd=sync)" and [fossil clone](/help?cmd=clone)" commands which are implemented as web requests - albeit requests that the human user never sees. Web requests can arrive at the Fossil server via direct TCP/IP (for example when Fossil is started using commands like "[fossil server](/help?cmd=server)") | | | | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | This happens for every webpage, regardless of how that webpage is launched, and regardless of the purpose of the webpage. This also happens on the server for "[fossil sync](/help?cmd=sync)" and [fossil clone](/help?cmd=clone)" commands which are implemented as web requests - albeit requests that the human user never sees. Web requests can arrive at the Fossil server via direct TCP/IP (for example when Fossil is started using commands like "[fossil server](/help?cmd=server)") or via [CGI](./server/any/cgi.md) or [SCGI](./server/any/scgi.md) or via SSH. A backoffice process might be started regardless of the origin of the request. The backoffice is not a daemon. Each backoffice process runs for a short while and then exits. This helps keep Fossil easy to manage, since there are no daemons to start and stop. To upgrade Fossil to a new version, you simply replace the older "fossil" executable with the newer one, and |
| ︙ | ︙ | |||
72 73 74 75 76 77 78 | ------------------------------- The automatic backoffice runs are sufficient for most installations. However, the daily digest of email notifications is handled by the backoffice. If a Fossil server can sometimes go more than a day without being accessed, then the automatic backoffice will never run, and the daily digest might not go out until somebody does visit a webpage. | | | 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | ------------------------------- The automatic backoffice runs are sufficient for most installations. However, the daily digest of email notifications is handled by the backoffice. If a Fossil server can sometimes go more than a day without being accessed, then the automatic backoffice will never run, and the daily digest might not go out until somebody does visit a webpage. If this is a problem, an administrator can set up a cron job to periodically run: > fossil backoffice _REPOSITORY_ That command will cause backoffice processing to occur immediately. Note that this is almost never necessary for an internet-facing Fossil repository, since most repositories will get multiple accesses |
| ︙ | ︙ |
| ︙ | ︙ | |||
9 10 11 12 13 14 15 | cryptography. Each block contains a cryptographic hash of the previous block, a timestamp, and transaction data..." [(1)][] By that definition, Fossil is clearly an implementation of blockchain. The blocks are ["manifests" artifacts](./fileformat.wiki#manifest). Each manifest has a SHA1 or SHA3 hash of its parent or parents, | | | | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | cryptography. Each block contains a cryptographic hash of the previous block, a timestamp, and transaction data..." [(1)][] By that definition, Fossil is clearly an implementation of blockchain. The blocks are ["manifests" artifacts](./fileformat.wiki#manifest). Each manifest has a SHA1 or SHA3 hash of its parent or parents, a timestamp, and other transactional data. The repository grows by adding new manifests onto the list. Some people have come to associate blockchain with cryptocurrency, however, and since Fossil has nothing to do with cryptocurrency, the claim that Fossil is built around blockchain is met with skepticism. The key thing to note here is that cryptocurrency implementations like BitCoin are built around blockchain, but they are not synonymous with blockchain. Blockchain is a much broader concept. Blockchain is a mechanism for constructing a distributed ledger of transactions. Yes, you can use a distributed ledger to implement a cryptocurrency, but you can also use a distributed ledger to implement a version control system, and probably many other kinds of applications as well. Blockchain is a much broader idea than cryptocurrency. [(1)]: https://en.wikipedia.org/wiki/Blockchain |
| ︙ | ︙ | |||
237 238 239 240 241 242 243 |
<ol>
<li><p id="offline">By Fossil itself when two users check in children to the same
leaf of a branch, as in Figure 2. If the fork occurs because
autosync is disabled on one or both of the repositories or because
the user doing the check-in has no network connection at the moment
of the commit, Fossil has no way of knowing that it is creating a
| | | | 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
<ol>
<li><p id="offline">By Fossil itself when two users check in children to the same
leaf of a branch, as in Figure 2. If the fork occurs because
autosync is disabled on one or both of the repositories or because
the user doing the check-in has no network connection at the moment
of the commit, Fossil has no way of knowing that it is creating a
fork until the two repositories are later synchronized.</p></li>
<li><p id="dist-clone">By Fossil when the cloning hierarchy is more
than 2 levels deep.
<br><br>
[./sync.wiki|Fossil's synchronization protocol] is a two-party
negotiation; syncs don't automatically propagate up the clone tree
beyond that. Because of that, if you have a master repository and
Alice clones it, then Bobby clones from Alice's repository, a
check-in by Bobby that autosyncs with Alice's repo will <i>not</i>
also autosync with the master repo. The master doesn't get a copy of
Bobby's checkin until Alice <i>separately</i> syncs with the master.
If Carol cloned from the master repo and checks something in that
|
| ︙ | ︙ | |||
589 590 591 592 593 594 595 | mainline of that branch due to some human error. (See <b>fossil amend</b> and the Fossil UI checkin amendment features.) This is a workaround for Fossil's [./shunning.wiki|normal inability to forget history]: we usually don't want to actually <i>remove</i> history, but would like to sometimes set some of it aside under a new label. Because some VCSes can't cope with duplicate branch names, Fossil | | | 589 590 591 592 593 594 595 596 597 598 599 600 | mainline of that branch due to some human error. (See <b>fossil amend</b> and the Fossil UI checkin amendment features.) This is a workaround for Fossil's [./shunning.wiki|normal inability to forget history]: we usually don't want to actually <i>remove</i> history, but would like to sometimes set some of it aside under a new label. Because some VCSes can't cope with duplicate branch names, Fossil collapses such names down on export using the same time stamp based arbitration logic, so that only the branch with the newest checkin gets the branch name in the export. All of the above is true of tags in general, not just branches. |
| ︙ | ︙ | |||
131 132 133 134 135 136 137 | <li><p><i>Unix without running "configure"</i> → if you prefer to avoid running configure, you can also use: <b>make -f Makefile.classic</b>. You may want to make minor edits to Makefile.classic to configure the build for your system. <li><p><i>MinGW 3.x (<u>not</u> 4.x) / MinGW-w64</i> → Use the MinGW makefile: "<b>make -f win/Makefile.mingw</b>". On a Windows box you will need either | | | 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | <li><p><i>Unix without running "configure"</i> → if you prefer to avoid running configure, you can also use: <b>make -f Makefile.classic</b>. You may want to make minor edits to Makefile.classic to configure the build for your system. <li><p><i>MinGW 3.x (<u>not</u> 4.x) / MinGW-w64</i> → Use the MinGW makefile: "<b>make -f win/Makefile.mingw</b>". On a Windows box you will need either Cygwin or MSYS as build environment. On Cygwin, Linux or Darwin you may want to make minor edits to win/Makefile.mingw to configure the cross-compile environment. To enable the native [./th1.md#tclEval | Tcl integration feature], use a command line like the following (all on one line): <b>make -f win/Makefile.mingw FOSSIL_ENABLE_TCL=1 FOSSIL_ENABLE_TCL_STUBS=1 FOSSIL_ENABLE_TCL_PRIVATE_STUBS=1</b> |
| ︙ | ︙ | |||
159 160 161 162 163 164 165 | file "<b>win\buildmsvc.bat</b>" may be used and it will attempt to detect and use the latest installed version of MSVC.<br><br>To enable the optional <a href="https://www.openssl.org/">OpenSSL</a> support, first <a href="https://www.openssl.org/source/">download the official source code for OpenSSL</a> and extract it to an appropriately named "<b>openssl-X.Y.ZA</b>" subdirectory within the local [/tree?ci=trunk&name=compat | compat] directory (e.g. | | | 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | file "<b>win\buildmsvc.bat</b>" may be used and it will attempt to detect and use the latest installed version of MSVC.<br><br>To enable the optional <a href="https://www.openssl.org/">OpenSSL</a> support, first <a href="https://www.openssl.org/source/">download the official source code for OpenSSL</a> and extract it to an appropriately named "<b>openssl-X.Y.ZA</b>" subdirectory within the local [/tree?ci=trunk&name=compat | compat] directory (e.g. "<b>compat/openssl-1.1.1g</b>"), then make sure that some recent <a href="http://www.perl.org/">Perl</a> binaries are installed locally, and finally run one of the following commands: <blockquote><pre> nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin </pre></blockquote> <blockquote><pre> buildmsvc.bat FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin |
| ︙ | ︙ | |||
227 228 229 230 231 232 233 | TCC += -DSQLITE_WITHOUT_ZONEMALLOC TCC += -D_BSD_SOURCE TCC += -DWITHOUT_ICONV TCC += -Dsocketlen_t=int TCC += -DSQLITE_MAX_MMAP_SIZE=0 </pre></blockquote> </ul> | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
TCC += -DSQLITE_WITHOUT_ZONEMALLOC
TCC += -D_BSD_SOURCE
TCC += -DWITHOUT_ICONV
TCC += -Dsocketlen_t=int
TCC += -DSQLITE_MAX_MMAP_SIZE=0
</pre></blockquote>
</ul>
<h2>5.0 Building a Static Binary on Linux using Docker</h2>
Building a static binary on Linux is not as straightforward as it
could be because the GNU C library requires that certain components be
dynamically loadable. That can be worked around by building against a
different C library, which is simplest to do by way of a container
environment like [https://www.docker.com/ | Docker].
The following instructions for building fossil using Docker
were adapted from [https://fossil-scm.org/forum/forumpost/5dd2d61e5f | forumpost/5dd2d61e5f].
These instructions assume that docker is installed and that the user running
these instructions has permission to do so (i.e., they are <tt>root</tt> or
are a member of the <tt>docker</tt> group).
First, create a file named <tt>Dockerfile</tt> with the following contents:
<pre><code>
FROM alpine:edge
RUN apk update \
&& apk upgrade \
\
&& apk add --no-cache \
curl gcc make tcl \
musl-dev \
openssl-dev zlib-dev \
openssl-libs-static zlib-static \
\
&& curl \
"https://www.fossil-scm.org/index.html/tarball/fossil-src.tar.gz?name=fossil-src&uuid=trunk" \
-o fossil-src.tar.gz \
\
&& tar xf fossil-src.tar.gz \
&& cd fossil-src \
\
&& ./configure \
--static \
--disable-fusefs \
--with-th1-docs \
--with-th1-hooks \
\
&& make
</code></pre>
Be sure to modify the <tt>configure</tt> flags, if desired. e.g., add <tt>--json</tt>
for JSON support.
From the directory containing that file, build it with docker:
<pre><code># docker build -t fossil_static .</code></pre>
If you get permissions errors when running that as a non-root user,
be sure to add the user to the <tt>docker</tt> group before trying
again.
That creates a docker image and builds a static fossil binary inside
it. That step will take several minutes or more, depending on the
speed of the build environment.
Next, create a docker container to host the image we just created:
<pre><code># docker create --name fossil fossil_static</code></pre>
Then copy the fossil binary from that container:
<pre><code># docker cp fossil:/fossil-src/fossil fossil</code></pre>
The resulting binary will be <em>huge</em> because it is built with
debug info. To strip that information, reducing the size greatly:
<pre><code># strip fossil</code></pre>
To delete the Docker container and image (if desired), run:
<pre><code># docker container rm fossil
# docker image ls
</code></pre>
Note the IDs of the images named <tt>fossil_static</tt> and <tt>alpine</tt>, then:
<pre><code>docker image rm THE_FOSSIL_ID THE_ALPINE_ID</code></pre>
<h2>6.0 Building on/for Android</h2>
<h3>6.1 Cross-compiling from Linux</h3>
The following instructions for building Fossil for Andoid,
without requiring a rooted OS, are adapted from
[https://fossil-scm.org/forum/forumpost/e0e9de4a7e | forumpost/e0e9de4a7e].
On the development machine, from the fossil source tree:
<pre><code>export CC=$NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang
./configure --with-openssl=none
make
</code></pre>
On the Android device, enable the <em>USB debugging</em> option from
Developer menu in Device Options. Connect the device to the development
system with USB. If it's configured and connected properly,
the device should show up in the output of <code>adb devices</code>:
<pre><code>sudo adb devices
</code></pre>
Copy the resulting fossil binary onto the device...
<pre><code>sudo adb push fossil /data/local/tmp
</code></pre>
And run it from an <code>adb</code> shell:
<pre><code>sudo adb shell
> cd /data/local/tmp
# Fossil requires a HOME directory to work with:
> export HOME=$PWD
> export PATH=$PWD:$PATH
> fossil version
This is fossil version 2.11 [e5653a4ceb] 2020-03-26 18:54:02 UTC
</code></pre>
The output might, or might not, include warnings such as:
<pre><code>WARNING: linker: ./fossil: unused DT entry: type 0x6ffffef5 arg 0x1464
WARNING: linker: ./fossil: unused DT entry: type 0x6ffffffe arg 0x1ba8
WARNING: linker: ./fossil: unused DT entry: type 0x6fffffff arg 0x2
</code></pre>
The source of such warnings is not 100% certain.
Some information about these (reportedly harmless) warnings can
be found
[https://stackoverflow.com/a/41900551 | on this StackOverflow post].
|
|
| | < < < < < | | > | < | < | | | | | > | | | > > > > > | > | > | | > | > | < < < > | < > | | < < > > > > > > > > > > | > | | | < < < > > > > > < < < | | | 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 | # Differences Between Setup and Admin User This document explains the distinction between [Setup users][caps] and [Admin users][capa]. For other information about use types, see: * [Administering User Capabilities](./) * [How Moderation Works](../forum.wiki#moderation) * [Users vs Subscribers](../alerts.md#uvs) * [Defense Against Spiders](../antibot.wiki) ## <a name="philosophy"></a>Philosophical Core The Setup user "owns" the Fossil repository and may delegate a subset of that power to one or more Admin users. The Setup user can grant Admin capability and take it away, but Admin users cannot grant themselves Setup capability, either directly via the Admin → Users UI page or via any indirect means. (If you discover indirect means to elevate Admin privilege to Setup, it's a bug, so please [report it][forum]!) It is common for the Setup user to have administrative control over the host system running the Fossil repository, whereas it makes no sense for Admin users to have that ability. If an Admin-only user had `root` access on a Linux box running the Fossil instance they are an Admin on, they could elevate their capability to Setup in several ways. (The `fossil user` command, the `fossil sql` command, editing the repository DB file directly, etc.) Therefore, if you wish to grant someone Setup-like capability on a Fossil repository but you're unwilling to give them a login on the host system, you probably want to grant them Admin capability instead. Admin power is delegated from Setup. When a Setup user grants Admin capability, it is an expression of trust in that user's judgement. Admin-only users must not fight against the policies of the Setup user. Such a rift would be just cause for the Setup user to strip the Admin user's capabilities. This may then create a fork in the project’s development effort as the ex-Admin takes their clone and stands it up elsewhere, so they may become that fork’s Setup user. A useful rule of thumb here is that Admin users should only change things that the Setup user has not changed from the stock configuration. In this way, an Admin-only user can avoid overriding the Setup user's choices. You can also look at the role of Admin from the other direction, up through the [user power hierarchy][ucap] rather than down from Setup. An Admin user is usually a “super-developer” role, given full control over the repository’s managed content: versioned artifacts in [the block chain][bc], [unversioned content][uv], forum posts, wiki articles, tickets, etc. We’ll explore these distinctions in the rest of this document. [bc]: ../blockchain.md [ucap]: ./index.md#ucap [uv]: ../unvers.wiki ## <a name="binary"></a>No Granularity Fossil doesn’t make any distinction between these two user types beyond this binary choice: Setup or Admin. A few features of Fossil are broken down so that only part of the feature is accessible to Admin, with the rest left only to Setup users, but for the most part each feature affected by this distinction is either Admin + Setup or Setup-only. We could add more capability letters to break down individual sub-features, but we’d run out of ASCII alphanumerics pretty quickly, and we might even run out of ASCII punctuation and symbols. Then would we need to shift to Unicode? Consider the Admin → Settings page, which is currently restricted to Setup users only: you might imagine breaking this up into several subsets so that some settings can be changed by Admin users. Is that a good idea? Maybe, but it should be done only after due consideration. It would definitely be wrong to assign a user capability bit to *each* setting on that page. Now consider the opposite sort of case, Admin → Skins. Fossil grants Admin users full access to this page so that the Admins can maintain and extend the skin as the repository evolves, not so Admins can switch the entire skin to another without consulting with the Setup user first. How would Fossil decide, using user capabilities only, which skin changes the Admin user is allowed to do, and which must be left to Setup? Do we assign a separate capability letter to each step in `/setup_skin`? Do we assign one more each to the five sections of a skin? (Header, Footer, CSS, JavaScript, and Details.) It quickly becomes unmanageable. ## <a name="capgroups"></a>Capability Groups We can break up the set of powers the Admin user capability grants into several groups, then defend each group as a coherent whole. ### <a name="security"></a>Security While establishing the Fossil repository's security policy is a task for the Setup user, *maintaining* that policy is something that Fossil allows a Setup user to delegate to trustworthy users via the Admin user capability: * **Manage users**: The only thing an Admin-only user cannot do on the |
| ︙ | ︙ | |||
122 123 124 125 126 127 128 | Some security-conscious people might be bothered by the fact that Admin-only users have these abilities. Think of a large IT organization: if the CIO hires a [tiger team][tt] to test the company's internal IT defenses, the line grunts fix the reported problems, not the CIO. | | | | | | | 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 |
Some security-conscious people might be bothered by the fact that
Admin-only users have these abilities. Think of a large IT organization:
if the CIO hires a [tiger team][tt] to test the company's internal IT
defenses, the line grunts fix the reported problems, not the CIO.
### <a name="administrivia"></a>Administrivia
It is perfectly fine for a Fossil repository to only have Setup users,
no Admin users. The smaller the repository, the more likely the
repository has no Admin-only users. If the Setup user neither needs nor
wants to grant Admin power to others, there is no requirement in Fossil
to do so. [Setup capability is a pure superset of Admin capability.][sia]
As the number of users on a Fossil repository grows, the value in
delegating administrivia also grows, because the Setup user typically
has other time sinks they consider more important.
Admin users can take over the following routine tasks on behalf of the
Setup user:
* **Shunning**: After user management, this is one of the greatest
powers of an Admin-only user. Fossil grants access to the Admin →
Shunned page to Admin users rather than reserve it to Setup users
because one of the primary purposes of [the Fossil shunning
system][shun] is to clean up after a spammer, and that's
exactly the sort of administrivia we wish to delegate to Admin users.
Coupled with the Rebuild button on the same page, an Admin user has
the power to delete the repository's entire
[blockchain][bc]! This makes this feature a pretty good
razor in deciding whether to grant someone Admin capability: do you
trust that user to shun Fossil artifacts responsibly?
Realize that shunning is cooperative in Fossil. As long as there are
surviving repository clones, an Admin-only user who deletes the
whole blockchain has merely caused a nuisance. An Admin-only user
cannot permanently destroy the repository unless the Setup user has
been so silly as to have no up-to-date clones.
* **Moderation**: According to the power hierarchy laid out at the top
of this article, Admins are greater than Moderators, so control over
what Moderators can do clearly belongs to both Admins and to the
Setup user(s).
* **Status**: Although the Fossil `/stat` page is visible to every
user with Read capability, there are several additional things this
page gives access to when a user also has the Admin capability:
* <p>[Email alerts][ale] and [backoffice](../backoffice.md)
status. Admin-only users cannot modify the email alerts setup,
but they can see some details about its configuration and
current status.</p>
* <p>The `/urllist` page, which is a read-only page showing the
ways the repository can be accessed and how it has been accessed
in the past. Logically, this is an extension to logging,
|
| ︙ | ︙ | |||
188 189 190 191 192 193 194 195 |
this squarely into the "administrivia" category.</p>
* <p>Web cache status, environment, and logging: more
administrivia meant to help the Admin debug problems.</p>
* **Configure search**
| > > > | | 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
this squarely into the "administrivia" category.</p>
* <p>Web cache status, environment, and logging: more
administrivia meant to help the Admin debug problems.</p>
* **Configure search**
[ale]: ../alerts.md
[shun]: ../shunning.wiki
### <a name="cosmetics"></a>Cosmetics
While the Setup user is responsible for setting up the initial "look" of
a Fossil repository, the Setup user entrusts Admin users with
*maintaining* that look. An Admin-only user therefore has the following
special abilities:
* Modify the repository skin
|
| ︙ | ︙ | |||
212 213 214 215 216 217 218 | These capabilities allow an Admin-only user to affect the branding and possibly even the back-end finances of a project. This is why we began this document with a philosophical discussion: if you cannot entrust a user with these powers, you should not grant that user Admin capability. | | > | > > > > > | > > > > > > > > > > > > > > > | | | | | > > | | > > > > > > > | 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 |
These capabilities allow an Admin-only user to affect the branding and
possibly even the back-end finances of a project. This is why we began
this document with a philosophical discussion: if you cannot entrust a
user with these powers, you should not grant that user Admin capability.
## <a name="clones"></a>Clones and Backups
Keep in mind that Fossil is a *distributed* version control system,
which means that a user known to Fossil might have Setup capability on
one repository but be a mere "user" on one of its clones. The most
common case is that when you clone a repository, even anonymously, you
gain Setup power over the local clone.
The distinctions above therefore are intransitive: they apply only
within a single repository instance.
The exception to this is when the clone is done as a Setup user, since
this also copies the `user` table on the initial clone. A user with
Setup capability can subsequently say [`fossil conf pull all`][fcp] to
update that table and everything else not normally synchronized between
Fossil repositories. In this way, a Setup user can create multiple
interchangeable clones. This is useful not only to guard against rogue
Admin-only users, it is a useful element of a load balancing and
failover system.
## <a name="apsu"></a>The All-Powerful Setup User
Setup users get [every user capability](./ref.html) of Fossil except for
[two exceptionally dangerous capabilities](#dcap), which they can later
grant to themselves or to others.
In addition, Setup users can use every feature of the Fossil UI. If Fossil can do a
thing, a Setup user on that repo can make Fossil do it.
Setup users can do many things that Admin users cannot:
* Use all of the Admin UI features
* See record IDs (RIDs) on screens that show them
* See the MIME type of attachments on [`/ainfo` pages](/help?cmd=/ainfo)
* See a remote repo’s HTTP [cache status](/help?cmd=/cachestat)
and [pull cache entries](/help?cmd=/cacheget)
* Edit a Setup user’s account!
The “Admin” feature of Fossil UI is so-named because Admin users can use
about half of its functions, but only Setup can use these pages:
* **Access**: This page falls under the [Security](#security)
category above, but like Configuration, it's generally something set
up once and never touched, so only Setup users should change it.
* **Configuration**: This page nominally falls
under [Cosmetics](#cosmetics) above, but it's such a core part of the Fossil
configuration — something every Setup user is expected to fully
specify on initial repository setup — that we have trouble
justifying any case where an Admin-only user would have good cause
to modify any of it. This page is generally set up once and then
never touched again.
* **Email-Server**: This is an experimental SMTP server feature which
is currently unused in Fossil. Should we get it working, it will
likely remain Setup-only, since it will likely be used as a
replacement for the platform’s default SMTP server, a powerful
position for a piece of software to take.
* **Login-Group**: [Login groups][lg] allow one Fossil repository to
delegate user access to another. Since an Admin-only user on one
repo might not have such access to another repo on the same host
system, this must be a Setup-only task.
* **Notification**: This is the main UI for setting up integration
with a platform’s SMTP service, for use in sending out [email
notifications][ale]. Because this screen can set commands to execute
on the host, and because finishing the configuration requires a
login on the Fossil host system, it is not appropriate to give Admin
users access to it.
* **Settings**: The [repository settings][rs] available via Admin →
Settings have too wide a range of power to allow modification by
Admin-only users:
* <p><b>Harmless</b>: Admin-only users on a repository may well
have checkin rights on the repository, so the fact that
|
| ︙ | ︙ | |||
302 303 304 305 306 307 308 309 310 311 312 |
allow them to change such settings.</p>
* **SQL**: The Admin → SQL feature allows the Setup user to enter raw
SQL queries against the Fossil repository via Fossil UI. This not
only allows arbitrary ability to modify the repository blockchain
and its backing data tables, it can probably also be used to damage
the host such as via `PRAGMA temp_store = FILE`.
* **TH1**: The [TH1 language][TH1] is quite restricted relative to the
Tcl language it descends from, so this author does not believe there
is a way to damage the Fossil repository or its host via the Admin →
| > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < | 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 |
allow them to change such settings.</p>
* **SQL**: The Admin → SQL feature allows the Setup user to enter raw
SQL queries against the Fossil repository via Fossil UI. This not
only allows arbitrary ability to modify the repository blockchain
and its backing data tables, it can probably also be used to damage
the host such as via `PRAGMA temp_store = FILE`.
* **Tickets**: This section allows input of aribtrary TH1 code that
runs on the server, affecting the way the Fossil ticketing system
works. The justification in the **TH1** section below therefore
applies.
* **TH1**: The [TH1 language][TH1] is quite restricted relative to the
Tcl language it descends from, so this author does not believe there
is a way to damage the Fossil repository or its host via the Admin →
TH1 feature, which allows execution of arbitrary TH1 code within the
repository's execution context. Nevertheless, interpreters are a
well-known source of security problems, so it seems best to restrict
this feature to Setup-only users as long as we lack a good reason
for Admin-only users to have access to it.
* **Transfers**: This is for setting up TH1 hooks on various actions,
so the justification in the **TH1** section above applies.
* **Wiki**: These are mainly cosmetic and usability settings. We might
open this up to Admin users in the future.
Just remember, [user caps affect Fossil’s web interfaces only][webo]. A
user is a Setup user by default on their local clone of a repo, and
Fossil’s ability to protect itself against malicious (or even simply
incorrect) pushes is limited. Someone with clone and push capability on
your repo could clone it, modify their local repo, and then push the
changes back to your repo. Be careful who you give that combination of
capabilities to!
When you run [`fossil ui`][fui], you are the Setup user on that repo
through that UI instance, regardless of the capability set defined in
the repo’s user table. This is true even if you cloned a remote repo
where you do not have Setup caps. This is why `ui` always binds to
`localhost` without needing the `--localhost` flag: in this mode, anyone
who can connect to that repo’s web UI has full power over that repo.
## <a name="dcap"></a>Dangerous Capabilities Initially Denied to Everyone
There are two capabilities that Fossil doesn’t grant by default to Setup
or Admin users automatically. They are exceptionally dangerous, so
Fossil makes these users grant themselves (or others) these capabilities
deliberately, hopefully after careful consideration.
### <a name="y"></a>Write Unversioned
Fossil currently doesn’t distinguish the sub-operations of [`fossil
uv`](/help?cmd=uv); they’re all covered by [**WrUnver**][capy] (“y”)
capability. Since some of these operations are unconditionally
destructive due to the nature of unversioned content, and since this
goes against Fossil’s philosophy of immutable history, nobody gets cap
“y” on a Fossil repo by default, not even the Setup or Admin users. A
Setup or Admin user must grant cap “y” to someone — not necessarily
themselves! — before modifications to remote
unversioned content are possible.
Operations on unversioned content made without this capability affect
your local clone only. In this way, your local unversioned file table
can have different content from that in its parent repo. This state of
affairs will continue until your user either gets cap “y” and syncs that
content with its parent or you say `fossil uv revert` to make your local
unversioned content table match that of its parent repo.
### <a name="x"></a>Private Branch Push
For private branches to remain private, they must never be accidentally
pushed to a public repository. It can be [difficult to impossible][shun]
to recover from such a mistake, so nobody gets [**Private**][capx] (“x”)
capability on a Fossil repo by default, not even Admin or Setup users.
There are two common uses for private branches.
One use is part of a local social contract allowing individual
developers to work on some things in private until they’re ready to push
them up to the parent repository. This goes against [a core tenet][fdp]
of Fossil’s design philosophy, but Fossil allows it, so some development
organizations do this. If yours is one of these, you might give cap “x”
to the “developer” category.
The other use is in development organizations that follow the Fossil
philosophy, where you do not work in private unless you absolutely must.
You may have a public-facing project — let’s call it “SQLite” for the
sake of argument — but then someone comes along and commissions a custom
modification to your project which they wish to keep proprietary. You
do your work on a private branch, which you absolutely must never push
to the public repo, because that would be illegal. (Breach of contract,
copyright violation on a work-for-hire agreement, etc.) If you are using
Fossil in this way, we recommend that you give “x” capability to a
special developer account only, if at all, to minimize the chance of an
accidental push.
[capa]: ./ref.html#a
[caps]: ./ref.html#s
[capx]: ./ref.html#x
[capy]: ./ref.html#y
[fcp]: https://fossil-scm.org/fossil/help?cmd=configuration
[fdp]: ../fossil-v-git.wiki#devorg
[forum]: https://fossil-scm.org/forum/
[fui]: /help?cmd=ui
[lg]: ./login-groups.md
[rs]: https://www.fossil-scm.org/index.html/doc/trunk/www/settings.wiki
[sia]: https://fossil-scm.org/fossil/artifact?udc=1&ln=1259-1260&name=0fda31b6683c206a
[snoy]: https://fossil-scm.org/forum/forumpost/00e1c4ecff
[tt]: https://en.wikipedia.org/wiki/Tiger_team#Security
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Implementation Details of User Capabilities
## <a name="choices"></a>Capability Letter Choices
We [assigned][ref] user capability characters using only lowercase ASCII
letters at first, so those are the most important within Fossil: they
control the functions most core to Fossil’s operation. Once we used up
most of the lowercase letters, we started using uppercase, and then
during the development of the [forum feature][for] we assigned most of
the decimal numerals. All of the lowercase ASCII letters are now
assigned. Eventually, we might have to start using ASCII
punctuation and symbols. We expect to run out of reasons to define new caps before
we’re forced to switch to Unicode, though the possibilities for [mnemonic][mn]
assignments with emoji are intriguing. <span style="vertical-align:
bottom">😉</span>
The existing caps are usually mnemonic, especially among the
earliest and therefore most central assignments, made when we still had
lots of letters to choose from. There is still hope for good future
mnemonic assignments among the uppercase letters, which are mostly still
unused.
## <a name="bitfield"></a>Why Not Bitfields?
Some may question the use of ASCII character strings for [capability
sets][ucap] instead of bitfields, which are more efficient, both in
terms of storage and processing time.
Fossil handles these character strings in one of two ways. For most HTTP
hits, Fossil [expands][sexp] the string into a [`struct` full of
flags][sff] so that later code can just do simple Boolean tests. In a
minority of cases, where Fossil only needs to check for the presence of
a single flag, it just does a [`strchr()` call][sc] on the string
instead.
Both methods are slower than bit testing in a bitfield, but keep the
execution context in mind: at the front end of an HTTP request handler,
where the nanosecond differences in such implementation details are
completely swamped by the millisecond scale ping time of that repo’s
network connection, followed by the required I/O to satisfy the request.
Either method is plenty fast in that context.
In exchange for this immeasurable cost per hit, we get human-readable
capability sets.
## <a name="filter"></a>Why Doesn’t Fossil Filter “Bad” Artifacts on Sync?
Fossil is more trusting about the content it receives from a remote
clone during sync than you might expect. Common manifestations of this
design choice are:
1. A user may be able to impersonate other users. This can be
[accidental](./index.md#defuser) as well as purposeful.
2. If your local system clock is out-of-sync with absolute time,
artifacts committed to that repo will appear with the “wrong” time
when sync’d. If the time sync error is big enough, it can make
check-ins appear to go back in time and other bad effects.
3. You can purposely overwrite good timestamps with bad ones and push
those changes up to the remote with no interference, even though
Fossil tries to make that a Setup-only operation.
All of this falls out of two of Fossil’s design choices: sync is
all-or-nothing, and [the Fossil block chain][bc] is immutable. Fossil
would have to violate one or both of these principles to filter such
problems out of incoming syncs.
We have considered auto-[shunning][shun] “bad” content on sync, but this
is [difficult][asd] due to [the design of the sync protocol][dsp]. This
is not an impossible set of circumstances, but implementing a robust
filter on this input path would be roughly as difficult as writing a
basic [inter-frame video codec][ifvc]: do-able, but still a lot of
work. Patches to do this will be thoughtfully considered.
We can’t simply change content as it arrives. Such manipulations would
change the artifact manifests, which would change the hashes, which
would require rewriting all parts of the block chain from that point out
to the tips of those branches. The local Fossil repo must then go
through the same process as the remote one on subsequent syncs in order
to build up a sync sequence that the remote can understand. Even if
you’re willing to accept all of that, this would break all references to
the old artifact IDs in forum posts, wiki articles, check-in comments,
tickets, etc.
The bottom line here is that [**Clone**](./ref.html#g) and
[**Write**](./ref.html#i) are a potent combination of user capabilities.
Be careful who you give that pair to!
-----
*[Back to Administering User Capabilities](./)*
<!-- add padding so anchor links always scroll ref’d section to top -->
<div style="height: 75em"></div>
[asd]: https://fossil-scm.org/forum/forumpost/ce4a3b5f3e
[bc]: ../blockchain.md
[dsp]: https://fossil-scm.org/fossil/doc/trunk/www/sync.wiki
[for]: ./forum.wiki
[ifvc]: https://en.wikipedia.org/wiki/Inter_frame
[mn]: https://en.wikipedia.org/wiki/Mnemonic
[ref]: ./ref.html
[sexp]: http://fossil-scm.org/fossil/artifact?udc=1&ln=1223-1298&name=889d6724
[sff]: http://fossil-scm.org/fossil/artifact?udc=1&ln=80-117&name=52d2860f
[sc]: https://en.cppreference.com/w/c/string/byte/strchr
[shun]: ../shunning.wiki
[ucap]: ./index.md#ucap
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Administering User Capabilities
Fossil includes a powerful [role-based access control system][rbac]
which affects which users have which capabilities within a given
[served][svr] Fossil repository. We call this the capability system, or
“caps” for short.
Fossil stores a user’s caps as an unordered string of ASCII characters,
one capability per, [currently](./impl.md#choices) limited to
[alphanumerics][an]. Caps are case-sensitive: “**A**” and “**a**” are
different user capabilities.
This is a complex topic, so some sub-topics have their own documents:
1. [Login Groups][lg]
2. [Implementation Details](./impl.md)
3. [User Capability Reference](./ref.html)
[an]: https://en.wikipedia.org/wiki/Alphanumeric
[avs]: ./admin-v-setup.md
[lg]: ./login-groups.md
[rbac]: https://en.wikipedia.org/wiki/Role-based_access_control
## <a name="ucat"></a>User Categories
Before we explain individual user capabilities and their proper
administration, we want to talk about an oft-overlooked and
misunderstood feature of Fossil: user categories.
Fossil defines four user categories. Two of these apply based on the
user’s login status: **nobody** and **anonymous**. The other two act
like Unix or LDAP user groups: **reader** and **developer**. Because we
use the word “group” for [another purpose][lg] in Fossil, we will
avoid using it that way again in this document. The correct term in
Fossil is “category.”
Fossil user categories give you a way to define capability sets for four
hard-coded situations within the Fossil C source code. Logically
speaking:
> *(developer* ∨ *reader)* ≥ *anonymous* ≥ *nobody*
When a user visits a [served Fossil repository][svr] via its web UI,
they initially get the capabilities of the “nobody” user category. This
category would be better named “everybody” because it applies whether
you’re logged in or not.
When a user logs in as “anonymous” via [`/login`](/help?name=/login) they
get all of the “nobody” category’s caps plus those assigned to the
“anonymous” user category. It would be better named “user” because it
affects all logged-in users, not just those logged in via Fossil’s
anonymous user feature.
When a user with either the “reader” ([**u**][u]) or “developer”
([**v**][v]) capability letter logs in, they get their [individual user
caps](#ucap) plus those assigned to this special user category. They
also get those assigned to the “anonymous” and “nobody” categories.
Because “developer” users do not automatically inherit “reader” caps,
it is standard practice to give both letters to your “developer” users:
**uv**. You could instead just assign cap **u** to the “developer”
category.
Fossil shows how these capabilities apply hierarchically in the user
editing screen (Admin → Users → name) with the `[N]` `[A]` `[D]` `[R]`
tags next to each capability check box. If a user gets a capability from
one of the user categories already assigned to it, there is no value in
redundantly assigning that same cap to the user explicitly. For example,
with the default **ei** cap set for the “developer” category, the cap
set **ve** is redundant because **v** grants **ei**, which includes
**e**.
We suggest that you lean heavily on these fixed user categories when
setting up new users. Ideally, your users will group neatly into one of
the predefined categories, but if not, you might be able to shoehorn
them into our fixed scheme. For example, the administrator of a
wiki-only Fossil repo for non-developers could treat the “developer”
user category as if it were called “author,” and a forum-only repo could
treat the same category as if it were called “member.”
There is currently no way to define custom user categories.
[svr]: ../server/
## <a name="ucap"></a>Individual User Capabilities
When one or more users need to be different from the basic capabilities
defined in user categories, you can assign caps to individual users. You
may want to have the [cap reference][ref] open when doing such work.
It is useful at this time to expand on the logical
expression [above](#cat), which covered only the four fixed user categories.
When we bring the individual user capabilities into it, the complete
expression of the way Fossil implements user power becomes:
> *setup* ≥ *admin* ≥ *moderator* ≥ *(developer* ∨ *reader)* ≥ *[subscriber]* ≥ *anonymous* ≥ *nobody*
The two additions at the top are clear: [setup is all-powerful][apsu],
and since admin users have [all capabilities][ref] except for Setup
capability, they are [subordinate only to the setup user(s)][avsp].
The moderator insertion could go anywhere from where it’s shown now down
to above the “anonymous” level, depending on what other caps you give to
your moderators. Also, there is not just one type of moderator: Fossil
has [wiki][l], [ticket][q], and [forum][5] moderators, each
independent of the others. Usually your moderators are fairly
high-status users, with developer capabilities or higher, but Fossil
does allow the creation of low-status moderators.
The placement of “subscriber” in that hierarchy is for the
sort of subscriber who has registered an account on the repository
purely to [receive email alerts and announcements][7]. Users with
additional caps can also be subscribers, but not all users *are* in fact
subscribers, which is why we show it in square brackets. (See [Users vs
Subscribers](../alerts.md#uvs).)
[apsu]: ./admin-v-setup.md#apsu
[avsp]: ./admin-v-setup.md#philosophy
## <a name="new"></a>New Repository Defaults
Fossil creates one user account in new repos, which is named after your
OS user name [by default](#defuser).
Fossil gives the initial repository user the [all-powerful Setup
capability][apsu].
Users who visit a [served repository][svr] without logging in get the
“nobody” user category’s caps which default to
**[g][g][j][j][o][o][r][r][z][z]**: clone the repo, read the wiki,
check-out files via the web UI, view tickets, and pull version archives.
This default is suited to random passers-by on a typical FOSS project’s
public web site and its code repository.
Users who [prove they are not a bot][bot] by logging in — even if only
as “anonymous” — get the “nobody” capability set plus
**[h][h][m][m][n][n][c][c]**: see internal hyperlinks, append to
existing wiki articles, file new tickets, and comment on existing
tickets. We chose these additional capabilities as those we don’t want
bots to have, but which a typical small FOSS project would be happy to
give anonymous humans visiting the project site.
The “reader” user category is typically assigned to users who want to be
identified within the repository but who primarily have a passive role
in the project. The default capability set on a Fossil repo adds
**[k][k][p][p][t][t][w][w]** caps to those granted by “nobody” and
“anonymous”. This category is not well-named, because the default caps
are all about modifying repository content: edit existing wiki pages,
change one’s own password, create new ticket report formats, and modify
existing tickets. This category would be better named “participant”.
Those in the “developer” category get the “nobody” and “anonymous” cap
sets plus **[e][e][i][i]**: view
sensitive user material and check in changes.
[bot]: ../antibot.wiki
## <a name="pvt"></a>Consequences of Taking a Repository Private
When you click Admin → Security-Audit → “Take it private,” one of the
things it does is set the user capabilities for the “nobody” and
“anonymous” user categories to blank, so that users who haven’t logged
in can’t even see your project’s home page, and the option to log in as
“anonymous” isn’t even offered. Until you log in with a user name, all
you see is the repository’s skin and those few UI elements that work
without any user capability checks at all, such as the “Login” link.
Beware: Fossil does not reassign the capabilities these users had to
other users or to the “reader” or “developer” user category! All users
except those with Setup capability will lose all capabilities they
inherited from “nobody” and “anonymous” categories. Setup is the [lone
exception][apsu].
If you will have non-Setup users in your private repo, you should parcel
out some subset of the capability set the “nobody” and “anonymous”
categories had to other categories or to individual users first.
## <a name="read-v-clone"></a>Reading vs. Cloning
Fossil has two capabilities that are often confused:
[**Read**](./ref.html#o) and [**Clone**](./ref.html#g).
The **Read** capability has nothing to do with reading data from a local
repository, because [caps affect Fossil’s web interfaces
only](#webonly). Once you’ve cloned a remote repository to your local
machine, you can do any reading you want on that repository irrespective
of whether your local user within that repo has <b>Read</b> capability.
The repo clone is completely under your user’s power at that point,
affected only by OS file permissions and such. If you need to prevent
that, you want to deny **Clone** capability instead.
Withholding the **Read** capability has a different effect: it
prevents a web client from viewing [embedded
documentation][edoc], using [the file
browser](/help?name=/dir), and pulling file content via the
[`/artifact`](/help?name=/artifact), [`/file`](/help?name=/file), and
[`/raw`](/help?name=/raw) URLs.
It is is common to withhold **Read** capability from low-status visitors
on private or semi-private repos to prevent them from pulling individual
elements of the repo over the web one at a time, as someone may do when
denied the bulk **Clone** capability.
[edoc]: ../embeddeddoc.wiki
## <a name="defuser"></a>Default User Name
By default, Fossil assumes your OS user account name is the same as the
one you use in any Fossil repository. It is the [default for a new
repository](#new), though you can override this with [the `--admin-user`
option][auo]. Fossil has other ways of overriding this in other contexts
such as the `name@` syntax in clone URLs.
It’s simplest to stick with the default; a mismatch can cause problems.
For example, if you clone someone else’s repo anonymously, turn off
autosync, and make check-ins to that repository, they will be assigned
to your OS user name by default. If you later get a login on the remote
repository under a different name and sync your repo with it, your
earlier “private” check-ins will get synced to the remote under your OS
user name!
When such problems occur, you can amend the check-in to hide the
incorrect name from Fossil reports, but the original values remain in
the repository [forever][shun]. It is [difficult enough][fos] to fix
such problems automatically during sync that we are unlikely to ever do
so.
[auo]: /help?name=new
[fos]: ./impl.md#filter
[shun]: ../shunning.wiki
## <a name="utclone"></a>Cloning the User Table
When cloning over HTTP, the initial user table in the local clone is set
to its “[new state:](#new)” only one user with Setup capability, named
after either your OS user account, per the default above, or after the
user given in the clone URL.
There is one exception: if you clone as a named Setup user, you get a
complete copy of the user information. This restriction keeps the user
table private except for the only user allowed to make absolutely
complete clones of a remote repo, such as for failover or backup
purposes. Every other user’s clone is missing this and a few other
items, either for information security or PII privacy reasons.
When cloning with file system paths, `file://` URLs, or over SSH, you
get a complete clone, including the parent repo’s complete user table.
All of the above applies to [login groups][lg] as well.
## <a name="webonly"></a>Caps Affect Web Interfaces Only
User caps only affect Fossil’s [UI pages][wp], remote operations over
`http[s]://` URLs, and [the JSON API][japi].
User caps *do not* affect operations done on a local repo opened via a
`file://` URL or a file system path. This should strike you as sensible:
only local file permissions matter when operating on a local SQLite DB
file. The same is true when working on a clone done over such a path,
except that there are then two sets of file system permission checks:
once to modify the working check-out’s repo clone DB file, then again on
[sync][sync] with the parent DB file. The Fossil capability checks are
effectively defeated because your user has [**Setup**][s] capability on
both sides of the sync.
What may surprise you is that user caps *also do not affect SSH!* When
you make a change to such a repository, the change first goes to the
local clone, where file system permissions are all that matter, but then
upon sync, the situation is effectively the same as when the parent repo
is on the local file system. If you can log into the remote system over
SSH and that user has the necessary file system permissions on that
remote repo DB file, it is the same situation as for `file://` URLs.
All Fossil syncs are done over HTTP, even for `file://` and `ssh://`
URLs:
* For `ssh://` URLs, Fossil pipes the HTTP conversation through a
local SSH client to a remote instance of Fossil running the
[`test-http`](/help?name=test-http) command to recieve the tunneled
HTTP connection without cap checks. The SSH client defaults to “`ssh
-e none -T`” on most platforms, except on Windows where it defaults
to “`plink -ssh -T`”. You can override this with [the `ssh-command`
setting](/help?name=ssh-command).
* For `file://` URLs, the “sending” Fossil instance writes its side of
the HTTP conversation out to a temporary file in the same directory
as the local repo clone and then calls itself on the “receiving”
repository to read that same HTTP transcript file back in to apply
those changes to that repository. Presumably Fossil doesn’t do this
with a pipe to ease portability to Windows.
Because both mechanisms work on local repos, the checks for capabilities
like [**Read**][o] and [**Write**][i] within the HTTP conversation for
such URLs can never return “false,” because you are the [**Setup**][s]
user on both sides of the conversation. Such checks only have a useful
effect when done over an `http[s]://` URL.
## <a name="pubpg"></a>Public Pages
In Admin → Access, there is an option for giving a list of [globs][glob]
to name URLs which get treated as if the visitor had [the default cap
set](#defcap). For example, you could take the [**Read**][o] capability
away from the “nobody” user category, who has it by default, to prevent
users without logins from pulling down your repository contents one
artifact at a time, yet give those users the ability to read the project
documentation by setting the glob to match your [embedded
documentation][edoc]’s URL root.
## <a name="defcap"></a>Default User Capability Set
In Admin → Access, you can define a default user capability set, which
is used as:
1. the default caps for users newly created by an Admin or Setup user
2. the default caps for self-registered users, an option in that same UI
3. the effective caps for URIs considered [public pages](#pubpg)
This defaults to [**Reader**][u].
<!-- add padding so anchor links always scroll ref’d section to top -->
<div style="height: 75em"></div>
[ref]: ./ref.html
[a]: ./ref.html#a
[b]: ./ref.html#b
[c]: ./ref.html#c
[d]: ./ref.html#d
[e]: ./ref.html#e
[f]: ./ref.html#f
[g]: ./ref.html#g
[h]: ./ref.html#h
[i]: ./ref.html#i
[j]: ./ref.html#j
[k]: ./ref.html#k
[l]: ./ref.html#l
[m]: ./ref.html#m
[n]: ./ref.html#n
[o]: ./ref.html#o
[p]: ./ref.html#p
[q]: ./ref.html#q
[r]: ./ref.html#r
[s]: ./ref.html#s
[t]: ./ref.html#t
[u]: ./ref.html#u
[v]: ./ref.html#v
[w]: ./ref.html#w
[x]: ./ref.html#x
[y]: ./ref.html#y
[z]: ./ref.html#z
[2]: ./ref.html#2
[3]: ./ref.html#3
[4]: ./ref.html#4
[5]: ./ref.html#5
[6]: ./ref.html#6
[7]: ./ref.html#7
[glob]: https://en.wikipedia.org/wiki/Glob_(programming)
[japi]: https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/view#heading=h.6k0k5plm18p1
[sp]: ../sync.wiki
[sync]: /help?name=sync
[wp]: /help#webpages
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | # Login Groups The Admin → Login-Groups UI feature and its corresponding [`login-group` command][lg] solve a common problem with Fossil: you’ve created multiple repositories that some set of users all need access to, those users all have the same access level on all of these shared repositories, and you don’t want to redundantly configure the user set for each repository. This feature ties changes to the “`user`” table in one repo to that in one or more other repos. With this configured, you get a new choice on the user edit screen, offering to make changes specific to the one repository only or to apply it to all others in the login group as well. A user can log into one repo in a login group only if that user has an entry in that repo’s user table. That is, setting up a login group doesn’t automatically transfer all user accounts from the joined repo to the joining repo. Only when a user exists by name in both repos will that user be able to share credentials across the repos. Login groups can have names, allowing one “master” repo to host multiple subsets of its users to other repos. Trust in login groups is transitive within a single server. If repo C joined repo B and repo B joined A, changes in C’s user table affect both A and B, if you tell Fossil that the change applies to all repos in the login group. [lg]: /help?cmd=login-group ----- *[Back to Administering User Capabilities](./)* |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
<div class='fossil-doc' data-title="User Capability Reference">
<style type="text/css">
p#backlink {
/* Make empty space below the table so hyperlinks to named anchors
near the bottom of the table still scroll that row to the top of
the user's browser, even on tall screens. */
margin-bottom: 75em;
}
tr > th {
background-color: #e8e8e8;
vertical-align: top;
}
tr.cols th {
white-space: nowrap;
}
td, th {
padding: 0.4em;
}
</style>
<p>Here we document each currently-defined user capability character in
more detail than the brief summary on the <a
href="/setup_ucap_list">“key” page</a> in the Fossil user editor. Each
row gives the capability letter used in the Fossil user editor followed
by the C code’s name for that cap within the <tt>FossilUserPerms</tt>
object, so you can use this reference both from the UI down and from the
C code up.</p>
<p>The <a href="https://en.wikipedia.org/wiki/Mnemonic">mnemonics</a>
given here vary from obviously-correct to <i>post facto</i>
rationalizations to the outright fanciful. To <a
href="./impl.md#choices">some extent</a>, this is unavoidable.</p>
<h2>Reference</h2>
<table>
<tr class="cols">
<th>?</th>
<th>Name</th>
<th style="text-align: left">Description</th>
</tr>
<tr id="a">
<th>a</th>
<th>Admin</th>
<td>
Admin users have <em>all</em> of the capabilities below except for
<a href="#s">setup</a>, <a herf="#x">Private</a>, and <a href="#y">WrUnver</a>.
See <a href="admin-v-setup.md">Admin vs. Setup</a> for a more
nuanced discussion. Mnemonic: <b>a</b>dministrate.
</td>
</tr>
<tr id="b">
<th>b</th>
<th>Attach</th>
<td>
Add attachments to wiki articles or tickets. Mnemonics: <b>b</b>ind,
<b>b</b>utton, <b>b</b>ond, or <b>b</b>olt.
</td>
</tr>
<tr id="c">
<th>c</th>
<th>ApndTkt</th>
<td>
Append comments to existing tickets. Mnemonic: <b>c</b>omment.
</td>
</tr>
<tr id="d">
<th>d</th>
<th>n/a</th>
<td>
Legacy capability letter from Fossil's forebear <a
href="http://cvstrac.org/">CVSTrac</a>, which has no useful
meaning in Fossil due to its durable blockchain nature. This
letter was assigned by default to Developer in repos created with
Fossil 2.10 or earlier, but it has no effect in current or past
versions of Fossil; we recommend that you remove it in case we
ever reuse this letter for another purpose. See <a
href="https://fossil-scm.org/forum/forumpost/43c78f4bef">this
post</a> for details.
</td>
</tr>
<tr id="e">
<th>e</th>
<th>RdAddr</th>
<td>
View <a
href="https://en.wikipedia.org/wiki/Personal_data">personal
identifying information</a> (PII) about other users such as email
addresses. Mnemonics: show <b>e</b>mail addresses; or
<b>E</b>urope, home of <a
href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation">GDPR</a>.
</td>
</tr>
<tr id="f">
<th>f</th>
<th>NewWiki</th>
<td>
Create new wiki articles. Mnemonic: <b>f</b>ast, English
translation of the Hawaiian word <a
href="https://en.wikipedia.org/wiki/History_of_wikis#WikiWikiWeb,_the_first_wiki"><i>wiki</i></a>.
</td>
</tr>
<tr id="g">
<th>g</th>
<th>Clone</th>
<td>
Clone the repository. Note that this is distinct from <a
href="#o">check-out capability, <b>o</b></a>. Mnemonic:
<b>g</b>et.
</td>
</tr>
<tr id="h">
<th>h</th>
<th>Hyperlink</th>
<td>
Get hyperlinks in generated HTML which link you to other parts of
the repository. This capability exists so we can deny it to the
“nobody” category, to <a href="../antibot.wiki">prevent bots from
wandering around aimlessly</a> in the site’s hyperlink web, <a
href="../loadmgmt.md">chewing up server resources</a> to little
good purpose. Mnemonic: <b>h</b>yperlink.
</td>
</tr>
<tr id="i">
<th>i</th>
<th>Write</th>
<td>
Check changes into the repository. Note that a lack of this
capability does not prevent you from checking changes into your
local clone, only from syncing those changes up to the parent
repo, and then <a href="./basics.md#webonly">only over HTTP</a>.
Granting this capability also grants <b>o (Read)</b> Mnemonics:
<b>i</b>nput, check <b>i</b>n changes.
</td>
</tr>
<tr id="j">
<th>j</th>
<th>RdWiki</th>
<td>
View wiki articles. Mnemonic: in<b>j</b>est page content. (All
right, you critics, you do better, then.)
</td>
</tr>
<tr id="k">
<th>k</th>
<th>WrWiki</th>
<td>
Edit wiki articles. Granting this capability also grants <a
href="#j"><b>RdWiki</b></a> and <a href="#m"><b>ApndWiki</b></a>,
but it does <em>not</em> grant <a href="#f"><b>NewWiki</b></a>!
Mnemonic: <b>k</b>ontribute.
</td>
</tr>
<tr id="l">
<th>l</th>
<th>ModWiki</th>
<td>
Moderate <a href="#m">wiki article appends</a>. Appends do not get
saved permanently to the receiving repo’s block chain until <a
href="#s">Setup</a> or someone with this cap approves it.
Mnemonic: a<b>l</b>low.
</td>
</tr>
<tr id="m">
<th>m</th>
<th>ApndWiki</th>
<td>
Append content to existing wiki articles. Mnemonic: a<b>m</b>end
wiki
</td>
</tr>
<tr id="n">
<th>n</th>
<th>NewTkt</th>
<td>
File new tickets. Mnemonic: <b>n</b>ew ticket.
</td>
</tr>
<tr id="o">
<th>o</th>
<th>Read</th>
<td>
Read repository content from a remote Fossil instance over
HTTP. See <a href="index.md#read-v-clone">Reading vs.
Cloning</a>. Mnemonic: check <b>o</b>ut remote repo contents.
</td>
</tr>
<tr id="p">
<th>p</th>
<th>Password</th>
<td>
Change one’s own password. Mnemonic: <b>p</b>assword.
</td>
</tr>
<tr id="q">
<th>q</th>
<th>ModTkt</th>
<td>
Moderate tickets: delete comments appended to tickets. Mnemonic:
<b>q</b>uash noise commentary.
</td>
</tr>
<tr id="r">
<th>r</th>
<th>RdTkt</th>
<td>
View existing tickets. Mnemonic: <b>r</b>ead tickets.
</td>
</tr>
<tr id="s">
<th>s</th>
<th>Setup</th>
<td>
The <a href="./admin-v-setup.md#apsu">all-powerful Setup user</a>.
Mnemonics: <b>s</b>etup or <b>s</b>uperuser.
</td>
</tr>
<tr id="t">
<th>t</th>
<th>TktFmt</th>
<td>
Create new ticket report formats. Note that although this allows
the user to provide SQL code to be run in the server’s context,
and this capability is given to the untrusted “anonymous” user
category by default, this is a safe capability to give to users
because it is internally restricted to read-only queries on the
tickets table only. (This restriction is done with a SQLite
authorization hook, not by any method so weak as SQL text
filtering.) Mnemonic: new <b>t</b>icket report.
</td>
</tr>
<tr id="u">
<th>u</th>
<th>n/a</th>
<td>
Inherit all capabilities of the “reader” user category; does not
have a dedicated flag internally within Fossil. Mnemonic:
<a href="./index.md#ucat"><b>u</b>ser</a>
</td>
</tr>
<tr id="v">
<th>v</th>
<th>n/a</th>
<td>
Inherit all capabilities of the “developer” user category; does
not have a dedicated flag internally within Fossil. Mnemonic:
de<b>v</b>eloper.
</td>
</tr>
<tr id="w">
<th>w</th>
<th>WrTkt</th>
<td>
Edit existing tickets. Granting this capability also grants <a
href="#r"><b>RdTkt</b></a>, <a href="#c"><b>ApndTkt</b></a>, and
<a href="#n"><b>NewTkt</b></a>. Mnemonic: <b>w</b>rite to ticket.
</td>
</tr>
<tr id="x">
<th>x</th>
<th>Private</th>
<td>
Push or pull <a href="../private.wiki">private branches</a>.
Mnemonic: e<b>x</b>clusivity; “x” connotes unknown material in
many Western languages due to its <a
href="https://en.wikipedia.org/wiki/La_Géométrie#The_text">traditional
use in mathematics</a>.
</td>
</tr>
<tr id="y">
<th>y</th>
<th>WrUnver</th>
<td>
Push <a href="../unvers.wiki">unversioned content</a>. Mnemonic:
<b>y</b>ield, <a href="https://en.wiktionary.org/wiki/yield">sense
4</a>: “hand over.”
</td>
</tr>
<tr id="z">
<th>z</th>
<th>Zip</th>
<td>
Pull archives of particular repository versions via <a
href="/help?cmd=/zip"><tt>/zip</tt></a>, <a
href="/help?cmd=/tarball"><tt>/tarball</tt></a>, and <a
href="/help?cmd=/sqlar"><tt>/sqlar</tt></a> URLs. This is an
expensive capability to grant, because creating such archives can
put a large load on <a href="../server/">a Fossil server</a> which
you may then need to <a href="../loadmgmt.md">manage</a>.
Mnemonic: <b>z</b>ip file download.
</td>
</tr>
<tr id="2">
<th>2</th>
<th>RdForum</th>
<td>
Read <a href="../forum.wiki">forum posts</a> by other users.
Mnemonic: from thee <b>2</b> me.
</td>
</tr>
<tr id="3">
<th>3</th>
<th>WrForum</th>
<td>
Create new forum threads, reply to threads created by others, and
edit one’s own posts. New posts are <a
href="../forum.wiki#moderation">held for moderation</a> and do
not appear in repo clones or syncs. Granting this capability also
grants <a href="#2"><b>RdForum</b></a>. Mnemonic: post for
<b>3</b> audiences: me, <a href="#5">the mods</a>, and <a
href="https://en.wikipedia.org/wiki/The_Man">the Man</a>.
</td>
</tr>
<tr id="4">
<th>4</th>
<th>WrTForum</th>
<td>
Extends <a href="#3"><b>WrForum</b></a>, bypassing the moderation
and sync restrictions. Mnemonic: post <b>4</b> immediate release.
</td>
</tr>
<tr id="5">
<th>5</th>
<th>ModForum</th>
<td>
<a href="../forum.wiki#moderation">Moderate forum posts</a>.
Granting this capability also grants <a
href="#4"><b>WrTForum</b></a> and <a href="#2"><b>RdForum</b></a>,
so a user with this cap never has to moderate their own posts.
Mnemonic: “May I have <b>5</b> seconds of your time, honored
Gatekeeper?”
</td>
</tr>
<tr id="6">
<th>6</th>
<th>AdminForum</th>
<td>
Users with this capability see a checkbox on unmoderated forum
posts labeled “Trust user X so that future posts by user X do not
require moderation.” Checking that box and then clicking the
moderator-only “Approve” button on that post grants <a
href="#4"><b>WrTForum</b></a> capability to that post’s author.
There is currently no UI for a user with this cap to
<em>revoke</em> trust from a user once it is granted; only <a
href="#a"><b>Admin</b></a> and <a href="#s"><b>Setup</b></a> can
currently revoke granted caps. Granting this capability also
grants <a href="#5"><b>ModForum</b></a> and those it in turn
grants. Mnemonic: “I’m <b>6</b> [sick] of hitting Approve on your
posts!”
</td>
</tr>
<tr id="7">
<th>7</th>
<th>EmailAlert</th>
<td>
User can sign up for <a href="../alerts.md">email alerts</a>.
Mnemonic: <a href="https://en.wikipedia.org/wiki/Heaven_Can_Wait">Seven can
wait</a>, I’ve got email to read now.
</td>
</tr>
<tr id="A">
<th>A</th>
<th>Announce</th>
<td>
Send email announcements to users <a href="#7">signed up to
receive them</a>. Mnemonic: <b>a</b>nnounce.
</td>
</tr>
<tr id="D">
<th>D</th>
<th>Debug</th>
<td>
Enable debugging features. Mnemonic: <b>d</b>ebug.
</td>
</tr>
</table>
<hr/>
<p id="backlink"><a href="./"><em>Back to Administering User
Capabilities</em></a></p>
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | <title>CGI Script Configuration Options</title> <h1>Summary</h1> It is not necessary to have a central server in order to use Fossil. But a central server can help a project run more smoothly by giving developers a common point of rendezvous for syncing, and by providing a web-based portal where developers and non-developers alike can learn about the project and its current state. Setting up a server using Fossil is easy. A [./server/|separate document] talks about all of the many different methods for setting up a Fossil server, one of which is [./server/any/cgi.md | as a CGI script]. CGI is the technique that the three [./selfhost.wiki|self-hosting Fossil repositories] all use. Setting up a Fossil server using CGI is mostly about writing a short script (usually just 2 lines line) in the cgi-bin folder of an ordinary web-server. But there are a lot of extra options that can be added to this script, to customize the configuration. This article describes those options. <h1>CGI Script Options</h1> The CGI script used to launch a Fossil server will usually look something like this: <blockquote><verbatim> #!/usr/bin/fossil repository: /home/www/fossils/myproject.fossil </verbatim></blockquote> Of course, pathnames will likely be different. The first line (the "shebang") always gives the name of the Fossil executable. Subsequent lines are of the form "<b>property: argument ...</b>". The remainder of this document describes the available properties and their arguments. <hr> <h2 id="repository">repository: <i>PATH</i></h2> This property defines the Fossil repository that the server will use. Every Fossil CGI requires either this property or the [#directory|<b>directory:</b>] property (but not both). Many Fossil repository sets have this one property and no other. <h2 id="directory">directory: <i>PATH</i></h2> The PATH is the name of a directory that contains one or more Fossil repository files having the suffix ".fossil". If this property is used instead of [#repository|<b>repository:</b>], then the Fossil server is able to serve all of the repositories in the directory. The specific repository used is selected by a prefix on the PATH_INFO. <h2 id="errorlog">errorlog: <i>FILENAME</i></h2> This setting causes the server to log any errors in FILENAME. It is ok for multiple Fossil CGIs to share the same error log. Setting up an error log for Fossil servers is not required, but it is recommended. <h2 id="notfound">notfound: <i>URL</i></h2> If the [#directory|<b>directory:</b>] option is used and if the PATH_INFO of the HTTP request does not correspond to any Fossil repository, then the request redirects to URL. <h2 id="repolist">repolist</h2> This is a Boolean property. If it is present, and if the [#directory:|<b>directory:</b>] option is used, and if the PATH_INFO string is empty, then Fossil will show a list of available Fossil repositories. The "skin" of the reply is determined by the first repository in the list that has a non-zero [/help?cmd=repolist-skin|repolist-skin] setting. If no repository has such a non-zero repolist-skin setting, then the repository list is generic HTML without any decoration. <h2 id="extroot">extroot: <i>PATH</i></h2> This property defines the DOCUMENT_ROOT for the [./serverext.wiki|CGI Server Extensions]. If this property is present, then CGI Server Extensions are enabled. When this property is omitted, CGI Server Extensions are disabled. A cascade of CGI invocations can occur here. Fossil itself is started as CGI, then Fossil can turn around and invoke a sub-CGI extension. The sub-CGI extension outputs reply text, when Fossil then (optionally) augments with its own header and footer and returns to the original requestor. The property controls the DOCUMENT_ROOT of the sub-CGI. <h2 id="timeout">timeout: <i>N</i></h2> This property changes the timeout on each CGI request to N seconds. If N is zero, then there is no timeout. If this property is omitted, then the default timeout is 300 seconds (5 minutes). <h2 id="localauth">localauth</h2> This is a Boolean property. If it is present, [./caps/ref.html#s | setup capability] is granted to any HTTP request that comes in over a loopback interface, such as 127.0.0.1. If the PATH_INFO string is empty, Fossil will show a list of available Fossil repositories. <h2 id="skin">skin: <i>NAME</i></h2> If NAME is the name of one of the built-in skins supported by Fossil, then this option causes Fossil to display using that built-in skin, and to ignore any custom skin that might be configured in the repository itself. So, if you wanted to set up a server for a single Fossil project, but also give users the option to use several of the different built-in skins, you could create multiple CGI scripts, each with a different "<b>skin:</b>" property, but all pointing to the same <b>repository:</b>. Then users can select which skin to use by using the appropriate CGI. <h2 id="files">files: </i>GLOBLIST</i></h2> The GLOBLIST argument is a comma-separate list of "globs" that specify filenames. In [#directory|<b>directory:</b> mode], if the PATH_INFO does not identify any Fossil repository, but it does refer some other file in the directory, and that filename matches one of the glob patterns in the GLOBLIST, then the file is returned as static content. <h2 id="setenv">setenv: <i>NAME VALUE</i></h2> This parameter causes additional environment variable NAME to have VALUE. This parameter can be repeated as many times as necessary. <h2 id="HOME">HOME: <i>PATH</i></h2> This parameter is a short-hand for "<b>setenv HOME <i>PATH</i></b>". <h2 id="cgi-debug">cgi-debug: <i>FILE</i></h2> Cause CGI-related debugging information to be appended in <i>FILE</i>. Use this to help debug CGI problems. |
1 2 3 | <title>Change Log</title> <a name='v2_10'></a> | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > | | 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 |
<title>Change Log</title>
<a name='v2_12'></a>
<h2>Changes for Version 2.12 (pending)</h2>
* Security fix in the "fossil git export" command. New "safety-nets"
added to prevent future problems.
* Enhancements to the graph display for cases when there are
many merges into a single check-in.
[/info/2d75e87b760c0a9?diff=0|Example]
* The markdown-to-html translator can prevent unsafe HTML
(for example: <script>) on user pages like forum and
tickets and wiki, at the administrators option. On by
default.
[https://www.fossil-scm.org/forum/forumpost/3714e6568f|Example].
* Enhance the [/help?cmd=revert|fossil revert] command so that it
is able to revert all files beneath a directory.
* Added <tt>--reset</tt> flag to the "[/help?cmd=add|fossil add]",
"[/help?cmd=rm|fossil rm]", and
"[/help?cmd=addremove|fossil addremove]" commands.
* Editing forum posts now applies delta compression to the edits.
* Added the [/help?cmd=/fileedit|/fileedit page], which allows
editing of text files online. Requires explicit activation by
a setup user.
* Update the built-in SQLite so that the
"[/help?cmd=sql|fossil sql]" command supports new output
modes ".mode box" and ".mode json".
* Delta compression now applied to forum edits.
<a name='v2_11'></a>
<h2>Changes for Version 2.11 (2020-05-25)</h2>
* Support [/md_rules|Markdown] in the default ticket configuration.
* Timestamp strings in [./checkin_names.wiki|object names]
can now omit punctation. So, for example, "202004181942" and
"2020-04-18 19:42" mean the same thing.
* Enhance backlink processing so that it works with Markdown-formatted
tickets and so that it works for wiki pages.
Ticket [a3572c6a5b47cd5a].
<ul><li> "[/help?cmd=rebuild|fossil rebuild]" is needed to
take full advantage of this fix. Fossil will continue
to work without the rebuild, but the new backlinks will be missing.</ul>
* The algorithm for finding the
[./tech_overview.wiki#configloc|location of the configuration database]
is enhanced to be XDG-compliant.
* Add a hide/show feature to
[./wikitheory.wiki#assocwiki|associated wiki] display on
check-in and branch information pages.
* Enhance the "[/help?cmd=info|fossil info]" command so that it
works with no arguments even if not within an open check-out.
* Many improvements to the forum and especially email notification
of forum posts, in response to community feedback after switching
SQLite support from a mailing list over to the forum.
* Minimum length of a self-registered user ID increased from 3 to 6
characters.
* When the "vfx" query parameter is used on the
"[/help?cmd=/timeline|/timeline]" page, it causes the complete
text of forum posts to be displayed.
* Rework the "[/help?cmd=grep|fossil grep]" command to be more useful.
* Expose the [/help?cmd=redirect-to-https|redirect-to-https]
setting to the [/help?cmd=settings|settings] command.
* Improve support for CGI on IIS web servers.
* The [./serverext.wiki|/ext page] can now render index files,
in the same way as the embedded docs.
* Most commands now support the Unix-conventional "<tt>--</tt>"
flag to treat all following arguments as filenames
instead of flags.
* Added the [/help?cmd=mimetypes|mimetypes config setting]
(versionable) to enable mimetype overrides and custom definitions.
* Add an option on the /Admin/Timeline setup page to set a default
timeline style other than "Modern".
* In [./embeddeddoc.wiki|embedded documentation], hyperlink URLs
of the form "/doc/$CURRENT/..." the "$CURRENT" text is translated
into the check-in hash for the document currently being viewed.
* Added the [/help?cmd=/phantoms|/phantoms] webpage that shows all
phantom artifacts.
* Enhancements to phantom processing to try to reduce
bandwidth-using chatter about phantoms on the sync protocol.
* Security: Fossil now assumes that the schema of every
database it opens has been tampered with by an adversary and takes
extra precautions to ensure that such tampering is harmless.
* Security: Fossil now puts the Content-Security-Policy in the
HTTP reply header, in addition to also leaving it in the
HTML <head> section, so that it is always available, even
if a custom skin overrides the HTML <head> and omits
the CSP in the process.
* Output of the [/help?cmd=diff|fossil diff -y] command automatically
adjusts according to the terminal width.
* The Content-Security-Policy is now set using the
[/help?cmd=default-csp|default-csp setting].
* Merge conflicts caused via the [/help?cmd=merge|merge] and
[/help?cmd=update|update] commands no longer leave temporary
files behind unless the new <tt>--keep-merge-files</tt> flag
is used.
* The [/help?cmd=/artifact_stats|/artifact_stats page] is now accessible
to all users if the new "artifact_stats_enable" setting is turned
on. There is a new checkbox under the /Admin/Access menu to turn
that capability on and off.
* Add the [/help?cmd=tls-config|fossil tls-config] command for viewing
the TLS configuration and the list of SSL Cert exceptions.
* Captchas all include a button to read the captcha using an audio
file, so that they can be completed by the visually impaired.
* Stop using the IP address as part of the login cookie.
* Bug fix: fix the SSL cert validation logic so that if an exception
is allowed for particular site, the exception expires as soon as the
cert changes values.
* Bug fix: the FTS search into for forum posts is now kept up-to-date
correctly.
* Bug fix: the "fossil git export" command is now working on Windows
* Bug fix: display Technote items on the timeline correctly
* Bug fix: fix the capability summary matrix of the Security Audit
page so that it does not add "anonymous" capabilities to the
"nobody" user.
* Update internal Unicode character tables, used in regular expression
handling, from version 12.1 to 13.
* Many documentation enhancements.
* Many minor enhancements to existing features.
<a name='v2_10'></a>
<h2>Changes for Version 2.10 (2019-10-04)</h2>
* Added support for [./serverext.wiki|CGI-based Server Extensions].
* Added the [/help?cmd=repolist-skin|repolist-skin] setting used to
add style to repository list pages.
* Enhance the hierarchical display of Forum threads to do less
indentation and to provide links back to the previous message
in the thread. Provide sequential numbers for all messages in
a forum thread.
* Add support for fenced code blocks and improved hyperlink
processing to the [/md_rules|markdown formatter].
* Add support for hyperlinks to wiki pages in the
[/md_rules|markdown formatter].
* Enhance the [/help?cmd=/stat|/stat] page so that it gives the
option to show a breakdown of forum posts.
* The special check-in name "merge-in:BRANCH" means the source of
the most recent merge-in from the parent branch of BRANCH.
* Add hyperlinks to branch-diffs on the /info page and from
timelines of a branch.
* Add graphical context on the [/help?cmd=/vdiff|/vdiff] page.
* Uppercase query parameters, POST parameters, and cookie names are
converted to all lowercase and entered into the parameter set,
instead of being discarded.
* Change the default [./hashpolicy.wiki|hash policy] to SHA3.
* Timeout [./server/any/cgi.md|CGI requests] after 300 seconds, or
some other value set by the
[./cgi.wiki#timeout|"timeout:" property] in the CGI script.
* The check-in lock interval is reduced from 24 hours to 60 seconds,
though the interval is now configurable using a setting.
An additional check for conflicts is added after interactive
check-in comment entry, to compensate for the reduced lock interval.
* Performance optimizations.
* Many documentation improvements.
<a name='v2_9'></a>
<h2>Changes for Version 2.9 (2019-07-13)</h2>
* Added the [/help?cmd=git|fossil git export] command and instructions
for [./mirrortogithub.md|creating a GitHub mirror of a Fossil project].
* Improved handling of relative hyperlinks on the
|
| ︙ | ︙ |
1 2 3 4 | <title>Check-in Names</title> <table align="right" border="1" width="33%" cellpadding="10"> <tr><td> | | < < | > | > | | 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 | <title>Check-in Names</title> <table align="right" border="1" width="33%" cellpadding="10"> <tr><td> <h3>Quick Reference</h3> <ul> <li> Hash prefix <li> Branch name <li> Tag name <li> Timestamp: <i>YYYY-MM-DD HH:MM:SS</i> <li> <i>tag-name</i> <big><b>:</b></big> <i>timestamp</i> <li> <b>root :</b> <i>branchname</i> <li> <b>merge-in :</b> <i>branchname</i> <li> Special names: <ul> <li> <b>tip</b> <li> <b>current</b> <li> <b>next</b> <li> <b>previous</b> or <b>prev</b> <li> <b>ckout</b> (<a href='./embeddeddocs.wiki'>embedded docs</a> only) </ul> </ul> </td></tr> </table> Many Fossil [/help|commands] and [./webui.wiki | web-interface] URLs accept check-in names as an argument. For example, the "[/help/info|info]" command accepts an optional check-in name to identify the specific checkout |
| ︙ | ︙ | |||
40 41 42 43 44 45 46 | The URL above is an example of an [./embeddeddoc.wiki | embedded documentation] page in Fossil. The bold term of the pathname is a check-in name that determines which version of the documentation to display. Fossil provides a variety of ways to specify a check-in. This document describes the various methods. | | | 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | The URL above is an example of an [./embeddeddoc.wiki | embedded documentation] page in Fossil. The bold term of the pathname is a check-in name that determines which version of the documentation to display. Fossil provides a variety of ways to specify a check-in. This document describes the various methods. <h2 id="canonical">Canonical Check-in Name</h2> The canonical name of a check-in is the hash of its [./fileformat.wiki#manifest | manifest] expressed as a 40-or-more character lowercase hexadecimal number. For example: <blockquote><pre> fossil info e5a734a19a9826973e1d073b49dc2a16aa2308f9 |
| ︙ | ︙ | |||
65 66 67 68 69 70 71 | fossil info E5a734A fossil info e5a7 </blockquote> Many web-interface screens identify check-ins by 10- or 16-character prefix of canonical name. | | | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | fossil info E5a734A fossil info e5a7 </blockquote> Many web-interface screens identify check-ins by 10- or 16-character prefix of canonical name. <h2 id="tags">Tags And Branch Names</h2> Using a tag or branch name where a check-in name is expected causes Fossil to choose the most recent check-in with that tag or branch name. So, for example, as of this writing the most recent check-in that is tagged with "release" is [d0753799e44]. So the command: |
| ︙ | ︙ | |||
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | check-in is the most recent so it is the one that is selected. Note that unlike other command DVCSes, a "branch" in Fossil is not anything special; it is simply a sequence of check-ins that share a common tag. So the same mechanism that resolves tag names also resolves branch names. Note also that there can (in theory) be an ambiguity between tag names and canonical names. Suppose, for example, you had a check-in with the canonical name deed28aa99a835f01fa06d5b4a41ecc2121bf419 and you also happened to have tagged a different check-in with "deed2". If you use the "deed2" name, does it choose the canonical name or the tag name? In such cases, you can prefix the tag name with "tag:". For example: <blockquote><tt> fossil info tag:deed2 </tt></blockquote> The "tag:deed2" name will refer to the most recent check-in tagged with "deed2" not to the check-in whose canonical name begins with "deed2". | > | | | 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 | check-in is the most recent so it is the one that is selected. Note that unlike other command DVCSes, a "branch" in Fossil is not anything special; it is simply a sequence of check-ins that share a common tag. So the same mechanism that resolves tag names also resolves branch names. <a id="tagpfx"></a> Note also that there can (in theory) be an ambiguity between tag names and canonical names. Suppose, for example, you had a check-in with the canonical name deed28aa99a835f01fa06d5b4a41ecc2121bf419 and you also happened to have tagged a different check-in with "deed2". If you use the "deed2" name, does it choose the canonical name or the tag name? In such cases, you can prefix the tag name with "tag:". For example: <blockquote><tt> fossil info tag:deed2 </tt></blockquote> The "tag:deed2" name will refer to the most recent check-in tagged with "deed2" not to the check-in whose canonical name begins with "deed2". <h2 id="whole-branches">Whole Branches</h2> Usually when a branch name is specified, it means the latest check-in on that branch. But for some commands (ex: [/help/purge|purge]) a branch name on the argument means the earliest connected check-in on the branch. This seems confusing when being explained here, but it works out to be intuitive in practice. For example, the command "fossil purge XYZ" means to purge the check-in XYZ and all of its descendants. But when XYZ is in the form of a branch name, one generally wants to purge the entire branch, not just the last check-in on the branch. And so for this reason, commands like purge will interpret a branch name to be the first check-in of the branch rather than the last. If there are two or more branches with the same name, then these commands will select the first check-in of the branch that has the most recent check-in. What happens is that Fossil searches for the most recent check-in with the given tag, just as it always does. But if that tag is a branch name, it then walks back down the branch looking for the first check-in of that branch. Again, this behavior only occurs on a few commands where it make sense. <h2 id="timestamps">Timestamps</h2> A timestamp in one of the formats shown below means the most recent check-in that occurs no later than the timestamp given: 1. <i>YYYY-MM-DD</i> 2. <i>YYYY-MM-DD HH:MM</i> 3. <i>YYYY-MM-DD HH:MM:SS</i> |
| ︙ | ︙ | |||
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | But there is an option on the Admin/Timeline page of the web-interface to switch to local time. The "<b>Z</b>" suffix on a timestamp check-in name is meaningless if Fossil is in the default mode of using UTC for everything, but if Fossil has been switched to local time mode, then the "<b>Z</b>" suffix means to interpret that particular timestamp using UTC instead of local time. For an example of how timestamps are useful, consider the homepage for the Fossil website itself: <blockquote> http://www.fossil-scm.org/fossil/doc/<b>trunk</b>/www/index.wiki </blockquote> The bold component of that URL is a check-in name. To see what the Fossil website looked like on January 1, 2009, one has merely to change the URL to the following: <blockquote> http://www.fossil-scm.org/fossil/doc/<b>2009-01-01</b>/www/index.wiki </blockquote> | > > > > > > > > | | > > > > > > > | > > | > > > | > > > | | > | > > | < < | > > > | | > > > > > > > > > > > > > > > > > > | 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 |
But there is an option on the Admin/Timeline page of the web-interface to
switch to local time. The "<b>Z</b>" suffix on a timestamp check-in
name is meaningless if Fossil is in the default mode of using UTC for
everything, but if Fossil has been switched to local time mode, then the
"<b>Z</b>" suffix means to interpret that particular timestamp using
UTC instead of local time.
You may prefix a timestamp with the string “date:”, in which case
processing stops immediately, whether the string is parsed correctly and
refers to anything within the repository or not. The prefix is therefore
useful when the date could be misinterpreted as a tag. For example, a
repo could have release tags like “2020-04-01”, the date the release was
cut, but you could force Fossil to interpret that string as a date
rather than as a tag by passing “date:2020-04-01”.
For an example of how timestamps are useful,
consider the homepage for the Fossil website itself:
<blockquote>
http://www.fossil-scm.org/fossil/doc/<b>trunk</b>/www/index.wiki
</blockquote>
The bold component of that URL is a check-in name. To see what the
Fossil website looked like on January 1, 2009, one has merely to change
the URL to the following:
<blockquote>
http://www.fossil-scm.org/fossil/doc/<b>2009-01-01</b>/www/index.wiki
</blockquote>
<h2 id="tag-ts">Tag And Timestamp</h2>
A check-in name can also take the form of a tag or branch name followed by
a colon and then a timestamp. The combination means to take the most
recent check-in with the given tag or branch which is not more recent than
the timestamp. So, for example:
<blockquote>
fossil update trunk:2010-07-01T14:30
</blockquote>
Would cause Fossil to update the working check-out to be the most recent
check-in on the trunk that is not more recent that 14:30 (UTC) on
July 1, 2010.
<h2 id="root">Root Of A Branch</h2>
A branch name that begins with the "<tt>root:</tt>" prefix refers to the
last check-in in the parent branch prior to the beginning of the branch.
Such a label is useful, for example, in computing all diffs for a single
branch. The following example will show all changes in the hypothetical
branch "xyzzy":
<blockquote>
fossil diff --from root:xyzzy --to xyzzy
</blockquote>
<a id="merge-in"></a>
A branch name that begins with the "<tt>merge-in:</tt>" prefix refers not
to the root of the branch, but to the most recent merge-in for that branch
from its parent. The most recent merge-in is the version to diff the branch
against in order to see all changes in just the branch itself, omitting
any changes that have already been merged in from the parent branch.
<h2 id="special">Special Tags</h2>
The tag "tip" means the most recent check-in. The "tip" tag is roughly
equivalent to the timestamp tag "5000-01-01".
This special name works anywhere you can pass a "NAME", such as in in
<tt>/info</tt> URLs:
<blockquote><pre>
http://localhost:8080/info/tip
</pre></blockquote>
There are several other special names, but they only work from within a
check-out directory because they are relative to the current checked-out
version:
* "current": the current checked-out version
* "next": the youngest child of the current checked-out version
* "previous" or "prev": the primary (non-merge) parent of "current"
Therefore, you can use these names in a <tt>fossil info</tt> command,
but not in an <tt>/info</tt> URL, for example.
For embedded documentation URLs only, there is one more special name,
"ckout". See [./embeddeddoc.wiki#ckout | its coverage elsewhere] for
more details. You cannot currently use "ckout" anywhere other than in
<tt>/doc</tt> URLs.
<h2 id="examples">Additional Examples</h2>
To view the changes in the most recent check-in prior to the version currently
checked out:
<blockquote><pre>
fossil diff --from previous --to current
</pre></blockquote>
Suppose you are of the habit of tagging each release with a "release" tag.
Then to see everything that has changed on the trunk since the last release:
<blockquote><pre>
fossil diff --from release --to trunk
</pre></blockquote>
<h2 id="order">Resolution Order</h2>
Fossil currently resolves name strings to artifact hashes in the
following order:
# Exact matches on [#special | the special names]
# [#timestamps | Timestamps], with preference to ISO8601 forms
# [#tagpfx | tag:TAGNAME]
# [#root | root:BRANCH]
# [#merge-in | merge-in:BRANCH]
# [#tag-ts | TAG:timestamp]
# Full artifact hash or hash prefix.
# Any other type of symbolic name that Fossil extracts from
blockchain artifacts.
<div style="height:40em" id="this-space-intentionally-left-blank"></div>
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# The Server Chroot Jail
If you run Fossil as root in any mode that [serves data on the
network][srv], and you're running it on Unix or a compatible OS, Fossil
will drop itself into a [`chroot(2)` jail][cj] shortly after starting
up, once it's done everything that requires root access. Most commonly,
you run Fossil as root to allow it to bind to TCP port 80 for HTTP
service, since normal users are restricted to ports 1024 and up on OSes
where this behavior occurs.
Fossil uses the owner of the Fossil repository file as its new user
ID when dropping root privileges.
When this happens, Fossil needs to have all of its dependencies inside
the chroot jail in order to continue work. There are several things you
typically need in order to make things work properly:
* the repository file(s)
* `/dev/null` — create it with `mknod(8)` inside the jail directory
([Linux example][mnl], [OpenBSD example][obsd])
* `/dev/urandom` — ditto
* `/proc` — you might need to mount this virtual filesystem inside the
jail on Linux systems that make use of [Fossil’s server load
shedding feature][fls]
* any shared libraries your `fossil` binary is linked to, unless you
[configured Fossil with `--static`][bld] to avoid it
Fossil does all of this in order to protect the host OS. You can make it
bypass the jail part of this by passing <tt>--nojail</tt> to <tt>fossil server</tt>,
but you cannot make it skip the dropping of root privileges, on purpose.
[bld]: https://www.fossil-scm.org/fossil/doc/trunk/www/build.wiki
[cj]: https://en.wikipedia.org/wiki/Chroot
[fls]: ./loadmgmt.md
[mnl]: https://fossil-scm.org/forum/forumpost/90caff30cb
[srv]: ./server/
[obsd]: ./server/openbsd/httpd.md#chroot
|
| ︙ | ︙ | |||
78 79 80 81 82 83 84 | is a duplicate of a remote repository. Communication between repositories is via HTTP. Remote repositories are identified by URL. You can also point a web browser at a repository and get human-readable status, history, and tracking information about the project. | | | 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | is a duplicate of a remote repository. Communication between repositories is via HTTP. Remote repositories are identified by URL. You can also point a web browser at a repository and get human-readable status, history, and tracking information about the project. <h3><a id="artifacts"></a>2.1 Identification Of Artifacts</h3> 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/SHA1">SHA1</a> or <a href="http://en.wikipedia.org/wiki/SHA3">SHA3-256</a> hash of the content of that file expressed as either 40 or 64 characters of lower-case hexadecimal. (See the [./hashpolicy.wiki|hash policy |
| ︙ | ︙ | |||
194 195 196 197 198 199 200 | by downloading a <a href="https://www.fossil-scm.org/fossil/uv/download.html">pre-compiled version</a> or [./build.wiki | compiling it yourself]) and then putting that file somewhere on your PATH. Fossil is completely self-contained. It is not necessary to install any other software in order to use Fossil. You do <u>not</u> need | | | 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | by downloading a <a href="https://www.fossil-scm.org/fossil/uv/download.html">pre-compiled version</a> or [./build.wiki | compiling it yourself]) and then putting that file somewhere on your PATH. Fossil is completely self-contained. It is not necessary to 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, |
| ︙ | ︙ | |||
232 233 234 235 236 237 238 | In the next section, when we say things like "use the <b>help</b> command" we mean to use the command name "help" as the first token after the name of the Fossil executable, as shown above. <a name="workflow"></a> <h2>4.0 Workflow</h2> | | | 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | In the next section, when we say things like "use the <b>help</b> command" we mean to use the command name "help" as the first token after the name of the Fossil executable, as shown above. <a name="workflow"></a> <h2>4.0 Workflow</h2> <img src="concept2.gif" align="right" hspace="10" style="max-width:50%;"> Fossil has two modes of operation: <i>"autosync"</i> and <i>"manual-merge"</i> Autosync mode is reminiscent of CVS or SVN in that it automatically keeps your changes in synchronization with your co-workers through the use of a central server. The manual-merge mode is the standard workflow for GIT or Mercurial in that your local repository develops |
| ︙ | ︙ | |||
421 422 423 424 425 426 427 | SCGI requests from web-servers like Nginx. <li><p><b>Inetd or Stunnel.</b> Configure programs like inetd, xinetd, or stunnel to hand off HTTP requests directly to the [/help?cmd=http|fossil http] command. </ol> | | | 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 |
SCGI requests from web-servers like Nginx.
<li><p><b>Inetd or Stunnel.</b>
Configure programs like inetd, xinetd, or stunnel to hand off HTTP requests
directly to the [/help?cmd=http|fossil http] command.
</ol>
See the [./server/ | How To Configure A Fossil Server] document
for details.
<h2>6.0 Review Of Key Concepts</h2>
<ul>
<li>The <b>fossil</b> program is a self-contained stand-alone executable.
Just put it somewhere on your PATH to install it.</li>
|
| ︙ | ︙ |
| ︙ | ︙ | |||
53 54 55 56 57 58 59 | Fossil Architect (Richard Hipp) will merge changes onto the trunk.</p> Contributors are required to following the [./checkin.wiki | pre-checkin checklist] prior to every check-in to the Fossil self-hosting repository. This checklist is short and succinct and should only require a few seconds to follow. Contributors should print out a copy of the pre-checkin checklist and keep | | | 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | Fossil Architect (Richard Hipp) will merge changes onto the trunk.</p> Contributors are required to following the [./checkin.wiki | pre-checkin checklist] prior to every check-in to the Fossil self-hosting repository. This checklist is short and succinct and should only require a few seconds to follow. Contributors should print out a copy of the pre-checkin checklist and keep it on a note card beside their workstations, for quick reference. Contributors should review the [./style.wiki | Coding Style Guidelines] and mimic the coding style used through the rest of the Fossil source code. Your code should blend in. A third-party reader should be unable to distinguish your code from any other code in the source corpus. |
| ︙ | ︙ |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Fossil CSS Tips and Tricks
Many aspects of Fossil's appearance can be customized by
[customizing the site skin](customskin.md). This document
details certain specific CSS tweaks which users have asked
about via the forums.
This is a "living document" - please feel free to suggest
additions via [the Fossil forum](https://fossil-scm.org/forum/).
This document is *not* an introduction to CSS - the web is
full of tutorials on that topic. It covers only the specifics
of customizing certain CSS-based behaviors in a Fossil UI. That said...
## Is it Really `!important`?
By and large, CSS's `!important` qualifier is not needed when
customzing Fossil's CSS. On occasion, however, particular styles may
be set directly on DOM elements when Fossil generates its HTML, and
such cases require the use of `!important` to override them.
<!-- ============================================================ -->
# Main UI CSS
## Number of Columns in `/dir` View
The width of columns on the [`/dir` page](/dir) is calculated
dynamically as the page is generated, to attempt to fit the widest
name in a given directory. The number of columns is determined
automatically by CSS. To modify the number of columns and/or the entry width:
```css
div.columns {
columns: WIDTH COLUMN_COUNT !important;
/* Examples:
columns: 20ex 3 !important
columns: auto auto !important
*/
}
/* The default rule uses div.columns, but it can also be selected using: */
div.columns.files { ... }
```
The `!important` qualifier is required here because the style values are dynamically
calculated and applied when the HTML is emitted.
The file list itself can be further customized via:
```css
div.columns > ul {
...
}
ul.browser {
...
}
```
<!-- ============================================================ -->
# Forum-specific CSS
## Limiting Display Length of Long Posts
Excessively long posts can make scrolling through threads problematic,
especially on mobile devices. The amount of a post which is visible can
be configured using:
```css
div.forumPostBody {
max-height: 25em; /* change to the preferred maximum effective height */
overflow: auto; /* tells the browser to add scrollbars as needed */
}
```
|
| ︙ | ︙ | |||
81 82 83 84 85 86 87 | $<assigned_to> </td> <td align="right">Opened by:</td><td bgcolor="#d0d0d0"> $<opened_by> </td> </pre> This will add a row which displays these two fields, in the event the user has | | | 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | $<assigned_to> </td> <td align="right">Opened by:</td><td bgcolor="#d0d0d0"> $<opened_by> </td> </pre> This will add a row which displays these two fields, in the event the user has <a href="./caps/ref.html#w">ticket "edit" capability</a>. </p> </blockquote> <h2>Modify the 'edit ticket' page</h2><blockquote> <p> Before the "Severity:" line, add this: <pre> |
| ︙ | ︙ |
|
| | < | | < < < < < < < | < < < | < < < < < < | < < < < < | < < < | < < < < < < | | < < | | > | > > > | < | > | < < < | < < | < | | > | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | < | < | < < < < < | < < | < | | < < < < > | < | 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 |
# Skinning the Fossil Web Interface
The Fossil web interface comes with a pre-configured look and feel. The default
look and feel works fine in many situations. However, you may want to change
the look and feel (the "skin") of Fossil to better suite your own individual tastes.
This document provides background information to aid you in that task.
## <a name="builtin"></a>Built-in Skins
Fossil comes with multiple built-in skins. If the default skin does not
suite your tastes, perhaps one of the other built-in skins will work better.
If nothing else, the built-in skins can serve as examples or baselines that
you can use to develop your own custom skin.
The sources to these built-ins can
be found in the Fossil source tree under the skins/ folder. The
[skins/](/dir?ci=trunk&name=skins)
folder contains a separate subfolder for each built-in skin, with each
subfolders holding at least these five files:
* css.txt
* details.txt
* footer.txt
* header.txt
* js.txt
Try out the built-in skins by using the --skin option on the
[fossil ui](/help?cmd=ui) or [fossil server](/help?cmd=server) commands.
## <a name="sharing"></a>Sharing Skins
The skin of a repository is not part of the versioned state and does not
"push" or "pull" like checked-in files. The skin is local to the
repository. However, skins can be shared between repositories using
the [fossil config](/help?cmd=configuration) command.
The "fossil config push skin" command will send the local skin to a remote
repository and the "fossil config pull skin" command will import a skin
from a remote repository. The "fossil config export skin FILENAME"
will export the skin for a repository into a file FILENAME. This file
can then be imported into a different repository using the
"fossil config import FILENAME" command. Unlike "push" and "pull",
the "export" and "import" commands are able to move skins between
repositories for different projects. So, for example, if you have a
group of related repositories, you can develop a skin for one of them,
then get a consistent look across all the repositories by exporting
the skin from the first repository and importing into all the others.
The file generated by "fossil config export" could be checked into
one of your repositories and versioned, if desired. This will not
automatically change the skin when looking backwards in time, but it
will provide an historical record of what the skin used to be and
allow the historical look of the repositories to be recreated if
necessary.
When cloning a repository, the skin of the new repository is initialized to
the skin of the repository from which it was cloned.
# Structure Of A Fossil Web Page
Every HTML page generated by Fossil has the same basic structure:
<blockquote><table border=1 cellpadding=10><tbody>
<tr><td style='background-color:lightgreen;text-align:center;'>
Fossil-Generated HTML Header</td></tr>
<tr><td style='background-color:lightblue;text-align:center;'>Content Header</td></tr>
<tr><td style='background-color:lightgreen;text-align:center;'>
Fossil-Generated Content</td></tr>
<tr><td style='background-color:lightblue;text-align:center;'>Content Footer</td></tr>
<tr><td style='background-color:lightgreen;text-align:center;'>
Fossil-Generated HTML Footer</td></tr>
</tbody></table></blockquote>
The green parts are generated by Fossil. The blue parts are things that
you, the administrator, get to modify in order to customize the skin.
Fossil *usually* (but not always - [see below](#override))
generates the initial HTML Header section of a page. The
generated HTML Header will look something like this:
<html>
<head>
<base href="..." />
<meta http-equiv="Content-Security-Policy" content="...." />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>....</title>
<link rel="stylesheet" href="..." type="text/css" />
</head>
<body>
In most cases, it is best to leave the Fossil-generated HTML Header alone.
The configurable part of the skin begins with the Content Header section which
should followign the following template:
<div class="header">
... top banner and menu bar ...
</div>
Note that `<div class="header">` and `</div>` tags must be included in the
Content Header text of the skin. In other words, you the administrator need
to supply that text as part of your skin customization.
The Fossil-generated Content section immediately follows the Content Header.
The Content section will looks like this:
<div class="content">
... Fossil-generated content here ...
</div>
After the Content is the custom Content Footer section which should
following this template:
<div class="footer">
... skin-specific stuff here ...
</div>
<script nonce="$nonce">
<th1>styleScript</th1>
</script>
As with the Content Header, the template elements of the Content Footer
should appear exactly as they are shown.
Finally, Fossil always adds its own footer (unless overridden)
to close out the generated HTML:
</body>
</html>
## <a name="override"></a>Overriding the HTML Header and Footer
Notice that the `<html>`, `<head>`, and opening `<body>`
elements at the beginning of the document,
and the closing `</body>` and `</html>` elements at the end are automatically
generated by Fossil. This is recommended.
However, for maximum design flexibility, Fossil allows those elements to be
supplied as part of the configurable Content Header and Content Footer.
If the Content Header contains the text "`<body`", then Fossil assumes that
the Content Header and Content Footer will handle all of the `<html>`,
`<head>`, and `<body>` text itself, and the Fossil-generated header and
footer will be blank.
When overriding the HTML Header in this way, you will probably want to use some
of the [TH1 variables documented below](#vars) such as `$stylesheet_url`
to avoid hand-writing code that Fossil can generate for you.
# Designing, Debugging, and Installing A Custom Skin
It is possible to develop a new skin from scratch. But a better and easier
approach is to use one of the existing built-in skins as a baseline and
make incremental modifications, testing after each step, to obtain the
desired result.
The skin is controlled by five files:
<blockquote><dl>
<dt><b>css.txt</b></dt><dd>
<p>The css.txt file is the text of the CSS for Fossil.
Fossil might add additional CSS elements after the
the css.txt file, if it sees that the css.txt omits some
CSS components that Fossil needs. But for the most part,
the content of the css.txt is the CSS for the page.</dd>
<dt><b>details.txt</b><dt><dd>
<p>The details.txt file is short list of settings that control
the look and feel, mostly of the timeline. The default
details.txt file looks like this:
<blockquote><pre>
timeline-arrowheads: 1
timeline-circle-nodes: 1
timeline-color-graph-lines: 1
white-foreground: 0
</pre></blockquote>
The first three setings in details.txt control the appearance
of certain aspects of the timeline graph. The number on the
right is a boolean - "1" to activate the feature and "0" to
disable it. The "white-foreground:" setting should be set to
"1" if the page color has light-color text on a darker background,
and "0" if the page has dark text on a light-colored background.</dd>
<dt><b>footer.txt</b> and <b>header.txt</b></dt><dd>
<p>The footer.txt and header.txt files contain the Content Footer
and Content Header respectively. Of these, the Content Header is
the most important, as it contains the markup used to generate
the banner and menu bar for each page.
<p>Both the footer.txt and header.txt file are
[processed using TH1](#headfoot) prior to being output as
part of the overall web page.</dd>
<dt><b>js.txt</b></dt><dd>
<p>The js.txt file is intended to be javascript. The complete
text of this javascript is typically inserted into the Content Footer
by this part of the "footer.txt" file:
<blockquote><pre>
<script nonce="$nonce">
<th1>styleScript</th1>
</script>
</pre></blockquote>
<p>The js.txt file was originally intended to insert javascript
that controls the hamburger menu.
The footer.txt file probably should contain lines like the
above, even if js.txt is empty.</dd>
</dl></blockquote>
Developing a new skin is simply a matter of creating appropriate
versions of these five control files.
### Skin Development Using The Web Interface
Users with admin privileges can use the Admin/Skin configuration page
on the web interface to develop a new skin. The development of a new
skin occurs without disrupting the existing skin. So you can work on
a new skin for a Fossil instance while the existing skin is still in
active use.
The new skin is a "draft" skin. You initialize one of 9 draft skins
to either the current skin or to one of the built-in skins. Then
use forms to edit the 5 control files described above. The new
skin can be tested after each edit. Finally, once the new skin is
working as desired, the draft skin is "published" and becomes the
new live skin that most users see.
### Skin Development Using A Local Text Editor
An alternative approach is to copy the five control files for your
baseline skin into a temporary working directory (here called
"./newskin") and then launch the [fossil ui](/help?cmd=ui) command
with the "--skin ./newskin" option. If the argument to the --skin
option contains a "/" character, then the five control files are
read out of the directory named. You can then edit the control
files in the ./newskin folder using you favorite text editor, and
press "Reload" on your browser to see the effects.
## <a name="headfoot"></a>Header and Footer Processing
The `header.txt` and `footer.txt` control files of a skin are the HTML text
of the Contnet Header and Content Footer, except that before being inserted
into the output stream, the text is run through a
[TH1 interpreter](./th1.md) that might adjust the text as follows:
* All text within <th1>...</th1> is omitted from the
output and is instead run as a TH1 script. That TH1
script has the opportunity to insert new text in place of itself,
or to inhibit or enable the output of subsequent text.
* Text of the form "$NAME" or "$<NAME>" is replaced with
the value of the TH1 variable NAME.
For example, first few lines of a typical Content Header will look
like this:
<div class="header">
<div class="title"><h1>$<project_name></h1>$<title>/div>
After variables are substituted by TH1, that will look more like this:
<div class="header">
<div class="title"><h1>Project Name</h1>Page Title</div>
As you can see, two TH1 variable substitutions were done.
The same TH1 interpreter is used for both the header and the footer
and for all scripts contained within them both. Hence, any global
TH1 variables that are set by the header are available to the footer.
## <a name="menu"></a>Customizing the ≡ Hamburger Menu
The menu bar of the default skin has an entry to open a drop-down menu with
additional navigation links, represented by the ≡ button (hence the name
"hamburger menu"). The Javascript logic to open and close the hamburger menu
when the button is clicked is contained in the optional Javascript part (js.txt)
of the default skin. Out of the box, the drop-down menu shows the [Site
Map](../../../sitemap), loaded by an AJAX request prior to the first display.
|
| ︙ | ︙ | |||
191 192 193 194 195 196 197 |
</div>
The custom `data-anim-ms` attribute can be added to the panel element to direct
the Javascript logic to override the default menu animation duration of 400 ms.
A faster animation duration of 80-200 ms may be preferred for smaller menus. The
animation is disabled by setting the attribute to `"0"`.
| > | < | | 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 |
</div>
The custom `data-anim-ms` attribute can be added to the panel element to direct
the Javascript logic to override the default menu animation duration of 400 ms.
A faster animation duration of 80-200 ms may be preferred for smaller menus. The
animation is disabled by setting the attribute to `"0"`.
## <a name="vars"></a>TH1 Variables
Before expanding the TH1 within the header and footer, Fossil first
initializes a number of TH1 variables to values that depend on
repository settings and the specific page being generated.
* **project_name** - The project_name variable is filled with the
name of the project as configured under the Admin/Configuration
menu.
* **project_description** - The project_description variable is
filled with the description of the project as configured under
|
| ︙ | ︙ | |||
232 233 234 235 236 237 238 |
* **current_page** - The name of the page currently being processed,
without the leading "/" and without query parameters.
Examples: "timeline", "doc/trunk/README.txt", "wiki".
* **csrf_token** - A token used to prevent cross-site request forgery.
| | | > > | 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 |
* **current_page** - The name of the page currently being processed,
without the leading "/" and without query parameters.
Examples: "timeline", "doc/trunk/README.txt", "wiki".
* **csrf_token** - A token used to prevent cross-site request forgery.
* **default_csp** - [Fossil’s default CSP](./defcsp.md) unless
[overridden by custom TH1 code](./defcsp.md#th1). Useful within
the skin for inserting the CSP into a `<meta>` tag within [a
custom `<head>` element](#headfoot).
* **nonce** - The value of the cryptographic nonce for the request
being processed.
* **release_version** - The release version of Fossil. Ex: "1.31"
* **manifest_version** - A prefix on the check-in hash of the
|
| ︙ | ︙ | |||
266 267 268 269 270 271 272 |
project, as configured on the Admin/Logo page.
All of the above are variables in the sense that either the header or the
footer is free to change or erase them. But they should probably be treated
as constants. New predefined values are likely to be added in future
releases of Fossil.
| > | < | 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 |
project, as configured on the Admin/Logo page.
All of the above are variables in the sense that either the header or the
footer is free to change or erase them. But they should probably be treated
as constants. New predefined values are likely to be added in future
releases of Fossil.
## <a name="procedure"></a>Suggested Skin Customization Procedure
Developers are free, of course, to develop new skins using any method they
want, but the following is a technique that has worked well in the past and
can serve as a starting point for future work:
1. Select a built-in skin that is closest to the desired look. Make
copies of the css, footer, and header into files name "css.txt",
|
| ︙ | ︙ | |||
300 301 302 303 304 305 306 |
Iterate until the desired look is achieved.
4. Copy/paste the resulting css.txt, details.txt,
header.txt, and footer.txt files
into the CSS, details, header, and footer configuration screens
under the Admin/Skins menu.
| > | < | 436 437 438 439 440 441 442 443 444 445 446 |
Iterate until the desired look is achieved.
4. Copy/paste the resulting css.txt, details.txt,
header.txt, and footer.txt files
into the CSS, details, header, and footer configuration screens
under the Admin/Skins menu.
## See Also
* [Customizing the Timeline Graph](customgraph.md)
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# The Default Content Security Policy (CSP)
When Fossil’s web interface generates an HTML page, it normally includes
a [Content Security Policy][csp] (CSP) in the `<head>`. The CSP defines
a “white list” to tell the browser what types of content (HTML, images,
CSS, JavaScript...) the document may reference and the sources the
browser is allowed to pull and interpret such content from. The aim is to prevent
certain classes of [cross-site scripting][xss] (XSS) and code injection
attacks. The browser will not pull content types disallowed by the CSP;
the CSP also adds restrictions on the types of inline content the
browser is allowed to interpret.
Fossil has built-in server-side content filtering logic. For example, it
purposely breaks `<script>` tags when it finds them in Markdown and
Fossil Wiki documents. (But not in [HTML-formatted embedded
docs][hfed]!) We also back that with multiple levels of analysis and
checks to find and fix content security problems: compile-time static
analysis, run-time dynamic analysis, and manual code inspection. Fossil
is open source software, so it benefits from the “[many
eyeballs][llaw],” limited by the size of its developer community.
However, there is a practical limit to the power of server-side
filtering and code quality practices.
First, there is an endless battle between those looking for clever paths
around such barriers and those erecting the barriers. The developers of
Fossil are committed to holding up our end of that fight, but this is,
to some extent, a reactive posture. It is cold comfort if Fossil’s
developers react quickly to a report of code injection — as we do! — if
the bad guys learn of it and start exploiting it first.
Second, Fossil has purposefully powerful features that are inherently
difficult to police from the server side: HTML tags [in wiki](/wiki_rules)
and [in Markdown](/md_rules) docs, [TH1 docs](./th1.md), the Admin →
Wiki → “Use HTML as wiki markup language” mode, etc.
Fossil’s strong default CSP adds client-side filtering as a backstop for
all of this.
Fossil site administrators can [modify the default CSP](#override), perhaps
to add trusted external sources for auxiliary content. But for maximum
safety, site developers are encouraged to work within the restrictions
imposed by the default CSP and avoid the temptation to relax the CSP
unless they fully understand the security implications of what they are
doing.
[llaw]: https://en.wikipedia.org/wiki/Linus%27s_Law
## The Default Restrictions
The Fossil default CSP declares the following content restrictions:
### <a name="base"></a> default-src 'self' data:
This policy means mixed-origin content isn’t allowed, so you can’t refer
to resources on other web domains. Browsers will ignore a link like the
one in the following Markdown under our default CSP:

If you look in the browser’s developer console, you should see a CSP
error when attempting to render such a page.
The default policy does allow inline `data:` URIs, which means you could
[data-encode][de] your image content and put it inline within the
document:

That method is best used for fairly small resources. Large `data:` URIs
are hard to read and edit. There are secondary problems as well: if you
put a large image into a Fossil forum post this way, anyone subscribed
to email alerts will get a copy of the raw URI text, which can amount to
pages and pages of [ugly Base64-encoded text][b64].
For inline images within [embedded documentation][ed], it suffices to
store the referred-to files in the repo and then refer to them using
repo-relative URLs:

This avoids bloating the doc text with `data:` URI blobs:
There are many other cases, [covered below](#serving).
[b64]: https://en.wikipedia.org/wiki/Base64
[svr]: ./server/
### <a name="style"></a> style-src 'self' 'unsafe-inline'
This policy allows CSS information to come from separate files hosted
under the Fossil repo server’s Internet domain. It also allows inline CSS
`<style>` tags within the document text.
The `'unsafe-inline'` declaration allows CSS within individual HTML
elements:
<p style="margin-left: 4em">Indented text.</p>
As the "`unsafe-`" prefix on the name implies, the `'unsafe-inline'`
feature is suboptimal for security. However, there are
a few places in the Fossil-generated HTML that benefit from this
flexibility and the work-arounds are verbose and difficult to maintain.
Futhermore, the harm that can be done with style injections is far
less than the harm possible with injected javascript. And so the
`'unsafe-inline'` compromise is accepted for now, though it might
go away in some future release of Fossil.
### <a name="script"></a> script-src 'self' 'nonce-%s'
This policy disables in-line JavaScript and only allows `<script>`
elements if the `<script>` includes a `nonce` attribute that matches the
one declared by the CSP. That nonce is a large random number, unique for
each HTTP page generated by Fossil, so an attacker cannot guess the
value, so the browser will ignore an attacker’s injected JavaScript.
That nonce can only come from one of three sources, all of which should
be protected at the system administration level on the Fossil server:
* **Fossil server C code:** All code paths in Fossil that emit
`<script>` elements include the `nonce` attribute. There are several
cases, such as the “JavaScript” section of a [custom skin][cs].
That text is currently inserted into each HTML page generated by
Fossil,¹ which means it needs to include a `nonce` attribute to
allow it to run under this default CSP. We consider JavaScript
emitted via these paths to be safe because it’s audited by the
Fossil developers. We assume that you got your Fossil server’s code
from a trustworthy source and that an attacker cannot replace your
Fossil server binary.
* **TH1 code:** The Fossil TH1 interpreter pre-defines the
[`$nonce` variable](./th1.md#nonce) for use in [custom skins][cs]. For
example, some of the stock skins that ship with Fossil include a
wall clock feature up in the corner that updates once a minute.
These paths are safe in the default Fossil configuration because
only the [all-powerful Setup user][su] can write TH1 code that
executes in the server’s running context.
There is, however, [a default-disabled path](#xss) to beware of,
covered in the next section.
* **[CGI server extensions][ext]:** Fossil exports the nonce to the
CGI in the `FOSSIL_NONCE` environment variable, which it can then
use in `<script>` elements it generates. Because these extensions
can only be installed by the Fossil server’s system administrator,
this path is also considered safe.
[ext]: ./serverext.wiki
[su]: ./caps/admin-v-setup.md#apsu
#### <a name="xss"></a>Cross-Site Scripting via Ordinary User Capabilities
We’re so restrictive about how we treat JavaScript because it can lead
to difficult-to-avoid scripting attacks. If we used the same CSP for
`<script>` tags [as for `<style>` tags](#style), anyone with check-in
rights on your repository could add a JavaScript file to your repository
and then refer to it from other content added to the site. Since
JavaScript code can access any data from any URI served under its same
Internet domain, and many Fossil users host multiple Fossil repositories
under a single Internet domain, such a CSP would only be safe if all of
those repositories are trusted equally.
Consider [the Chisel hosting service](http://chiselapp.com/), which
offers free Fossil repository hosting to anyone on the Internet, all
served under the same `http://chiselapp.com/user/$NAME/$REPO` URL
scheme. Any one of those hundreds of repositories could trick you into
visiting their repository home page, set to [an HTML-formatted embedded
doc page][hfed] via Admin → Configuration → Index Page, with this
content:
<script src="/doc/trunk/bad.js"></script>
That script can then do anything allowed in JavaScript to *any other*
Chisel repository your browser can access.The possibilities for mischief
are *vast*. For just one example, if you have login cookies on four
different Chisel repositories, your attacker could harvest the login
cookies for all of them through this path if we allowed Fossil to serve
JavaScript files under the same CSP policy as we do for CSS files.
This is why the default configuration of Fossil has no way for [embedded
docs][ed], [wiki articles][wiki], [tickets][tkt], [forum posts][fp], or
[tech notes][tn] to automatically insert a nonce into the page content.
This is all user-provided content, which could link to user-provided
JavaScript via check-in rights, effectively giving all such users a
capability that is usually reserved to the repository’s administrator.
The default-disabled [TH1 documents feature][edtf] is the only known
path around this restriction. If you are serving a Fossil repository
that has any user you do not implicitly trust to a level that you would
willingly run any JavaScript code they’ve provided, blind, you **must
not** give the `--with-th1-docs` option when configuring Fossil, because
that allows substitution of the [pre-defined `$nonce` TH1
variable](./th1.md#nonce) into [HTML-formatted embedded docs][hfed]:
<script src="/doc/trunk/bad.js" nonce="$nonce"></script>
Even with this feature enabled, you cannot put `<script>` tags into
Fossil Wiki or Markdown-formatted content, because our HTML generators
for those formats purposely strip or disable such tags in the output.
Therefore, if you trust those users with check-in rights to provide
JavaScript but not those allowed to file tickets, append to wiki
articles, etc., you might justify enabling TH1 docs on your repository,
since the only way to create or modify HTML-formatted embedded docs is
through check-ins.
[ed]: ./embeddeddoc.wiki
[edtf]: ./embeddeddoc.wiki#th1
[hfed]: ./embeddeddoc.wiki#html
## <a name="serving"></a>Serving Files Within the Limits
There are several ways to serve files within the above restrictions,
avoiding the need to [override the default CSP](#override). In
decreasing order of simplicity and preference:
1. Within [embedded documentation][ed] (only!) you can refer to files
stored in the repo using document-relative file URLs:

2. Relative file URLs don’t work from [wiki articles][wiki],
[tickets][tkt], [forum posts][fp], or [tech notes][tn], but you can
still refer to them inside the repo with [`/doc`][du] or
[`/raw`][ru] URLs:

<img src="/raw/logo.png" style="float: right; margin-left: 2em">
3. Store the files as [unversioned content][uv], referred to using
[`/uv`][uu] URLs instead:

4. Use the [optional CGI server extensions feature](./serverext.wiki)
to serve such content via `/ext` URLs.
5. Put Fossil behind a [front-end proxy server][svr] as a virtual
subdirectory within the site, so that our default CSP’s “self” rules
match static file routes on that same site. For instance, your repo
might be at `https://example.com/code`, allowing documentes in that
repo to refer to:
* images as `/image/foo.png`
* JavaScript files as `/js/bar.js`
* CSS style sheets as `/style/qux.css`
Although those files are all outside the Fossil repo at `/code`,
keep in mind that it is the browser’s notion of “self” that matters
here, not Fossil’s. All resources come from the same Internet
domain, so the browser cannot distinguish Fossil-provided content
from static content served directly by the proxy server.
This method opens up many other potential benefits, such as [TLS
encryption](./tls-nginx.md), high-performance tuning via custom HTTP
headers, integration with other web technologies like PHP, etc.
You might wonder why we rank in-repo content as most preferred above. It
is because the first two options are the only ones that cause such
resources to be included in an initial clone or in subsequent repo
syncs. The methods further down the list have a number of undesirable
properties:
1. Relative links to out-of-repo files break in `fossil ui` when run on
a clone.
2. Absolute links back to the public repo instance solve that:

...but using them breaks some types of failover and load-balancing
schemes, because it creates a [single point of failure][spof].
3. Absolute links fail when one’s purpose in using a clone is to
recover from the loss of a project web site by standing that clone
up [as a server][svr] elsewhere. You probably forgot to copy such
external resources in the backup copies, so that when the main repo
site disappears, so do those files.
Unversioned content is in the middle of the first list above — between
fully-external content and fully in-repo content — because it isn’t
included in a clone unless you give the `--unversioned` flag. If you
then want updates to the unversioned content to be included in syncs,
you have to give the same flag to [a `sync` command](/help?cmd=sync).
There is no equivalent with other commands such as `up` and `pull`, so
you must then remember to give `fossil uv` commands when necessary to
pull new unversioned content down.
Thus our recommendation that you refer to in-repo resources exclusively.
[du]: /help?cmd=/doc
[fp]: ./forum.wiki
[ru]: /help?cmd=/raw
[spof]: https://en.wikipedia.org/wiki/Single_point_of_failure
[tkt]: ./tickets.wiki
[tn]: ./event.wiki
[uu]: /help?cmd=/uv
[uv]: ./unvers.wiki
[wiki]: ./wikitheory.wiki
## <a name="override"></a>Overriding the Default CSP
If you wish to relax the default CSP’s restrictions or to tighten them
further, there are two ways to accomplish that:
### <a name="th1"></a>TH1 Setup Hook
The stock CSP text is hard-coded in the Fossil C source code, but it’s
only used to set the default value of one of [the TH1 skinning
variables](./customskin.md#vars), `$default_csp`. That means you can
override the default CSP by giving this variable a value before Fossil
sees that it’s undefined and uses this default.
The best place to do that is from the [`th1-setup`
script](./th1-hooks.md), which runs before TH1 processing happens during
skin processing:
$ fossil set th1-setup "set default_csp {default-src 'self'}"
This is the cleanest method, allowing you to set a custom CSP without
recompiling Fossil or providing a hand-written `<head>` section in the
Header section of a custom skin.
You can’t remove the CSP entirely with this method, but you can get the
same effect by telling the browser there are no content restrictions:
$ fossil set th1-setup 'set default_csp {default-src *}'
### <a name="header"></a>Custom Skin Header
Fossil only inserts a CSP into the HTML pages it generates when the
[skin’s Header section](./customskin.md#headfoot) doesn’t contain a
`<head>` tag. None of the stock skins include a `<head>` tag,² so if you
haven’t [created a custom skin][cs], you should be getting Fossil’s
default CSP.
We say “should” because long-time Fossil users may be hanging onto a
legacy behavior from before Fossil 2.5, when Fossil added this automatic
`<head>` insertion feature. Repositories created before that release
where the admin either defined a custom skin *or chose one of the stock
skins* (!) will effectively override this automatic HTML `<head>`
insertion feature because the skins from before that time did include
these elements. Unless the admin for such a repository updated the skin
to track this switch to automatic `<head>` insertion, the default CSP
added to the generated header text in Fossil 2.7 is probably being
overridden by the skin.
If you want the protection of the default CSP in your custom skin, the
simplest method is to leave the `<html><head>...` elements out of the
skin’s Header section, starting it with the `<div class="head">` element
instead as described in the custom skinning guide. Alternately, you can
[make use of `$default_csp`](#th1).
This then tells you one way to override Fossil’s default CSP: provide
your own HTML header in a custom skin.
A useful combination is to entirely override the default CSP in the skin
but then provide a new CSP [in the front-end proxy layer][svr]
using any of the many reverse proxy servers that can define custom HTTP
headers.
------------
**Asides and Digressions:**
1. Fossil might someday switch to serving the “JavaScript” section of a
custom skin as a virtual text file, allowing it to be cached by the
browser, reducing page load times.
2. The stock Bootstrap skin does actually include a `<head>` tag, but
from Fossil 2.7 through Fossil 2.9, it just repeated the same CSP
text that Fossil’s C code inserts into the HTML header for all other
stock skins. With Fossil 2.10, the stock Bootstrap skin uses
`$default_csp` instead, so you can [override it as above](#th1).
[cs]: ./customskin.md
[csp]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
[de]: https://dopiaza.org/tools/datauri/index.php
[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
|
| ︙ | ︙ | |||
22 23 24 25 26 27 28 |
3. Only people with check-in privileges can modify the documentation.
(This might be either an advantage or disadvantage, depending
on the nature of your project.)
We will call documentation that is included as files in the source tree
"embedded documentation".
| | | < | | > | | | > | | | < | | | | > | | > > > > > > > > | 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 |
3. Only people with check-in privileges can modify the documentation.
(This might be either an advantage or disadvantage, depending
on the nature of your project.)
We will call documentation that is included as files in the source tree
"embedded documentation".
<h1>1.0 Fossil Support For Embedded Documentation</h1>
The fossil web interface supports embedded documentation using
the "/doc" page. To access embedded documentation, one points
a web browser to a fossil URL of the following form:
<blockquote>
<i><baseurl></i><big><b>/doc/</b></big><i><version></i><big><b>/</b></big><i><filename></i>
</blockquote>
The <i><baseurl></i> is the main URL used to access the fossil web server.
For example, the <i><baseurl></i> for the fossil project itself is
[https://www.fossil-scm.org/fossil].
If you launch the web server using the "[/help?cmd=ui|fossil ui]" command line,
then the <i><baseurl></i> is usually
<b>http://localhost:8080/</b>.
The <i><version></i> is the
[./checkin_names.wiki|name of a check-in]
that contains the embedded document. This might be a hash prefix for
the check-in, or it might be the name of a branch or tag, or it might
be a timestamp. See the prior link
for more possibilities and examples.
<a id="ckout"></a>The <i><version></i> can
also be the special identifier "<b>ckout</b>".
The "<b>ckout</b>" keywords means to
pull the documentation file from the local source tree on disk, not
from the any check-in. The "<b>ckout</b>" keyword
only works when you start your server using the
"[/help?cmd=server|fossil server]" or "[/help?cmd=ui|fossil ui]"
commands. The "/doc/ckout" URL is intended to show a preview of
the documentation you are currently editing but have not yet you checked in.
The original designed purpose of the "ckout" feature is to allow the
user to preview local changes to documentation before committing the
change. This is an important facility, since unlike other document
languages like HTML, there is still a lot of variation among rendering
engines for Fossil's markup languages, Markdown and Fossil Wiki. Your
changes may look fine in your whizzy GUI Markdown editor, but what
actually matters is how <i>Fossil</i> will interpret your Markdown text.
Therefore, you should always preview your edits before committing them.
Finally, the <i><filename></i> element of the URL is the
pathname of the documentation file relative to the root of the source
tree.
The mimetype (and thus the rendering) of documentation files is
determined by the file suffix. Fossil currently understands
|
| ︙ | ︙ | |||
78 79 80 81 82 83 84 | [/md_rules | Markdown markup language]. Documentation files ending in ".txt" are plain text. Wiki, markdown, and plain text documentation files are rendered with the standard fossil header and footer added. Most other mimetypes are delivered directly to the requesting web browser without interpretation, additions, or changes. | > > | | > | > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | 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 |
[/md_rules | Markdown markup language].
Documentation files ending in ".txt" are plain text.
Wiki, markdown, and plain text documentation files
are rendered with the standard fossil header and footer added.
Most other mimetypes are delivered directly to the requesting
web browser without interpretation, additions, or changes.
<h2>1.1 HTML Rendering With Fossil Headers And Footers</h2>
<a name="html"></a>Files with the mimetype "text/html" (the .html or .htm suffix) are
usually rendered directly to the browser without interpretation.
However, if the file begins with a <div> element like this:
<b><div class='fossil-doc' data-title='<i>Title Text</i>'></b>
Then the standard Fossil header and footer are added to the document
prior to being displayed. The "class='fossil-doc'" attribute is
required for this to occur. The "data-title='...'" attribute is
optional, but if it is present the text will become the title displayed
in the Fossil header. An example of this can be seen in Fossil
Documentation Index www/permutedindex.html:
* [/file/www/permutedindex.html?txt|source text for <b>www/permutedindex.html</b>]
* [/doc/trunk/www/permutedindex.html|<b>www/permutedindex.html</b> rendered as HTML]
Beware that such HTML files render in the same browser security context
as all other embedded documentation served from Fossil; they are not
fully-independent web pages. One practical consequence of this is that
embedded <tt><script></tt> tags will cause a
[https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP | Content
Security Policy] error in your browser with the default CSP as served by
Fossil. See the documentation on [./customskin.md#headfoot | Header and
Footer Processing] and [./defcsp.md | The Default CSP].
<h1>2.0 Server-Side Text Substitution</h1>
Fossil can do a few types of substitution of server-side information
into the embedded document.
<h2>2.1 "$ROOT" In HTML and Markdown Hyperlinks</h2>
Hyperlinks in Markdown and HTML embedded documents can reference
the root of the Fossil repository using the special text "$ROOT"
at the beginning of a URL. For example, a Markdown hyperlink to
the Markdown formatting rules might be
written in the embedded document like this:
<nowiki><pre>
[Markdown formatting rules]($ROOT/wiki_rules)
</pre></nowiki>
Depending on how the how the Fossil server is configured, that hyperlink
might be renderer like one of the following:
<nowiki><pre>
<a href="/wiki_rules">Wiki formatting rules</a>
<a href="/cgi-bin/fossil/wiki_rules">Wiki formatting rules</a>
</pre></nowiki>
So, in other words, the "$ROOT" text is converted into whatever
the "<baseurl>" is for the document.
This substitution works for HTML and Markdown documents.
It does not work for Wiki embedded documents, since with
Wiki you can just begin a URL with "/" and it automatically knows
to prepend the $ROOT.
<h2>2.2 "$CURRENT" In "/doc/" Hyperlinks</h2>
Similarly, URLs of the form "/doc/$CURRENT/..." have the check-in
hash of the check-in currently being viewed substituted in place of
the "$CURRENT" text. This feature, in combination with the "$ROOT"
substitution above, allows an absolute path to be used for hyperlinks.
For example, if an embedded document documented wanted to reference
some other document in a separate file named "www/otherdoc.md",
it could use a URL like this:
<nowiki><pre>
[Other Document]($ROOT/doc/$CURRENT/www/otherdoc.md)
</pre></nowiki>
As with "$ROOT", this substitution only works for Markdown and HTML
documents. For Wiki documents, you would need to use a relative URL.
<h2 id="th1">2.3 TH1 Documents</h2>
Fossil will substitute the value of [./th1.md | TH1 expressions] within
<tt>{</tt> curly braces <tt>}</tt> into the output HTML if you have
configured it with the <tt>--with-th1-docs</tt> option, which is
disabled by default.
Since TH1 is a full scripting language, this feature essential grants
the ability to execute code on the server to any with check-in
privilege for the project.
This is a security risk that needs to be carefully managed.
The feature is off by default.
Administrators should understand and carefully assess the risks
before enabling the use of TH1 within embedded documentation.
<h1>3.0 Examples</h1>
This file that you are currently reading is an example of
embedded documentation. The name of this file in the fossil
source tree is "<b>www/embeddeddoc.wiki</b>".
You are perhaps looking at this
file using the URL:
[http://www.fossil-scm.org/fossil/doc/trunk/www/embeddeddoc.wiki].
The first part of this path, the "[http://www.fossil-scm.org/fossil]",
is the base URL. You might have originally typed:
[http://www.fossil-scm.org/]. The web server at the www.fossil-scm.org
site automatically redirects such links by appending "fossil". The
"fossil" file on www.fossil-scm.org is really a CGI script
which runs the fossil web service in CGI mode.
The "fossil" CGI script looks like this:
<blockquote><pre>
#!/usr/bin/fossil
repository: /fossil/fossil.fossil
</pre></blockquote>
This is one of the many ways to set up a
<a href="./server/">Fossil server</a>.
The "<b>/trunk/</b>" part of the URL tells fossil to use
the documentation files from the most recent trunk check-in.
If you wanted to see an historical version of this document,
you could substitute the name of a check-in for "<b>/trunk/</b>".
For example, to see the version of this document associated with
check-in [9be1b00392], simply replace the "<b>/trunk/</b>" with
|
| ︙ | ︙ | |||
142 143 144 145 146 147 148 | When the symbolic name is a date and time, fossil shows the version of the document that was most recently checked in as of the date and time specified. So, for example, to see what the fossil website looked like at the beginning of 2010, enter: <blockquote> | | | | 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | When the symbolic name is a date and time, fossil shows the version of the document that was most recently checked in as of the date and time specified. So, for example, to see what the fossil website looked like at the beginning of 2010, enter: <blockquote> <a href="http://www.fossil-scm.org/fossil/doc/2010-01-01/www/index.wiki"> http://www.fossil-scm.org/fossil/doc/<b>2010-01-01</b>/www/index.wiki </a> </blockquote> The file that encodes this document is stored in the fossil source tree under the name "<b>www/embeddeddoc.wiki</b>" and so that name forms the last part of the URL for this document. |
| ︙ | ︙ |
| ︙ | ︙ | |||
48 49 50 51 52 53 54 | </pre></blockquote> A setting of 1 or greater prevents fossil from trying to remember the previous sync password. <blockquote><pre> export FOSSIL_SECURITY_LEVEL=2 </pre></blockquote> A setting of 2 or greater | | | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | </pre></blockquote> A setting of 1 or greater prevents fossil from trying to remember the previous sync password. <blockquote><pre> export FOSSIL_SECURITY_LEVEL=2 </pre></blockquote> A setting of 2 or greater causes all password prompts to be preceded by a random translation matrix similar to the following: <blockquote><pre> abcde fghij klmno pqrst uvwyz qresw gjymu dpcoa fhkzv inlbt </pre></blockquote> When entering the password, the user must substitute the letter on the second line that corresponds to the letter on the first line. Uppercase substitutes |
| ︙ | ︙ |
| ︙ | ︙ | |||
110 111 112 113 114 115 116 | `--vfs VFSNAME`: Load the named VFS into SQLite. Environment Variables --------------------- | | | | < < | | 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | `--vfs VFSNAME`: Load the named VFS into SQLite. Environment Variables --------------------- The location of the user's account-wide [configuration database][configdb] depends on the operating system and on the existance of various environment variables and/or files. See the discussion of the [configuration database location algorithm][configloc] for details. `EDITOR`: Name the editor to use for check-in and stash comments. Overridden by the local or global `editor` setting or the `VISUAL` environment variable. `FOSSIL_BREAK`: If set, an opportunity will be created to attach a debugger to the Fossil process prior to any significant work being |
| ︙ | ︙ | |||
138 139 140 141 142 143 144 | `FOSSIL_FORCE_WIKI_MODERATION`: If set, *ALL* changes for wiki pages will be required to go through moderation (even those performed by the local interactive user via the command line). This can be useful for local (or remote) testing of the moderation subsystem and its impact on the contents and status of wiki pages. | | < < < | | | | 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | `FOSSIL_FORCE_WIKI_MODERATION`: If set, *ALL* changes for wiki pages will be required to go through moderation (even those performed by the local interactive user via the command line). This can be useful for local (or remote) testing of the moderation subsystem and its impact on the contents and status of wiki pages. `FOSSIL_HOME`: Location of [configuration database][configdb]. See the [configuration database location][configloc] description for additional information. `FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for SEE as text to be hashed into the actual encryption key. This has no effect if Fossil was not compiled with SEE support enabled. `FOSSIL_USER`: Name of the default user account if the checkout, local or global `default-user` setting is not present. The first environment variable found in the environment from the list `FOSSIL_USER`, `USER`, `LOGNAME`, and `USERNAME` is the user name. If none of those are set, |
| ︙ | ︙ | |||
173 174 175 176 177 178 179 | * _≥2_ — Use a scrambled matrix for password input. `FOSSIL_TCL_PATH`: When Tcl stubs support is configured, point to a specific file or folder containing the version of Tcl to load at run time. | < < < < < < < < < < | < < < | | 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 | * _≥2_ — Use a scrambled matrix for password input. `FOSSIL_TCL_PATH`: When Tcl stubs support is configured, point to a specific file or folder containing the version of Tcl to load at run time. `FOSSIL_TEST_DANGEROUS_IGNORE_OPEN_CHECKOUT`: When set to the literal value `YES_DO_IT`, the test suite will relax the constraint that some tests may not run within an open checkout. This is subject to removal in the future. `FOSSIL_VFS`: Name a VFS to load into SQLite. `GATEWAY_INTERFACE`: If present and the `--nocgi` option is not, assume fossil is invoked from a web server as a CGI command, and act accordingly. `HOME`: Potential location of the [configuration database][configdb]. See the [configuration database location][configloc] description for details. `HOMEDRIVE`, `HOMEPATH`: (Windows) Location of the `~/.fossil` file. The first environment variable found in the environment from the list `FOSSIL_HOME`, `LOCALAPPDATA` (Windows), `APPDATA` (Windows), `HOMEDRIVE` and `HOMEPATH` (Windows, used together), and `HOME` is used as the location of the `~/.fossil` file. |
| ︙ | ︙ | |||
257 258 259 260 261 262 263 | `SQLITE_TMPDIR`: Names the temporary file location for SQLite. When set, this will be used instead of `TMPDIR`. `SYSTEMROOT`: (Windows) Used to locate `notepad.exe` as a fall back comment editor. | < < < < < < | 239 240 241 242 243 244 245 246 247 248 249 250 251 252 | `SQLITE_TMPDIR`: Names the temporary file location for SQLite. When set, this will be used instead of `TMPDIR`. `SYSTEMROOT`: (Windows) Used to locate `notepad.exe` as a fall back comment editor. `TERM`: If the linenoise library is used (almost certainly not on Windows), it will check `TERM` to verify that the interactive terminal is not named on a short list on terminals known to not work with linenoise. Linenoise is a library that provides command history and command line editing to interactive programs, and can be used in the `fossil sqlite3` command. |
| ︙ | ︙ | |||
294 295 296 297 298 299 300 | when processing the `--set-anon-caps` option for the `test-th-eval`, `test-th-render`, and `test-th-source` test commands. `TH1_TEST_USER_CAPS`: Override the default user permissions used when processing the `--set-user-caps` option for the `test-th-eval`, `test-th-render`, and `test-th-source` test commands. | < < < < < < < < < < < < | 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 | when processing the `--set-anon-caps` option for the `test-th-eval`, `test-th-render`, and `test-th-source` test commands. `TH1_TEST_USER_CAPS`: Override the default user permissions used when processing the `--set-user-caps` option for the `test-th-eval`, `test-th-render`, and `test-th-source` test commands. `TMPDIR`: Names the temporary file location for SQLite. `USER`: Name of the logged in user on many Unix-like platforms. Used as the fossil user name if `FOSSIL_USER` is not specified. See the discussion of Fossil Username below for a lot more detail. `USERNAME`: Name of the logged in user on Windows platforms. Used as the fossil user name if `FOSSIL_USER` is not specified. See the discussion of Fossil Username below for a lot more detail. `VISUAL`: Name the editor to use for check-in and stash comments. Overrides the `EDITOR` environment variable. Overridden by the local or global `editor` setting. Notes on Related Values |
| ︙ | ︙ | |||
419 420 421 422 423 424 425 | in the clone even before any users have been created, and in that case it will be the new admin user. If `default-user` is not set, then the first found environment variable from the list `FOSSIL_USER`, `USER`, `LOGNAME`, and `USERNAME`, is the user name. As a final fallback, if none of those are set, then the default user name is "root". | | | > | | < | | | > < | | | < > > > | | | > | > > | > > | > | > | | < | < | | | < | | 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 | in the clone even before any users have been created, and in that case it will be the new admin user. If `default-user` is not set, then the first found environment variable from the list `FOSSIL_USER`, `USER`, `LOGNAME`, and `USERNAME`, is the user name. As a final fallback, if none of those are set, then the default user name is "root". ### Configuration Database Location Fossil keeps some information pertinent to each user in the user's [configuration database file][configdb]. The configuration database file includes the global settings and the list of repositories and checkouts used by `fossil all`. The location of the configuration database file depends on the operating system and on the existance of various environment variables and/or files. In brief, the configuration database is usually: * Traditional unix → "`$HOME/.fossil`" * Windows → "`%LOCALAPPDATA%/_fossil`" * [XDG-unix][xdg] → "`$HOME/.config/fossil.db`" [xdg]: https://www.freedesktop.org/wiki/ See the [configuration database location algorithm][configloc] discussion for full information. ### SQLite VFS to use See [the SQLite documentation](http://www.sqlite.org/vfs.html) for an explanation of what a VFS actually is and what it does. If the default VFS underneath SQLite is not suitable, an alternative can be selected with either the `--vfs VFSNAME` option or the `FOSSIL_VFS` environment variable. The `--vfs` option takes precedence. ### <a name="temp"></a>Temporary File Location Fossil places some temporary files in the checkout directory. Most notably, supporting files related to merge conflicts are placed in the same folder as the merge result. Other temporary files need a different home. The rules for choosing one are complicated. Fossil-specific code uses `FOSSIL_TEMP`, `TEMP`, and `TMP`, in that order. Fossil’s own test suite prepends `FOSSIL_TEST_TEMP` to that list. The underlying SQLite code uses several different path sets for its temp files, depending on the platform type. On Unix-like platforms, excepting Cygwin, SQLite first checks the environment variables `SQLITE_TMPDIR` and `TMPDIR`, in that order. If neither is defined, it falls back to a hard-coded list of paths: `/var/tmp`, `/usr/tmp`, and `/tmp`. If all of that fails, it uses the current working directory. For Cygwin builds, SQLite instead uses the first defined variable in this list: `SQLITE_TMPDIR`, `TMPDIR`, `TMP`, `TEMP`, and `USERPROFILE`. For native Windows builds, SQLite simply calls the OS’s [`GetTempPath()` API][gtp]. See that reference page for details. [gtp]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364992%28v=vs.85%29.aspx That said, it is not unusual for utilities on all platforms to assume that `TEMP` or `TMP` point somewhere safe for temporary files. |
| ︙ | ︙ | |||
504 505 506 507 508 509 510 | `google-chrome` that it can find on the `PATH`. On Apple platforms, it assumes that `open` is the command to open an URL in the user's configured default browser. On Windows platforms, it assumes that `start` is the command to open an URL in the user's configured default browser. | > > > | 474 475 476 477 478 479 480 481 482 483 | `google-chrome` that it can find on the `PATH`. On Apple platforms, it assumes that `open` is the command to open an URL in the user's configured default browser. On Windows platforms, it assumes that `start` is the command to open an URL in the user's configured default browser. [configdb]: ./tech_overview.wiki#configdb [configloc]: ./tech_overview.wiki#configloc |
| ︙ | ︙ | |||
31 32 33 34 35 36 37 |
* <b>Process Checkpoints</b>. For projects that have a formal process,
technotes can be used to record the completion or the initiation of
various process steps. For example, a technote can be used to record
the successful completion of a long-running test, perhaps with
performance results and details of where the test was run and who
ran it recorded in the wiki content.
| | | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
* <b>Process Checkpoints</b>. For projects that have a formal process,
technotes can be used to record the completion or the initiation of
various process steps. For example, a technote can be used to record
the successful completion of a long-running test, perhaps with
performance results and details of where the test was run and who
ran it recorded in the wiki content.
* <b>News Articles</b>. Significant occurrences in the life cycle of
a project can be recorded as news articles using technotes. Perhaps the
domain name of the canonical website for a project changes, or new
server hardware is obtained. Such happenings are appropriate for
reporting as news.
* <b>Announcements</b>. Changes to the composition of the development
team or acquisition of new project sponsors can be communicated as
|
| ︙ | ︙ |
1 2 3 | <title>Fossil FAQ</title> <h1 align="center">Frequently Asked Questions</h1> | | > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | <title>Fossil FAQ</title> <h1 align="center">Frequently Asked Questions</h1> <p>Note: This page is old and has not been kept up-to-date. See the [/finfo?name=www/faq.wiki|change history of this page].</p> <ol> <li><a href="#q1">What GUIs are available for fossil?</a></li> <li><a href="#q2">What is the difference between a "branch" and a "fork"?</a></li> <li><a href="#q3">How do I create a new branch?</a></li> <li><a href="#q4">How do I tag a check-in?</a></li> <li><a href="#q5">How do I create a private branch that won't get pushed back to the |
| ︙ | ︙ |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# The fileedit Page
This document describes the limitations of, caveats for, and
disclaimers for the [](/fileedit) page, which provides users with
[checkin privileges](./caps/index.md) basic editing features for files
via the web interface.
# Important Caveats and Disclaimers
Predictably, the ability to edit files in a repository from a web
browser halfway around the world comes with several obligatory caveats
and disclaimers...
## `/fileedit` Does *Nothing* by Default.
In order to "activate" it, a user with [the "setup"
permission](./caps/index.md) must set the
[fileedit-glob](/help?cmd=fileedit-glob) repository setting to a
comma- or newline-delimited list of globs representing a whitelist of
files which may be edited online. Any user with commit access may then
edit files matching one of those globs. Certain pages within the UI
get an "edit" link added to them when the current user's permissions
and the whitelist both permit editing of that file.
## CSRF & HTTP Referrer Headers
In order to protect against [Cross-site Request Forgery (CSRF)][csrf]
attacks, Fossil UI features which write to the database require that
the browser send the so-called [HTTP `Referer` header][referer]
(noting that the misspelling of "referrer" is a historical accident
which has long-since been standardized!). Modern browsers, by default,
include such information automatically for *interactive* actions which
lead to a request, e.g. clicking on a link back to the same
server. However, `/fileedit` uses asynchronous ["XHR"][xhr]
connections, which browsers *may* treat differently than strictly
interactive elements.
- **Firefox**: configuration option `network.http.sendRefererHeader`
specifies whether the `Referer` header is sent. It must have a value
of 2 (which is the default) for XHR requests to get the `Referer`
header. Purely interactive Fossil features, in which users directly
activate links or forms, work with a level of 1 or higher.
- **Chrome**: apparently requires an add-on in order to change this
policy, so Chrome without such an add-on will not suppress this
header.
- **Safari**: ???
- **Other browsers**: ???
If `/filepage` shows an error message saying "CSRF violation," the
problem is that the browser is not sending a `Referer` header to XHR
connections. Fossil does not offer a way to disable its CSRF
protections.
[referer]: https://en.wikipedia.org/wiki/HTTP_referer
[csrf]: https://en.wikipedia.org/wiki/Cross-site_request_forgery
[xhr]: https://en.wikipedia.org/wiki/XMLHttpRequest
## `/fileedit` **Works by Creating Commits**
Thus any edits made via that page become a normal part of the
repository's blockchain.
## `/fileedit` is *Intended* for use with Embedded Docs
... and similar text files, and is most certainly
**not intended for editing code**.
Editing files with unusual syntax requirements, e.g. hard tabs in
makefiles, may break them. *You Have Been Warned.*
Similarly, though every effort is made to retain the end-of-line
style used by being-edited files, the round-trip through an HTML
textarea element may change the EOLs. The Commit section of the page
offers three different options for how to treat newlines when saving
changes. **Files with mixed EOL styles** *will be normalized to a single
EOL style* when modified using `/fileedit`. When "inheriting" the EOL
style from a previous version which has mixed styles, the first EOL
style detected in the previous version of the file is used.
## `/fileedit` **is Not a Replacement for a Checkout**
A full-featured checkout allows far more possibilities than this basic
online editor permits, and the feature scope of `/fileedit` is
intentionally kept small, implementing only the bare necessities
needed for performing basic edits online. It *is not, and will never
be, a replacement for a checkout.*
It is to be expected that users will want to do "more" with this
page, and we generally encourage feature requests, but be aware that
certain types of ostensibly sensible feature requests *will be
rejected* for `/fileedit`. These include, but are not limited to:
- Features which are already provided by other pages, e.g.
the ability to create a new named branch or add tags.
- Features which would require re-implementing significant
capabilities provided only within a checkout (e.g. merging files).
- The ability to edit/manipulate files which are in a local
checkout. (If you have a checkout, use your local editor, not
`/fileedit`.)
- Editing of non-text files, e.g. images. Use a checkout and your
preferred graphics editor.
- Support for syncing/pulling/pushing of a repository before and/or
after edits. Those features cannot be *reliably* provided via a web
interface for several reasons.
Similarly, some *potential* features have significant downsides,
abuses, and/or implementation hurdles which make the decision of
whether or not to implement them subject to notable contributor
debate. e.g. the ability to add new files or remove/rename older
files.
## `/fileedit` **Stores Only Limited Local Edits While Working**
When changes are made to a given checkin/file combination,
`/fileedit` will, if possible, store them in [`window.localStorage`
or `window.sessionStorage`][html5storage], if available, but...
- Which storage is used is unspecified and may differ across
environments.
- If neither of those is available, the storage is transient and
will not survive a page reload. In this case, the UI issues a clear
warning in the editor tab.
- It stores only the most recent checkin/file combinations which have
been modified (exactly how many may differ - the number will be
noted somewhere in the UI). Note that changing the "executable bit"
is counted as a modification, but the checkin *comment* is *not*
and is reset after a commit.
- If its internal limit on the number of modified files is exceeded,
it silently discards the oldest edits to keep the list at its limit.
Edits are saved whenever the editor component fires its "change"
event, which essentially means as soon as it loses input focus. Thus
to force the browser to save any pending changes, simply click
somwhere on the page outside of the editor.
Exactly how long `localStorage` will survive, and how much it or
`sessionStorage` can hold, is environment-dependent. `sessionStorage`
will survive until the current browser tab is closed, but it survives
across reloads of the same tab.
If `/filepage` determines that no peristent storage is available a
warning is displayed on the editor page.
[html5storage]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API
## The Power is Yours, but...
> "With great power comes great responsibility."
**Use this feature judiciously, *if at all*.**
Now, with those warnings and caveats out of the way...
-----
# Tips and Tricks
## `fossil` Global-scope JS Object
`/fileedit` is largely implemented in JavaScript, and makes heavy use
of the global-scope `fossil` object, which provides
infrastructure-level features intended for use by Fossil UI pages.
(That said, that infrastructure was introduced with `/fileedit`, and
most pages do not use it.)
The `fossil.page` object represents the UI's current page (on pages
which make use of this API - most do not). That object supports
listening to page-specific events so that JS code installed via
[client-side edits to the site skin's footer](customskin.md) may react
to those changes somehow. The next section describes one such use for
such events...
## Integrating Syntax Highlighting
Assuming a repository has integrated a 3rd-party syntax highlighting
solution, it can probably (depending on its API) be told how to
highlight `/fileedit`'s wiki/markdown-format previews. Here are
instructions for doing so with [highlightjs](https://highlightjs.org/):
At the very bottom of the [site skin's footer](customskin.md), add a
script tag similar to the following:
```javascript
<script nonce="$<nonce>">
if(window.fossil && fossil.page && fossil.page.name==='fileedit'){
fossil.page.addEventListener(
'fileedit-preview-updated',
(ev)=>{
if(ev.detail.previewMode==='wiki'){
ev.detail.element.querySelectorAll(
'code[class^=language-]'
).forEach((e)=>hljs.highlightBlock(e));
}
}
);
}
</script>
```
Note that the `nonce="$<nonce>"` part is intended to be entered
literally as shown above. It will be expanded to contain the current
request's nonce value when the page is rendered.
The first line of the script just ensures that the expected JS-level
infrastructure is loaded. It's only loaded in the `/fileedit` page and
possibly pages added or "upgraded" since `/fileedit`'s introduction.
The part in the `if` block adds an event listener to the `/filepage`
app which gets called when the preview is refreshed. That event
contains 3 properties:
- `previewMode`: a string describing the current preview mode: `wiki`
(which includes Fossil-native wiki and markdown), `text`,
`htmlInline`, `htmlIframe`. We should "probably" only highlight wiki
text, and thus the example above limits its work to that type of
preview. It won't work with `htmlIframe`, as that represents an
iframe element which contains a complete HTML document.
- `element`: the DOM element in which the preview is rendered.
- `mimetype`: the mimetype of the being-previewed content, as determined
by Fossil (by its file extension).
The event listener callback shown above doesn't use the `mimetype`,
but makes used of the other two. It fishes all `code` blocks out of
the preview which explicitly have a CSS class named
`language-`something, and then asks highlightjs to highlight them.
## Integrating a Custom Editor Widget
*Hypothetically*, though this is currently unproven "in the wild," it
is possible to replace `/filepage`'s basic text-editing widget (a
`textarea` element) with a fancy 3rd-party editor widget by following
these instructions...
All JavaScript code which follows is assumed to be in a script tag
similar to the one shown in the previous section:
```javascript
<script nonce="$<nonce>">
if(window.fossil && fossil.page && fossil.page.name==='fileedit'){
// code specific to the fileedit page goes here
}
</script>
```
First, install proxy functions so that `fossil.page.fileContent()`
can get and set your content:
```
fossil.page.setFileContentMethods(
function(){ return text-form content of your widget },
function(content){ set text-form content of your widget }
};
```
Secondly, inject the custom editor widget into the UI, replacing
the default editor widget:
```javascript
fossil.page.replaceEditorWidget(yourNewWidgetElement);
```
That method must be passed a DOM element and may only be called once:
it *removes itself* the first time it is called.
That "should" be all there is to it. When `fossil.page` needs to get
the being-edited content, it will call `fossil.page.fileContent()`
with no arguments, and when it sets the content (immediately after
(re)loading a file), it will pass that content to
`fossil.page.fileContent()`. Those, in turn will trigger the installed
proxies and fire any relevant events.
|
| ︙ | ︙ | |||
341 342 343 344 345 346 347 348 349 350 351 352 353 354 | A wiki artifact defines a single version of a single wiki page. Wiki artifacts accept the following card types: <blockquote> <b>D</b> <i>time-and-date-stamp</i><br /> <b>L</b> <i>wiki-title</i><br /> <b>N</b> <i>mimetype</i><br /> <b>P</b> <i>parent-artifact-id</i>+<br /> <b>U</b> <i>user-name</i><br /> <b>W</b> <i>size</i> <b>\n</b> <i>text</i> <b>\n</b><br /> <b>Z</b> <i>checksum</i> | > | 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 | A wiki artifact defines a single version of a single wiki page. Wiki artifacts accept the following card types: <blockquote> <b>C</b> <i>change-comment</i><br> <b>D</b> <i>time-and-date-stamp</i><br /> <b>L</b> <i>wiki-title</i><br /> <b>N</b> <i>mimetype</i><br /> <b>P</b> <i>parent-artifact-id</i>+<br /> <b>U</b> <i>user-name</i><br /> <b>W</b> <i>size</i> <b>\n</b> <i>text</i> <b>\n</b><br /> <b>Z</b> <i>checksum</i> |
| ︙ | ︙ | |||
364 365 366 367 368 369 370 371 372 373 374 375 376 377 | the usual checksum over the entire artifact and is required. The <b>W</b> card is used to specify the text of the wiki page. The argument to the <b>W</b> 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 <b>W</b> card. The wiki text is always followed by one extra newline. An example wiki artifact can be seen [/artifact?name=7b2f5fd0e0&txt=1 | here]. <a name="tktchng"></a> <h3>2.5 Ticket Changes</h3> | > > > > > > > | 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 | the usual checksum over the entire artifact and is required. The <b>W</b> card is used to specify the text of the wiki page. The argument to the <b>W</b> 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 <b>W</b> card. The wiki text is always followed by one extra newline. The <b>C</b> card on a wiki page is optional. The argument is a comment that explains why the changes was made. The ability to have a <b>C</b> card on a wiki page artifact was added on 2019-12-02 at the suggestion of user George Krivov and is not currently used or generated by the implementation. Older versions of Fossil will reject a wiki-page artifact that includes a <b>C</b> card. An example wiki artifact can be seen [/artifact?name=7b2f5fd0e0&txt=1 | here]. <a name="tktchng"></a> <h3>2.5 Ticket Changes</h3> |
| ︙ | ︙ | |||
651 652 653 654 655 656 657 | <td> </td> </tr> <tr> <td><b>C</b> <i>comment-text</i></td> <td align=center><b>1</b></td> <td> </td> <td> </td> | | | 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 | <td> </td> </tr> <tr> <td><b>C</b> <i>comment-text</i></td> <td align=center><b>1</b></td> <td> </td> <td> </td> <td align=center><b>0-1</b></td> <td> </td> <td align=center><b>0-1</b></td> <td align=center><b>0-1</b></td> <td> </td> </tr> <tr> <td><b>D</b> <i>date-time-stamp</i></td> |
| ︙ | ︙ |
| ︙ | ︙ | |||
56 57 58 59 60 61 62 | otherwise just the listed file(s) will be checked in. <h2>Compare two revisions of a file</h2> <p>If you wish to compare the last revision of a file and its checked out version in your work directory:</p> <p>fossil gdiff myfile.c</p> <p>If you wish to compare two different revisions of a file in the repository:</p> | | | < < < < < < < | 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | otherwise just the listed file(s) will be checked in. <h2>Compare two revisions of a file</h2> <p>If you wish to compare the last revision of a file and its checked out version in your work directory:</p> <p>fossil gdiff myfile.c</p> <p>If you wish to compare two different revisions of a file in the repository:</p> <p>fossil finfo myfile: Note the first hash, which is the hash of the commit when the file was committed</p> <p>fossil gdiff --from HASH#1 --to HASH#2 myfile.c</p> <h2>Cancel changes and go back to previous revision</h2> <p>fossil revert myfile.c</p> <p>Fossil does not prompt when reverting a file. It simply reminds the user about the "undo" command, just in case the revert was a mistake.</p> |
| ︙ | ︙ | |||
57 58 59 60 61 62 63 |
or back to the office before you can search for past posts.
* <b>Contribute Off-Line:</b> Fossil forum posts work like any other
insertion into the repository, so a user can create new threads and
reply to existing ones while off-line, then sync their
contributions to the server they cloned from when back on-line.
Yes, you can post to the forum from inside a tent, miles from the
| | | | | | 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 |
or back to the office before you can search for past posts.
* <b>Contribute Off-Line:</b> Fossil forum posts work like any other
insertion into the repository, so a user can create new threads and
reply to existing ones while off-line, then sync their
contributions to the server they cloned from when back on-line.
Yes, you can post to the forum from inside a tent, miles from the
nearest WiFi router or cellular data tower.
* <b>Interlink with Other Fossil-Managed Artifacts:</b> Because forum
posts are normal Fossil artifacts, you can interlink them with
other Fossil artifacts using short internal links: link to forum
threads from a [./tickets.wiki | ticket], link to a wiki document
from a forum post, etc.
* <b>Durable Links:</b> Once you create a valid internal artifact
link in Fossil, it <em>remains</em> valid, durably. With
third-party forum software and mailing list search engines, your
links are only valid until the third-party component changes its
URL scheme or disappears from the web.
* <b>Role-Based Access Control:</b> The forum uses the same
[./caps/ | capability-based access control
system] that Fossil uses to control all other repository accesses.
The Fossil forum feature simply adds [./caps/ref.html#2 | several new fine-grained
capabilities] to the existing system.
* <b>Enduring, Open File Format:</b> Since Fossil has an
[./fileformat.wiki | open and well-documented file format], your
discussion archives are truly that: <em>archives</em>. You are no
longer dependent on the lifetime and business model of a
third-party piece of software or service. Should you choose to stop
using Fossil, you can easily extract your discussion traffic for
|
| ︙ | ︙ | |||
107 108 109 110 111 112 113 |
digest delivery.
<h2 id="setup">Setting up a Fossil Forum</h2>
<h3 id="caps">Capabilities</h3>
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | > > | | | | | > | | | 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 |
digest delivery.
<h2 id="setup">Setting up a Fossil Forum</h2>
<h3 id="caps">Capabilities</h3>
By default, no Fossil user has permission to use the forums except for
users with Setup and Admin capabilities, which get these as part of the
large package of other capabilities they get.
For public Fossil repositories that wish to accept new users without
involving a human, go into Admin → Access and enable the "Allow
users to register themselves" setting. You may also wish to give users
in [./caps/#ucat | the <tt>anonymous</tt> user category] the
<b>[./caps/ref.html#2 | RdForum]</b> and
<b>[./caps/ref.html#3 | WrForum]</b>
capabilities: this allows people to post without creating an account
simply by solving [./antibot.wiki | a simple CAPTCHA].
For a private repository, you probably won't want to give the
<tt>anonymous</tt> user any forum access, but you may wish to give the
<b>RdForum</b> capability to users in the <tt>reader</tt> category.
For either type of repository, you are likely to want to give at least
the <b>[./caps/ref.html#4 | WrTForum]</b> capability to users in the <tt>developer</tt>
category. If you did not give the <b>RdForum</b> capability to
<tt>anonymous</tt> above, you should give <tt>developer</tt> that
capability here if you choose to give it <b>WrForum</b> or
<b>WrTForum</b> capability.
If you want to use the email alert feature, by default only those
users in the Setup and Admin user categories can make use of it. Grant
the <b>[./caps/ref.html#7 | EmailAlert]</b> capability to give others access to this feature.
Alternately, you can handle alert signups outside of Fossil, with
a Setup or Admin users manually signing users up via Admin →
Notification. You'll want to grant this capability to the
<tt>nobody</tt> user category if you want anyone to sign up without any
restrictions. Give it to <tt>anonymous</tt> instead if you want the
user to solve a simple CAPTCHA before signing up. Or, give it to
<tt>reader</tt> or <tt>developer</tt> if you want only users with Fossil
logins to have this ability. (That's assuming you give one or both of
these capabilities to every user on your Fossil repository.)
By following this advice, you should not need to tediously add
capabilities to individual accounts except in atypical cases, such as
to grant the <b>[./caps/ref.html#5 | ModForum]</b> capability to an uncommonly
highly-trusted user.
<h3 id="skin">Skin Setup</h3>
If you create a new Fossil repository with version 2.7 or newer, its
default skin is already set up correctly for typical forum
|
| ︙ | ︙ | |||
208 209 210 211 212 213 214 |
<verbatim>
if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
menulink /forum Forum
}
</verbatim>
| | | | | 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
<verbatim>
if {[anycap 23456] || [anoncap 2] || [anoncap 3]} {
menulink /forum Forum
}
</verbatim>
These rules say that any logged-in user with any [./caps/ref.html#2 |
forum-related capability] or an anonymous user <b>RdForum</b> or
<b>WrForum</b> capability will see the "Forum" navbar
link, which just takes you to <tt>/forum</tt>.
The exact code you need here varies depending on which skin you're
using. Follow the style you see for the other navbar links.
The new forum feature also brings many new CSS styles to the table. If
you're using the stock skin or something sufficiently close, the changes
|
| ︙ | ︙ | |||
301 302 303 304 305 306 307 | participating in the forum have special capability bits for project assets managed by Fossil, so you wish to segregate the two user sets. Yet, what of the users who will have logins on both repositories? Some users will be trusted with access to the project's main Fossil repository, and these users will probably also participate in the project's Fossil-hosted forum. Fossil has a feature to solve this | | < < < | | | | < < | | | | 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 |
participating in the forum have special capability bits for project
assets managed by Fossil, so you wish to segregate the two user sets.
Yet, what of the users who will have logins on both repositories? Some
users will be trusted with access to the project's main Fossil
repository, and these users will probably also participate in the
project's Fossil-hosted forum. Fossil has a feature to solve this
problem: [./caps/login-groups.md | login groups].
<h3 id="alerts">Email Alerts (a.k.a. Notifications)</h3>
Internet email service has become rather complicated since its initial
simple and insecure implementation decades ago. Fossil's role in all of
this is rather small at the moment, but the details of the integration
are complex enough to justify [./alerts.md | a separate document].
(The other reason that document is separate is that Fossil's email
alerts system also gets used by features of Fossil other than the
forum.)
<h2 id="access">Accessing the Forum</h2>
There are many paths to a repository's Fossil forum:
<ul>
<li>
If you're using the default Fossil skin as shipped with Fossil
2.7+ or one [#skin | updated] to support it, there
is a Forum button in the navbar which appears for users able to
access the forum. With the default skin, that button will only
appear if the user's browser window is at least
1200 pixels wide. The
Fossil admin can adjust this limit in the skin's CSS section, down
near the bottom in the definition of the `wideonly` style.
</li>
<li>The other stock skins have this button in them as of 2.7 as well,
without the screen width restriction, since the navbar in those skins
wraps on narrow screens more gracefully than the default skin
does.</li>
|
| ︙ | ︙ | |||
366 367 368 369 370 371 372 | In this section, we're going to call all of the following a "forum update:" * create a new post * reply to an existing post * edit a post or reply | | | | < | | | | | | | 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 |
In this section, we're going to call all of the following a "forum
update:"
* create a new post
* reply to an existing post
* edit a post or reply
When a person with the normal <b>WrForum</b> capability
updates the forum, Fossil saves the update in its block chain, but this
update is impermanent because of two other table updates made at the
same time:
<ol>
<li>Fossil saves the update artifact's ID in its <tt>private</tt>
table, preventing Fossil from sending such artifacts to any of the
repository's clones. (This is the same mechanism behind
[./private.wiki | private branches].)</li>
<li>Fossil also adds a reference to that artifact in the
<tt>modreq</tt> table, which backs the moderation feature. This is
what causes Fossil to leave out the Reply button when rendering that
post's HTML in the forum's web interface.</li>
</ol>
When a moderator approves an update, Fossil deletes these table entries,
making the update [./shunning.wiki | semi-permanent]. This changes how Fossil renders the
HTML for that update. It also means the artifact will now sync to
users with <b>[./caps/ref.html#g | Clone]</b> capability.
When a forum user edits a moderator-approved artifact, what actually
happens under the hood is that Fossil writes another artifact to the
repository which refers to the original version as its parent, causing
Fossil UI to present the new version instead of the original. The
original version remains in the repository, just as with historical
checkins. The parent must remain in the repository for referential
integrity purposes.
When you "Delete" a moderator-approved post or reply through Fossil UI,
it's actually an edit with blank replacement content. The only way to
truly delete such artifacts is through [./shunning.wiki | shunning].
When a user with <b>WrTForum</b> capability
updates the forum, it happens in the same way except that Fossil skips
the <tt>private</tt> and <tt>modreq</tt> table insertions.
When a moderator rejects an update, that artifact is unceremoniously
removed from the tip of the block chain. This is safe because Fossil
prevents replies to a reply or post awaiting moderator approval, so
referential integrity cannot be harmed. Rejecting an edit is even
safer, since the original post remains behind, so that replies continue
to refer to that original post.
<h2 id="mod-user">Using the Moderation Feature</h2>
Having described all of the work that Fossil performs under the hood on
behalf of its users, we can now give the short list of work left for the
repository's administrators and moderators:
<ol>
<li>Add the <b>[./caps/ref.html#5 | ModForum]</b> capability to any of
your users who should have this ability. You don't need to do this
for any user with <b>[./caps/ref.html#s | Setup]</b> or
<b>[./caps/ref.html#a | Admin]</b> capability; it's
[http://fossil-scm.org/index.html/artifact/b16221ffb736caa2?ln=1246-1257
| already included].</li>
<li>When someone updates the forum, an entry will appear in the
timeline if the type filter is set to "Forum" or "Any Type". If that
user has only the <b>WrForum</b> capability, any
other user with the <b>ModForum</b> capability
will see a conditional link appear at the top of the main forum
page: "Moderation Requests". Clicking this takes the moderator to
the <tt>/modreq</tt> page. A moderator may wish to keep the main
forum page open in a browser tab, reloading it occasionally to see
when the "Moderation Requests" link reappears.</li>
<li>A moderator viewing an update pending moderation sees two
buttons at the bottom, "Approve" and "Reject" in place of the
"Delete" button that the post's creator sees. Beware that both
actions are durable and have no undo. Be careful!</li>
</ol>
|
1 2 3 4 | <title>Fossil Versus Git</title> <h2>1.0 Don't Stress!</h2> | > > > > > > > > > > > > | | < > | | < > | | > | > > > | > > > > > > | > > | > > > > > > > | > > > | | > > > | | > > > | | > | > | | > > > | | > > > > > > > > > > > > | | | > > > > > > > | > > > | > | | < < > > > > > > > > > | > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > | | > | | > | | | | > > | | | > > | | | | | | > > > > | > | > > > > > | > > > > > > > > > > > | > > | > | > > > > > > > > > > > > > > > | > > > > > > | > > > > > > > > | > > > > > > > > | > > > | > > > > > | | > | | | | | | 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 |
<title>Fossil Versus Git</title>
<h2>1.0 Don't Stress!</h2>
The feature sets of Fossil and [http://git-scm.com | Git] overlap in
many ways. Both are
[https://en.wikipedia.org/wiki/Distributed_version_control | distributed
version control systems] which store a tree of check-in objects to a
local repository clone. In both systems, the local clone starts out as a
full copy of the remote parent. New content gets added to the local
clone and then later optionally pushed up to the remote, and changes to
the remote can be pulled down to the local clone at will. Both systems
offer diffing, patching, branching, merging, cherry-picking, bisecting,
private branches, a stash, etc.
Fossil has inbound and outbound Git conversion features, so if you start
out using one DVCS and later decide you like the other better, you can
easily [./inout.wiki | move your version-controlled file content].¹
In this document, we set all of that similarity and interoperability
aside and focus on the important differences between the two, especially
those that impact the user experience.
Keep in mind that you are reading this on a Fossil website, and though
we try to be fair, the information here
might be biased in favor of Fossil, if only because we spend most of our
time using Fossil, not Git. Ask around for second opinions from
people who have used <em>both</em> Fossil and Git.
<h2>2.0 Differences Between Fossil And Git</h2>
Differences between Fossil and Git are summarized by the following table,
with further description in the text that follows.
<blockquote><table border=1 cellpadding=5 align=center>
<tr><th width="49%">GIT</th><th width="49%">FOSSIL</th><th width="2%">more</th></tr>
<tr>
<td>File versioning only</td>
<td>VCS, tickets, wiki, docs, notes, forum, UI,
[https://en.wikipedia.org/wiki/Role-based_access_control|RBAC]</td>
<td><a href="#features">2.1 ↓</a></td>
</tr>
<tr>
<td>Sprawling and inefficient</td>
<td>Self-contained and efficient</td>
<td><a href="#efficient">2.2 ↓</a></td>
</tr>
<tr>
<td>One-off custom pile-of-files data store</td>
<td>[https://sqlite.org/famous.html|The most popular database in the world]</td>
<td><a href="#durable">2.3 ↓</a></td>
</tr>
<tr>
<td>Runs natively on POSIX systems only</td>
<td>Native on common desktop & server platforms</td>
<td><a href="#portable">2.4 ↓</a></td>
</tr>
<tr>
<td>Bazaar-style development</td>
<td>Cathedral-style development</td>
<td><a href="#devorg">2.5.1 ↓</a></td>
</tr>
<tr>
<td>Designed for Linux kernel development</td>
<td>Designed for SQLite development</td>
<td><a href="#scale">2.5.2 ↓</a></td>
</tr>
<tr>
<td>Many contributors</td>
<td>Select contributors</td>
<td><a href="#contrib">2.5.3 ↓</a></td>
</tr>
<tr>
<td>Focus on individual branches</td>
<td>Focus on the entire tree of changes</td>
<td><a href="#branches">2.5.4 ↓</a></td>
</tr>
<tr>
<td>One check-out per repository</td>
<td>Many check-outs per repository</td>
<td><a href="#checkouts">2.6 ↓</a></td>
</tr>
<tr>
<td>Remembers what you should have done</td>
<td>Remembers what you actually did</td>
<td><a href="#history">2.7 ↓</a></td>
</tr>
<tr>
<td>Commit first</td>
<td>Test first</td>
<td><a href="#testing">2.8 ↓</a></td>
</tr>
<tr>
<td>SHA-2</td>
<td>SHA-3</td>
<td><a href="#hash">2.9 ↓</a></td>
</tr>
</table></blockquote>
<h3 id="features">2.1 Featureful</h3>
Git provides file versioning services only, whereas Fossil adds
an integrated [./wikitheory.wiki | wiki],
[./bugtheory.wiki | ticketing & bug tracking],
[./embeddeddoc.wiki | embedded documentation],
[./event.wiki | technical notes], and a [./forum.wiki | web forum],
all within a single nicely-designed [./customskin.md|skinnable] web
[/help?cmd=ui|UI],
protected by [./caps/ | a fine-grained role-based
access control system].
These additional capabilities are available for Git as 3rd-party
add-ons, but with Fossil they are integrated into
the design. One way to describe Fossil is that it is
"[https://github.com/ | GitHub]-in-a-box."
Fossil can do operations over all local repo clones and check-out
directories with a single command. For example, Fossil lets you say
<tt>fossil all sync</tt> on a laptop prior to taking it off the network
hosting those repos. You can sync up to all of the private repos on your
company network plus those public Internet-hosted repos you use. Whether
going out for a working lunch or on a transoceanic airplane trip, one
command gets you in sync. This works with several other Fossil
sub-commands, such as <tt>fossil all changes</tt> to get a list of files
that you forgot to commit prior to the end of your working day, across
all repos.
Whenever Fossil is told to modify the local checkout in some destructive
way ([/help?cmd=rm|fossil rm], [/help?cmd=update|fossil update],
[/help?cmd=revert|fossil revert], etc.) Fossil remembers the prior state
and is able to return the check-out directory to that state with a
<tt>fossil undo</tt> command. You cannot undo a commit in Fossil
([#history | on purpose!]) but as long as the change remains confined to
the local check-out directory only, Fossil makes undo
[https://git-scm.com/book/en/v2/Git-Basics-Undoing-Things|easier than in
Git].
For developers who choose to self-host projects (rather than using a
3rd-party service such as GitHub) Fossil is much easier to set up, since
the stand-alone Fossil executable together with a [./server/any/cgi.md|2-line CGI script]
suffice to instantiate a full-featured developer website. To accomplish
the same using Git requires locating, installing, configuring, integrating,
and managing a wide assortment of separate tools. Standing up a developer
website using Fossil can be done in minutes, whereas doing the same using
Git requires hours or days.
Fossil is small, complete, and self-contained. If you clone
[https://github.com/git/git|Git's self-hosting repository], you get just
Git's source code. If you clone Fossil's self-hosting repository, you
get the entire Fossil website — source code, documentation, ticket
history, and so forth.² That means you get a copy of this very article
and all of its historical versions, plus the same for all of the other
public content on this site.
<h3 id="efficient" name="effective">2.2 Efficient</h3>
Git is actually a collection of many small tools, each doing one small
part of the job, which can be recombined (by experts) to perform
powerful operations. Git has a lot of complexity and many dependencies,
so that most people end up installing it via some kind of package
manager, simply because the creation of complicated binary packages is
best delegated to people skilled in their creation. Normal Git users are
not expected to build Git from source and install it themselves.
Fossil is a single self-contained stand-alone executable which by default
depends only on common platform libraries. If your platform allows static
linking — not all do these days! — you can even get it down to
a single executable with no external dependencies at all. Most notably,
we deliver the official Windows builds of Fossil this way: the Zip file
contains only <tt>fossil.exe</tt>, a self-contained Fossil executable;
it is not a <tt>setup.exe</tt> style installer, it is the whole enchilada.
A typical Fossil executable is about 5 MiB, not counting system
libraries it shares in common with Git such as OpenSSL and zlib, which
we can factor out of the discussion.
These properties allow Fossil to easily run inside a minimally configured
[https://en.wikipedia.org/wiki/Chroot|chroot jail], from a Windows memory
stick, off a Raspberry Pi with a tiny SD card, etc. To install Fossil,
one merely puts the executable somewhere in the <tt>$PATH</tt>. Fossil is
[https://fossil-scm.org/fossil/doc/trunk/www/build.wiki|straightforward
to build and install], so that many Fossil users do in fact build and
install "trunk" versions to get new features between formal releases.
Contrast a basic installation of Git, which takes up about
15 MiB on Debian 10 across 230 files, not counting the contents of
<tt>/usr/share/doc</tt> or <tt>/usr/share/locale</tt>. If you need to
deploy to any platform where you cannot count facilities like the POSIX
shell, Perl interpreter, and Tcl/Tk platform needed to fully use Git
as part of the base platform, the full footprint of a Git installation
extends to more like 45 MiB and thousands of files. This complicates
several common scenarios: Git for Windows, chrooted Git servers,
Docker images...
Some say that Git more closely adheres to the Unix philosophy,
summarized as "many small tools, loosely joined," but we have many
examples of other successful Unix software that violates that principle
to good effect, from Apache to Python to ZFS. We can infer from that
that this is not an absolute principle of good software design.
Sometimes "many features, tightly-coupled" works better. What actually
matters is effectiveness and efficiency. We believe Fossil achieves
this.
The above size comparisons aren't apples-to-apples anyway. We've
compared the size of Fossil with all of its [#features | many built-in
features] to a fairly minimal Git installation. You must add a lot
of third-party
software to Git to give it a Fossil-equivalent feature set. Consider
[https://about.gitlab.com/|GitLab], a third-party extension to Git
wrapping it in many features, making it roughly Fossil-equivalent,
though [https://docs.gitlab.com/ee/install/requirements.html|much more
resource hungry] and hence more costly to run than the equivalent
Fossil setup. GitLab's basic requirements are easy to accept when you're dedicating
a local rack server or blade to it, since its minimum requirements are
more or less a description of the smallest
thing you could call a "server" these days, but when you go to host that
in the cloud, you can expect to pay about 8× as much to comfortably host
GitLab as for Fossil.³ This difference is largely due to basic
technology choices: Ruby and PostgreSQL vs C and SQLite.
The Fossil project itself is [./selfhost.wiki|hosted on a very small
VPS], and we've received many reports on the Fossil forum about people
successfully hosting Fossil service on bare-bones $5/month VPS hosts,
spare Raspberry Pi boards, and other small hosts.
<h3 id="durable" name="database">2.3 Durable</h3>
The baseline data structures for Fossil and Git are the same, modulo
formatting details. Both systems manage a
[https://en.wikipedia.org/wiki/Directed_acyclic_graph | directed acyclic
graph] (DAG) of [https://en.wikipedia.org/wiki/Merkle_tree | Merkle
tree] / [./blockchain.md | block chain] structured check-in objects.
Check-ins are identified by a cryptographic hash of the check-in
comment, and each check-in refers to its parent via <i>its</i> hash.
The difference is that Git stores its objects as individual files in the
<tt>.git</tt> folder or compressed into bespoke
[https://git-scm.com/book/en/v2/Git-Internals-Packfiles|pack-files],
whereas Fossil stores its objects in a [https://www.sqlite.org/|SQLite]
database file using a hybrid NoSQL/relational data model of the check-in
history. Git's data storage system is an ad-hoc pile-of-files key/value
database, whereas Fossil uses a proven,
[https://sqlite.org/testing.html|heavily-tested], general-purpose,
[https://sqlite.org/transactional.html|durable] SQL database. This
difference is more than an implementation detail. It has important
practical consequences.
With Git, one can easily locate the ancestors of a particular check-in
by following the pointers embedded in the check-in object, but it is
difficult to go the other direction and locate the descendants of a
check-in. It is so difficult, in fact, that neither native Git nor
GitHub provide this capability short of
[http://catb.org/jargon/html/G/grovel.html|groveling] the
[https://www.git-scm.com/docs/git-log|commit log]. With Git, if you
are looking at some historical check-in then you cannot ask "What came
next?" or "What are the children of this check-in?"
Fossil, on the other hand, parses essential information about check-ins
(parents, children, committers, comments, files changed, etc.) into a
relational database that can easily be queried using concise SQL
statements to find both ancestors and descendants of a check-in. This is
the hybrid data model mentioned above: Fossil manages your check-in and
other data in a NoSQL block chain structured data store, but that's backed
by a set of relational lookup tables for quick indexing into that
artifact store. (See "[./theory1.wiki|Thoughts On The Design Of The
Fossil DVCS]" for more details.)
Leaf check-ins in Git that lack a "ref" become "detached," making them
difficult to locate and subject to garbage collection. This
[http://gitfaq.org/articles/what-is-a-detached-head.html|detached head
state] problem has caused untold grief for
[https://www.google.com/search?q=git+detached+head+state | a huge number
of Git users]. With
Fossil, detached heads are simply impossible because we can always find
our way back into the block chain using one or more of the relational
indices it automatically manages for you.
This design difference shows up in several other places within each
tool. It is why Fossil's [/help?cmd=timeline|timeline] is generally more
detailed yet more clear than those available in Git front-ends.
(Contrast [/timeline?c=6df7a853ec16865b|this Fossil timeline] with
[https://github.com/drhsqlite/fossil-mirror/commits/master?after=f720c106d297ca1f61bccb30c5c191b88a626d01+34|its
closest equivalent in GitHub].) It's why there is no inverse of the
cryptic <tt>@~</tt> notation in Git, meaning "the parent of HEAD," which
Fossil simply calls "prev", but there <i>is</i> a "next"
[./checkin_names.wiki|special check-in name] in Fossil. It is why Fossil
has so many [./webpage-ex.md|built-in status reports] to help maintain
situational awareness, aid comprehension, and avoid errors.
These differences are due, in part, to Fossil's start a year later than
Git: we were able to learn from its key design mistakes.
<h3 id="portable">2.4 Portable</h3>
Fossil is largely written in ISO C, almost purely conforming to the
original 1989 standard. We make very little use of
[https://en.wikipedia.org/wiki/C99|C99], and we do not knowingly make
any use of
[https://en.wikipedia.org/wiki/C11_(C_standard_revision)|C11]. Fossil
does call POSIX and Windows APIs where necessary, but it's about
as portable as you can ask given that ISO C doesn't define all of the
facilities Fossil needs to do its thing. (Network sockets, file locking,
etc.) There are certainly well-known platforms Fossil hasn't been ported
to yet, but that's most likely due to lack of interest rather than
inherent difficulties in doing the port. We believe the most stringent
limit on its portability is that it assumes at least a 32-bit CPU and
several megs of flat-addressed memory.⁴ Fossil isn't quite as
[https://www.sqlite.org/custombuild.html|portable as SQLite], but it's
close.
Over half of the C code in Fossil is actually an embedded copy of the
current version of SQLite. Much of what is Fossil-specific after you set
SQLite itself aside is SQL code calling into SQLite. The number of lines
of SQL code in Fossil isn't large by percentage, but since SQL is such
an expressive, declarative language, it has an outsized contribution to
Fossil's user-visible functionality.
Fossil isn't entirely C and SQL code. Its web UI [./javascript.md |
uses JavaScript where
necessary]. The server-side
UI scripting uses a custom minimal
[https://en.wikipedia.org/wiki/Tcl|Tcl] dialect called
[https://www.fossil-scm.org/xfer/doc/trunk/www/th1.md|TH1], which is
embedded into Fossil itself. Fossil's build system and test suite are
largely based on Tcl.⁵ All of this is quite portable.
About half of Git's code is POSIX C, and about a third is POSIX shell
code. This is largely why the so-called "Git for Windows" distributions
(both [https://git-scm.com/download/win|first-party] and
[https://gitforwindows.org/|third-party]) are actually an
[http://mingw.org/wiki/msys|MSYS POSIX portability environment] bundled
with all of the Git stuff, because it would be too painful to port Git
natively to Windows. Git is a foreign citizen on Windows, speaking to it
only through a translator.⁶
While Fossil does lean toward POSIX norms when given a choice — LF-only
line endings are treated as first-class citizens over CR+LF, for example
— the Windows build of Fossil is truly native.
The third-party extensions to Git tend to follow this same pattern.
[http://mingw.org/wiki/msys|GitLab isn't portable to Windows at all],
for example. For that matter, GitLab isn't even officially supported on
macOS, the BSDs, or uncommon Linuxes! We have many users who regularly
build and run Fossil on all of these systems.
<h3 id="vs-linux">2.5 Linux vs. SQLite</h3>
Fossil and Git promote different development styles because each one was
specifically designed to support the creator's main software
development project: [https://en.wikipedia.org/wiki/Linus_Torvalds|Linus
Torvalds] designed Git to support development of
[https://www.kernel.org/|the Linux kernel], and
[https://en.wikipedia.org/wiki/D._Richard_Hipp|D. Richard Hipp] designed
Fossil to support the development of [https://sqlite.org/|SQLite].
Both projects must rank high on any objective list of "most
important FOSS projects," yet these two projects are almost entirely unlike
one another, so it is natural that the DVCSes created to support these
projects also differ in many ways.
In the following sections, we will explain how four key differences
between the Linux and SQLite software development projects dictated the
design of each DVCS's low-friction usage path.
When deciding between these two DVCSes, you should ask yourself, "Is my
project more like Linux or more like SQLite?"
<h4 id="devorg">2.5.1 Development Organization</h4>
Eric S. Raymond's seminal essay-turned-book
"[https://en.wikipedia.org/wiki/The_Cathedral_and_the_Bazaar|The
Cathedral and the Bazaar]" details the two major development
organization styles found in
[https://en.wikipedia.org/wiki/Free_and_open-source_software|FOSS]
projects. As it happens, Linux and SQLite fall on opposite sides of this
dichotomy. Differing development organization styles dictate a different
design and low-friction usage path in the tools created to support each
project.
Git promotes the Linux kernel's bazaar development style, in which a
loosely-associated mass of developers contribute their work through
[https://git-scm.com/book/en/v2/Distributed-Git-Distributed-Workflows#_dictator_and_lieutenants_workflow|a
hierarchy of lieutenants] who manage and clean up these contributions
for consideration by Linus Torvalds, who has the power to cherry-pick
individual contributions into his version of the Linux kernel. Git
allows an anonymous developer to rebase and push specific locally-named
private branches, so that a Git repo clone often isn't really a clone at
all: it may have an arbitrary number of differences relative to the
repository it originally cloned from. Git encourages siloed development.
Select work in a developer's local repository may remain private
indefinitely.
|
| ︙ | ︙ | |||
169 170 171 172 173 174 175 |
<ul>
<li><p><b>Personal engagement:</b> SQLite's developers know each
other by name and work together daily on the project.</p></li>
<li><p><b>Trust over hierarchy:</b> SQLite's developers check
changes into their local repository, and these are immediately and
| | | | | | | 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 |
<ul>
<li><p><b>Personal engagement:</b> SQLite's developers know each
other by name and work together daily on the project.</p></li>
<li><p><b>Trust over hierarchy:</b> SQLite's developers check
changes into their local repository, and these are immediately and
automatically synchronized up to the central repository; there is no
"[https://git-scm.com/book/en/v2/Distributed-Git-Distributed-Workflows#_dictator_and_lieutenants_workflow|dictator
and lieutenants]" hierarchy as with Linux kernel contributions. D.
Richard Hipp rarely overrides decisions made by those he has trusted
with commit access on his repositories. Fossil allows you to give
[./caps/admin-v-setup.md|some users] more power over what
they can do with the repository, but Fossil [./caps/index.md#ucap |
only loosely supports] the enforcement of a development organization's
social and power hierarchies. Fossil is a great fit for
[https://en.wikipedia.org/wiki/Flat_organization|flat
organizations].</p></li>
<li><p><b>No easy drive-by contributions:</b> Git
[https://www.git-scm.com/docs/git-request-pull|pull requests] offer
a low-friction path to accepting
[https://www.jonobacon.com/2012/07/25/building-strong-community-structural-integrity/|drive-by
contributions]. Fossil's closest equivalent is its unique
[/help?cmd=bundle|bundle] feature, which requires higher engagement
than firing off a PR.⁷ This difference comes directly from the
initial designed purpose for each tool: the SQLite project doesn't
accept outside contributions from previously-unknown developers, but
the Linux kernel does.</p></li>
<li><p><b>No rebasing:</b> When your local repo clone syncs changes
up to its parent, those changes are sent exactly as they were
committed locally. [#history|There is no rebasing mechanism in
|
| ︙ | ︙ | |||
217 218 219 220 221 222 223 |
for us to integrate all at once.
[https://en.wikipedia.org/wiki/Jim_McCarthy_(author)|Jim McCarthy]
put it well in his book on software project management,
<i>[https://www.amazon.com/dp/0735623198/|Dynamics of Software
Development]</i>: "[https://www.youtube.com/watch?v=oY6BCHqEbyc|Beware
of a guy in a room]."</p></li>
| | | | > > > > | > > > > > > > > > > > > > > > > | | < < | | | > > > > > > > | > > > > > > > > > > > > | > > > | | | | | | | > | | | | | | < < < < < < < < < < < < < < < < < < < | | | | | | > > > > | > > > > > > > | > > > > > > > > > > > | > > < > | > > > > > > > > > | > > | > > | | > > > > | < < < < | < < > > | < | > > < < | < < < > | > > | | > < > > > > > > > > > > > > > > > > | > < < < < > > > > > > > < > > > > | < > > > > > | < < < < < > | > > > | > | < | < | < > > > > > | > > > > > > > > > | > > | < < > | < > > > | < < < > > | | < < < > | | | > > > > > > > > > > > > > > > | < < < < > > | < > > > < < > | < < > > > | | | > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | 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 |
for us to integrate all at once.
[https://en.wikipedia.org/wiki/Jim_McCarthy_(author)|Jim McCarthy]
put it well in his book on software project management,
<i>[https://www.amazon.com/dp/0735623198/|Dynamics of Software
Development]</i>: "[https://www.youtube.com/watch?v=oY6BCHqEbyc|Beware
of a guy in a room]."</p></li>
<li><p><b>Branch names sync:</b> Unlike in Git, branch names in
Fossil are not purely local labels. They sync along with everything
else, so everyone sees the same set of branch names. Fossil's design
choice here is a direct reflection of the Linux vs. SQLite project
outlook: SQLite's developers collaborate closely on a single
coherent project, whereas Linux's developers go off on tangents and
occasionally sync changes up with each other.</p></li>
<li><p><b>Private branches are rare:</b>
[/doc/trunk/www/private.wiki|Private branches exist in Fossil], but
they're normally used to handle rare exception cases, whereas in
many Git projects, they're part of the straight-line development
process.</p></li>
<li><p><b>Identical clones:</b> Fossil's autosync system tries to
keep each local clone identical to the repository it cloned
from.</p></li>
</ul>
Where Git encourages siloed development, Fossil fights against it.
Fossil places a lot of emphasis on synchronizing everyone's work and on
reporting on the state of the project and the work of its developers, so
that everyone — especially the project leader — can maintain a better
mental picture of what is happening, leading to better situational
awareness.
You can think about this difference in terms of
[https://en.wikipedia.org/wiki/Feedback | feedback loop size], which we
know from the mathematics of
[https://en.wikipedia.org/wiki/Control_theory | control theory] to
directly affect the speed at which any system can safely make changes.
The larger the feedback loop, the slower the whole system must run in
order to avoid loss of control. The same concept shows up in other
contexts, such as in the [https://en.wikipedia.org/wiki/OODA_loop | OODA
loop] concept originally developed to explain the success of the US F-86
Sabre fighter aircraft over the on-paper superior MiG-15, then later
applied in other contexts, such as business process management.
Committing your changes to private branches in order to delay a public
push to the parent repo increases the size of your collaborators'
control loops, either causing them to slow their work in order to safely
react to your work, or to overcorrect in response to each change.
Each DVCS can be used in the opposite style, but doing so works against
their low-friction paths.
<h4 id="scale">2.5.2 Scale</h4>
The Linux kernel has a far bigger developer community than that of
SQLite: there are thousands and thousands of contributors to Linux, most
of whom do not know each others names. These thousands are responsible
for producing roughly 89⨉ more code than is in SQLite. (10.7
[https://en.wikipedia.org/wiki/Source_lines_of_code|MLOC] vs. 0.12 MLOC
according to [https://dwheeler.com/sloccount/|SLOCCount].) The Linux
kernel and its development process were already uncommonly large back in
2005 when Git was designed, specifically to support the consequences of
having such a large set of developers working on such a large code base.
95% of the code in SQLite comes from just four programmers, and 64% of
it is from the lead developer alone. The SQLite developers know each
other well and interact daily. Fossil was designed for this development
model.
We think you should ask yourself whether you have Linus Torvalds scale
software configuration management problems or D. Richard Hipp scale
problems when choosing your DVCS. An
[https://en.wikipedia.org/wiki/Impact_wrench|automotive air impact
wrench] running at 8000 RPM driving an M8 socket-cap bolt at 16 cm/s is
not the best way to hang a picture on the living room wall.
<h4 id="contrib">2.5.3 Accepting Contributions</h4>
As of this writing, Git has received about 4.5⨉ as many commits as
Fossil resulting in about 2.5⨉ as many lines of source code. The line
count excludes tests and in-tree third-party dependencies. It does not
exclude the default GUI for each, since it's integral for Fossil, so we
count the size of <tt>gitk</tt> in this.
It is obvious that Git is bigger in part because of its first-mover
advantage, which resulted in a larger user community, which results in
more contributions. But is that the <i>only</i> reason? We believe there
are other relevant differences that also play into this which fall out
of the "Linux vs. SQLite" framing: licensing, community structure, and
how we react to
[https://www.jonobacon.com/2012/07/25/building-strong-community-structural-integrity/|drive-by
contributions]. In brief, it's harder to get a new feature into Fossil
than into Git.
A larger feature set is not necessarily a good thing. Git's command line
interface is famously arcane. Masters of the arcane are able to do
wizardly things, but only by studying their art deeply for years. This
strikes us as a good thing only in cases where use of the tool itself is
the primary point of that user's work.
Almost no one uses a DVCS for its own sake; very few people get paid
specifically in order to drive a DVCS. We use DVCSes as a tool to
support some other effort, so we do not necessarily want the DVCS with
the most features. We want a DVCS with easily internalized behavior so
we can thoroughly master it despite spending only a small fraction of
our working time thinking about the DVCS. We want to pick the tool up,
use it quickly, and then set it aside in order to get back to our actual
job as quickly as possible.
Professional software developers in particular are prone to focusing on
feature set sizes when choosing tools because this is sometimes a highly
important consideration. They spend all day, every day, in their
favorite text editors, and time they spend learning all of the arcana of
their favorite programming languages is well-spent. Skills with these
tools are direct productivity drivers, which in turn directly drives how
much money a developer can make. (Or how much idle time they can afford
to take, which amounts to the same thing.) But if you are a professional
software developer, we want you to ask yourself a question: "How do I
get paid more by mastering arcane features of my DVCS?" Unless you have
a good answer to that, you probably do not want to be choosing a DVCS
based on how many arcane features it has.
The argument is similar for other types of users: if you are a hobbyist,
how much time do you want to spend mastering your DVCS instead of on
the hobby supported by use of that DVCS?
There is some minimal set of features required to achieve the purposes
that drive our selection of a DVCS, but there is a level beyond which
more features only slow us down while we're learning the tool, since we
must plow through documentation on features we're not likely to ever
use. When the number of features grows to the point where people of
normal motivation cannot spend the time to master them all, the tool
becomes <i>less</i> productive to use.
The core developers of the Fossil project achieve a balance between feature
set size and ease of use by
carefully choosing which users to give commit bits to, then in being
choosy about which of the contributed feature branches to merge down to
trunk. We say "no" to a lot of feature proposals.
The end result is that Fossil more closely adheres to
[https://en.wikipedia.org/wiki/Principle_of_least_astonishment|the
principle of least astonishment] than Git does.
<h4 id="branches">2.5.4 Individual Branches vs. The Entire Change History</h4>
Both Fossil and Git store history as a directed acyclic graph (DAG)
of changes, but Git tends to focus more on individual branches of
the DAG, whereas Fossil puts more emphasis on the entire DAG.
For example, the default behavior in Git is to only synchronize
a single branch, whereas with Fossil the only sync option is to
sync the entire DAG. Git commands,
GitHub, and GitLab tend to show only a single branch at
a time, whereas Fossil usually shows all parallel branches at
once. Git has commands like "rebase" that help keep all relevant
changes on a single branch, whereas Fossil encourages a style of
many concurrent branches constantly springing into existence,
undergoing active development in parallel for a few days or weeks, then
merging back into the main line and disappearing.
This difference in emphasis arises from the different purposes of
the two systems. Git focuses on individual branches, because that
is exactly what you want for a highly-distributed bazaar-style project
such as Linux. Linus Torvalds does not want to see every check-in
by every contributor to Linux, as such extreme visibility does not scale
well. But Fossil was written for the cathedral-style SQLite project
with just a handful of active committers. Seeing all
changes on all branches all at once helps keep the whole team
up-to-date with what everybody else is doing, resulting in a more
tightly focused and cohesive implementation.
<h3 id="checkouts">2.6 One vs. Many Check-outs per Repository</h3>
Because Git commingles the repository data with the initial checkout of
that repository, the default mode of operation in Git is to stick to that
single work/repo tree, even when that's a shortsighted way of working.
Fossil doesn't work that way. A Fossil repository is a SQLite database
file which is normally stored outside the working checkout directory. You can
[/help?cmd=open | open] a Fossil repository any number of times into
any number of working directories. A common usage pattern is to have one
working directory per active working branch, so that switching branches
is done with a <tt>cd</tt> command rather than by checking out the
branches successively in a single working directory.
Fossil does allow you to switch branches within a working checkout
directory, and this is also often done. It is simply that there is no
inherent penalty to either choice in Fossil as there is in Git. The
standard advice is to use a switch-in-place workflow in Fossil when
the disturbance from switching branches is small, and to use multiple
checkouts when you have long-lived working branches that are different
enough that switching in place is disruptive.
You can use Git in the Fossil style, either by manually symlinking the
<tt>.git</tt> directory from one working directory to another or by use
of the <tt>[https://git-scm.com/docs/git-worktree|git-worktree]</tt>
feature. Nevertheless, Git's default tie between working directory and
repository means the standard method for working with a Git repo is to
have one working directory only. Most Git tutorials teach this style, so
it is how most people learn to use Git. Because relatively few people
use Git with multiple working directories per repository, there are
[https://duckduckgo.com/?q=git+worktree+problem | several known
problems] with that way of working, problems which don't happen in Fossil because of
the clear separation between repository and working directory.
This distinction matters because switching branches inside a single working directory loses local context
on each switch.
For instance, in any software project where the runnable program must be
built from source files, you invalidate build objects on each switch,
artificially increasing the time required to switch versions. Most obviously, this
affects software written in statically-compiled programming languages
such as C, Java, and Haskell, but it can even affect programs written in
dynamic languages like JavaScript. A typical
[https://en.wikipedia.org/wiki/Single-page_application | SPA] build
process involves several passes: [http://browserify.org/ | Browserify] to convert
[https://nodejs.org/ | Node] packages so they'll run in a web browser,
[https://sass-lang.com | SASS] to CSS translation,
transpilation of [https://www.typescriptlang.org | Typescript] to JavaScript,
[https://github.com/mishoo/UglifyJS | uglification], etc.
Once all that processing work is done for a given input
file in a given working directory, why re-do that work just to switch
versions? If most of the files that differ between versions don't change
very often, you can save substantial time by switching branches with
<tt>cd</tt> rather than swapping versions in-place within a working
checkout directory.
For another example, you might have an active long-running test grinding
away in a working directory, then get a call from a customer requiring
that you switch to a stable branch to answer questions in terms of the
version that customer is running. You don't want to stop the test in
order to switch your lone working directory to the stable branch.
Disk space is cheap. Having several working directories, each with its
own local state, makes switching versions cheap and fast. Plus,
<tt>cd</tt> is faster to type than <tt>git checkout</tt> or <tt>fossil
update</tt>.
<h3 id="history">2.7 What you should have done vs. What you actually did</h3>
Git puts a lot of emphasis on maintaining
a "clean" check-in history. Extraneous and experimental branches by
individual developers often never make it into the main repository. And
branches are often rebased before being pushed, to make
it appear as if development had been linear. Git strives to record what
the development of a project should have looked like had there been no
mistakes.
Fossil, in contrast, puts more emphasis on recording exactly what happened,
including all of the messy errors, dead-ends, experimental branches, and
so forth. One might argue that this
makes the history of a Fossil project "messy," but another point of view
is that this makes the history "accurate." In actual practice, the
superior reporting tools available in Fossil mean that the added "mess"
is not a factor.
Like Git, Fossil has an [/help?cmd=amend|amend command] for modifying
prior commits, but unlike in Git, this works not by replacing data in
the repository, but by adding a correction record to the repository that
affects how later Fossil operations present the corrected data. The old
information is still there in the repository, it is just overridden from
the amendment point forward. For extreme situations, Fossil adds the
[/doc/trunk/www/shunning.wiki|shunning mechanism], but it has strict
limitations that prevent global history rewrites.
One commentator characterized Git as recording history according to
the victors, whereas Fossil records history as it actually happened.
We go into more detail on this topic in a separate article,
[./rebaseharm.md | Rebase Considered Harmful].
<h3 id="testing">2.8 Test Before Commit</h3>
One of the things that falls out of Git's default separation of commit
from push is that there are several Git sub-commands that jump straight
to the commit step before a change could possibly be tested. Fossil, by
contrast, makes the equivalent change to the local working check-out
only, requiring a separate check-in step to commit the change. This
design difference falls naturally out of Fossil's default-enabled
autosync feature.
The prime example in Git is rebasing: the change happens to the local
repository immediately if successful, even though you haven't tested the
change yet. It's possible to argue for such a design in a tool like Git
which doesn't automatically push the change up to its parent, because
you can still test the change before pushing local changes to the parent
repo, but in the meantime you've made a durable change to your local Git
repository's blockchain. You must do something drastic like <tt>git
reset --hard</tt> to revert that rebase if it causes a problem. If you
push your rebased local repo up to the parent without testing first,
you've now committed the error on a public branch, effectively a
violation of
[https://www.atlassian.com/git/tutorials/merging-vs-rebasing#the-golden-rule-of-rebasing
| the golden rule of rebasing].
Lesser examples are the Git <tt>merge</tt>, <tt>cherry-pick</tt>, and
<tt>revert</tt> commands, all of which apply work from one branch onto
another, and all of which do their work immediately without giving you
an opportunity to test the change first locally unless you give the
<tt>--no-commit</tt> option.
Fossil cannot sensibly work that way because of its default-enabled
autosync feature. Instead of jumping straight to the commit step, Fossil
applies the proposed merge to the local working directory only,
requiring a separate check-in step before the change is committed to the
repository blockchain. This gives you a chance to test the change first,
either manually or by running your software's automatic tests. (Ideally,
both!)
Another difference is that because Fossil requires an explicit commit
for a merge, it makes you give an explicit commit <i>message</i> for
each merge, whereas Git writes that commit message itself by default
unless you give the optional <tt>--edit</tt> flag to override it.
We don't look at this difference as a workaround in Fossil for autosync,
but instead as a test-first philosophical difference. When every commit
is pushed to the parent repo by default, it encourages a working style
in which every commit is tested first. We think this is an inherently
good thing.
Incidentally, this is a good example of Git's messy command design.
These three commands:
<pre>
$ git merge HASH
$ git cherry-pick HASH
$ git revert HASH
</pre>
...are all the same command in Fossil:
<pre>
$ fossil merge HASH
$ fossil merge --cherrypick HASH
$ fossil merge --backout HASH
</pre>
If you think about it, they're all the same function: apply work done on
one branch to another. All that changes between these commands is how
much work gets applied — just one check-in or a whole branch — and the
merge direction. This is the sort of thing we mean when we point out
that Fossil's command interface is simpler than Git's: there are fewer
concepts to keep track of in your mental model of Fossil's internal
operation.
Fossil's implementation of the feature is also simpler to describe. The
brief online help for <tt>[/help?cmd=merge | fossil merge]</tt> is
currently 41 lines long, to which you want to add the 600 lines of
[./branching.wiki | the branching document]. The equivalent
documentation in Git is the aggregation of the man pages for the above
three commands, which is over 1000 lines, much of it mutually redundant.
(e.g. Git's <tt>--edit</tt> and <tt>--no-commit</tt> options get
described three times, each time differently.) Fossil's
documentation is not only more concise, it gives a nice split of brief
online help and full online documentation.
<h3 id="hash">2.9 Hash Algorithm: SHA-3 vs SHA-2 vs SHA-1</h3>
Fossil started out using 160-bit SHA-1 hashes to identify check-ins,
just as in Git. That changed in early 2017 when news of the
[https://shattered.io/|SHAttered attack] broke, demonstrating that SHA-1
collisions were now practical to create. Two weeks later, the creator of
Fossil delivered a new release allowing a clean migration to
[https://en.wikipedia.org/wiki/SHA-3|256-bit SHA-3] with
[./hashpolicy.wiki|full backwards compatibility] to old SHA-1 based
repositories.
In October 2019, after the last of the major binary
package repos offering Fossil upgraded to Fossil 2.<i>x</i>,
we switched the default hash mode so that from
Fossil 2.10 forward, the conversion to SHA-3 is fully automatic.
This not
only solves the SHAttered problem, it should prevent a reoccurrence of
similar problems for the foreseeable future.
Meanwhile, the Git community took until August 2018 to publish
[https://git-scm.com/docs/hash-function-transition/|their first plan]
for solving the same problem by moving to SHA-256, a variant of the
[https://en.wikipedia.org/wiki/SHA-2 | older SHA-2 algorithm]. As of
this writing in February 2020, that plan hasn't been implemented, as far
as this author is aware, but there is now
[https://lwn.net/ml/git/20200113124729.3684846-1-sandals@crustytoothpaste.net/
| a competing SHA-256 based plan] which requires complete repository
conversion from SHA-1 to SHA-256, breaking all public hashes in the
repo. One way to characterize such a massive upheaval in Git terms is a
whole-project rebase, which violates
[https://blog.axosoft.com/golden-rule-of-rebasing-in-git/ | Git's own
Golden Rule of Rebasing].
Regardless of the eventual implementation details, we fully expect Git
to move off SHA-1 eventually and for the changes to take years more to
percolate through the community.
Almost three years after Fossil solved this problem, the
[https://sha-mbles.github.io/ | SHAmbles attack] was published, further
weakening the case for continuing to use SHA-1.
The practical impact of attacks like SHAttered and SHAmbles on the
Git and Fossil blockchains isn't clear, but you want to have your repositories
moved over to a stronger hash algorithm before someone figures out how
to make use of the weaknesses in the old one. Fossil has had this covered
for years now, so that the solution is now almost universally deployed.
<hr/>
<h3>Asides and Digressions</h3>
<i><small><ol>
<li><p>[./mirrorlimitations.md|Many
things are lost] in making a Git mirror of a Fossil repo due to
limitations of Git relative to Fossil. GitHub adds some of these
missing features to stock
Git, but because they're not part of Git proper,
[./mirrortogithub.md|exporting a Fossil repository to GitHub] will
still not include them; Fossil tickets do not become GitHub issues,
for example.
<li><p>The <tt>fossil-scm.org</tt> web site is actually hosted in
several parts, so that it is not strictly true that "everything" on
it is in the self-hosting Fossil project repo. The web forum is
hosted as [https://fossil-scm.org/forum/|a separate Fossil repo]
from the [https://fossil-scm.org/fossil/|main Fossil self-hosting
repo] for administration reasons, and the Download page content
isn't normally synchronized with a "<tt>fossil clone</tt>" command unless
you add the "-u" option. (See "[./aboutdownload.wiki|How the
Download Page Works]" for details.) There may also be some purely
static elements of the web site served via D. Richard Hipp's own
lightweight web server,
<tt>[https://sqlite.org/docsrc/doc/trunk/misc/althttpd.md|althttpd]</tt>,
which is configured as a front end to Fossil running in CGI mode on
these sites.
<li><p>That estimate is based on pricing at Digital Ocean in
mid-2019: Fossil will run just fine on the smallest instance they
offer, at US $5/month, but the closest match to GitLab's minimum
requirements among Digital Ocean's offerings currently costs
$40/month.
<li><p>This means you can give up waiting for Fossil to be ported to
the PDP-11, but we remain hopeful that someone may eventually port
it to [https://en.wikipedia.org/wiki/Z/OS|z/OS].
<li><p>"Why is there all this Tcl in and around Fossil?" you may
ask. It is because D. Richard Hipp is a long-time Tcl user and
contributor. SQLite started out as an embedded database for Tcl
specifically. ([https://sqlite.org/tclsqlite.html | [Reference]])
When he then created Fossil to manage the development of SQLite, it
was natural for him to use Tcl-based tools for its scripting, build
system, test system, etc. It came full circle in 2011 when
[https://www.reddit.com/r/programming/comments/fwrx5/tcl_and_tk_move_away_from_cvs_to_fossil/
| the Tcl and Tk projects moved from CVS to Fossil].
<li><p>A minority of the pieces of the Git core software suite are
written in other languages, primarily Perl, Python, and Tcl. (e.g.
<tt>git-send-mail</tt>, <tt>git-p4</tt>, and <tt>gitk</tt>,
respectively.) Although these interpreters are quite portable, they
aren't installed by default everywhere, and on some platforms you
can't count on them at all. (Not just Windows, but also the BSDs and
many other non-Linux platforms.) This expands the dependency
footprint of Git considerably. It is why the current Git for Windows
distribution is 44.7 MiB but the current <tt>fossil.exe</tt>
zip file for Windows is 2.24 MiB. Fossil is much smaller
despite using a roughly similar amount of high-level scripting code
because its interpreters are compact and built into Fossil itself.
<li><p>Both Fossil and Git support
[https://en.wikipedia.org/wiki/Patch_(Unix)|<tt>patch(1)</tt>
files], a common way to allow drive-by contributions, but it's a
lossy contribution path for both systems. Unlike Git PRs and Fossil
bundles, patch files collapse multiple checkins together, they don't
include check-in comments, and they cannot encode changes made above
the individual file content layer: you lose branching decisions,
tag changes, file renames, and more when using patch files.</p></li>
</ol></i></small>
|
| ︙ | ︙ | |||
11 12 13 14 15 16 17 |
# variable $fossil_info_project_name to an empty string and return.
#
function get_fossil_data() {
fossil_info_project_name=""
eval `get_fossil_data2`
}
function get_fossil_data2() {
| > | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# variable $fossil_info_project_name to an empty string and return.
#
function get_fossil_data() {
fossil_info_project_name=""
eval `get_fossil_data2`
}
function get_fossil_data2() {
fossil info 2> /dev/null |tr '\042\047\140' _|grep "^[^ ]*:" |
while read LINE ; do
local field=`echo $LINE | sed 's/:.*$//' | sed 's/-/_/'`
local value=`echo $LINE | sed 's/^[^ ]*: *//'`
echo fossil_info_${field}=\"${value}\"
done
}
#-------------------------------------------------------------------------
|
| ︙ | ︙ |
1 2 3 4 5 6 7 | <title>Fossilized Bash Prompt</title> <h1>2013-02-21</h1> Dan Kennedy has contributed a [./fossil_prompt.sh?mimetype=text/plain | bash script] that manipulates the bash prompt to show the status of the Fossil repository that the user is currently visiting. | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <title>Fossilized Bash Prompt</title> <h1>2013-02-21</h1> Dan Kennedy has contributed a [./fossil_prompt.sh?mimetype=text/plain | bash script] that manipulates the bash prompt to show the status of the Fossil repository that the user is currently visiting. The prompt shows the branch, version, and time stamp for the current checkout, and the prompt changes colors from blue to red when there are uncommitted changes. To try out this script, simply download it from the link above, then type: <blockquote><pre> |
| ︙ | ︙ |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Hints For Users With Prior Git Experience
This document is a semi-random collection of hints intended to help
new users of Fossil who have had prior exposure to Git. In other words,
this document tries to describe the differences in how Fossil works
from the perspective of Git users.
## Help Improve This Document
If you have a lot of prior Git experience, and you are new to Fossil
and are struggling with some concepts, please ask for help on the
[Fossil Forum][1]. The people who write this document are intimately
familiar with Fossil and less familiar with Git. It is difficult for
us to anticipate the perspective of people who are initimately familiar
with Git and less familiar with Fossil. Asking questions on the Forum
will help us to improve the document.
[1]: https://fossil-scm.org/forum
Specific suggestions on how to improve this document are also welcomed,
of course.
## Repositories And Checkouts Are Distinct
A repository and a check-out are distinct concepts in Fossil, whereas
the two are often conflated with Git. A repository is a database in
which the entire history of a project is stored. A check-out is a
directory hierarchy that contains a snapshot of your project that you
are currently working on. See [detailed definitions][2] for more
information. With Git, the repository and check-out are closely
related - the repository is the contents of the "`.git`" subdirectory
at the root of your check-out. But with Fossil, the repository and
the check-out are completely separate. A Fossil repository can reside
in the same directory hierarchy with the check-out as with Git, but it
is more common to put the repository in a separate directory.
[2]: ./whyusefossil.wiki#definitions
Fossil repositories are a single file, rather than being a directory
hierarchy as with the "`.git`" folder in Git. The repository file
can be named anything you want, but it is best to use the "`.fossil`"
suffix. Many people choose to gather all of their Fossil repositories
in a single directory on their machine, such as "`~/Fossils`" or
"`C:\Fossils`". This can help humans to keep their repositories
organized, but Fossil itself doesn't really care.
Because Fossil cleanly separates the repository from the check-out, it
is routine to have multiple check-outs from the same repository. Each
check-out can be on a separate branch, or on the same branch. Each
check-out operates independently of the others.
Each Fossil check-out contains a file (usually named "`.fslckout`" on
unix or "`_FOSSIL_`" on Windows) that keeps track of the status of that
particular check-out and keeps a pointer to the repository. If you
move or rename the repository file, the check-outs won't be able to find
it and will complain. But you can freely move check-outs around without
causing any problems.
## There Is No Staging Area
Fossil omits the "Git index" or "staging area" concept. When you
type "`fossil commit`" _all_ changes in your check-out are committed,
automatically. There is no need for the "-a" option as with Git.
If you only want to commit just some of the changes, you can list the names
of the files you want to commit as arguments, like this:
fossil commit src/main.c doc/readme.md
## Create Branches After-The-Fact
Fossil perfers that you create new branches when you commit using
the "`--branch` _BRANCH-NAME_" command-line option. For example:
fossil commit --branch my-new-branch
It is not necessary to create branches ahead of time, as in Git, though
that is allowed using the "`fossil branch new`" command, if you
prefer. Fossil also allows you to move a check-in to a different branch
*after* you commit it, using the "`fossil amend`" command.
For example:
fossil amend current --branch my-new-branch
## Autosync
Fossil has a feature called "[autosync][5]". Autosync defaults on.
When autosync is enabled, Fossil automatically pushes your changes
to the remote server whenever you "`fossil commit`". It also automatically
pulls all remote changes down to your local repository before you
"`fossil update`".
[5]: ./concepts.wiki#workflow
Autosync provides most of the advantages of a centralized version
control system while retaining the advantages of distributed version
control. Your work stays synced up with your coworkers at all times.
If your local machine dies catastrophically, you haven't lost any
(committed) work. But you can still work and commit while off network,
with changes resyncing automatically when you get back on-line.
## Syncing Is All-Or-Nothing
Fossil does not support the concept of syncing, pushing, or pulling
individual branches. When you sync/push/pull in Fossil, you sync/push/pull
everything - all branches, all wiki, all tickets, all forum posts,
all tags, all technotes - everything.
## The Main Branch Is Called "`trunk`", not "`master`"
In Fossil, the traditional name and the default name for the main branch
is "`trunk`". The "`trunk`" branch in Fossil corresponds to the
"`master`" branch in Git.
These naming conventions are so embedded in each system, that the
"trunk" branch name is automatically translated to "master" when
a [Fossil repo is mirrored to GitHub][6].
[6]: ./mirrortogithub.md
## The "`fossil status`" Command Does Not Show Unmanaged Files
The "`fossil status`" command shows you what files in your check-out have
been edited and scheduled for adding or removing at the next commit.
But unlike "`git status`", the "`fossil status`" command does not warn
you about unmanaged files in your local check-out. There is a separate
"`fossil extras`" command for that.
## There Is No Rebase
Fossil does not support rebase.
This is a [deliberate design decision][3] that has been thoroughly,
carefully, and throughtfully discussed, many times. If you are fond
of rebase, you should read the [Rebase Considered Harmful][3] document
carefully before expressing your views.
[3]: ./rebaseharm.md
## Branch and Tag Names
Fossil has no special restrictions on the names of tags and branches,
though you might want to to keep [Git's tag and branch name restrictions][4]
in mind if you plan on mirroring your Fossil repository to GitHub.
[4]: https://git-scm.com/docs/git-check-ref-format
Fossil does not require tag and branch names to be unique. It is
common, for example, to put a "`release`" tag on every release for a
Fossil-hosted project.
## Only One "origin" At A Time
A Fossil repository only keeps track of one "origin" server at a time.
If you specify a new "origin" it forgets the previous one. Use the
"`fossil remote`" command to see or change the "origin".
Fossil uses a very different sync protocol than Git, so it isn't as
important for Fossil to keep track of multiple origins as it is with
Git. So only having a single origin has never been a big enough problem
in Fossil that somebody felt the need to extend it.
Maybe we will add multiple origin support to Fossil someday. Patches
are welcomed if you want to have a go at it.
## Cherry-pick Is An Option To The "merge" Command
In Git, "`git cherry-pick`" is a separate command.
In Fossil, "`fossil merge --cherrypick`" is an option on the merge
command. Otherwise, they work mostly the same.
Except, the Fossil file format remembers cherrypicks and actually
shows them as dashed lines on the graphical DAG display, whereas
there is no provision for recording cherry-picks in the Git file
format, so you have to talk about the cherry-pick in the commit
comment if you want to remember it.
## The "`fossil mv`" and "`fossil rm`" Commands Do Not Actually Rename Or Delete The Files (by default)
By default,
the "`fossil mv`" and "`fossil rm`" commands work like they do in CVS in
that they schedule the changes for the next commit, but do not actually
rename or delete the files in your check-out. You can to add the "--hard"
option to also changes the files in your check-out.
If you run
fossil setting --global mv-rm-files 1
it makes a notation in your per-user "~/.fossil" settings file so that
the "--hard" behavior becomes the new default.
|
1 2 3 4 5 6 7 8 | # File Name Glob Patterns A [glob pattern][glob] is a text expression that matches one or more file names using wild cards familiar to most users of a command line. For example, `*` is a glob that matches any name at all and `Readme.txt` is a glob that matches exactly one file. | < | > | > > | > < | | | | < < | | < < | < | | > | | < | < | | > > | < | | > > > > > | > > > | | > > > > > > > > > | > > | | > | | | < < < < < < < | | | | | | < | | > | > | | > > > | > | > | | > | | > > > > | 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 |
# File Name Glob Patterns
A [glob pattern][glob] is a text expression that matches one or more
file names using wild cards familiar to most users of a command line.
For example, `*` is a glob that matches any name at all and
`Readme.txt` is a glob that matches exactly one file.
A glob should not be confused with a [regular expression][regexp] (RE),
even though they use some of the same special characters for similar
purposes, because [they are not fully compatible][greinc] pattern
matching languages. Fossil uses globs when matching file names with the
settings described in this document, not REs.
[glob]: https://en.wikipedia.org/wiki/Glob_(programming)
[greinc]: https://unix.stackexchange.com/a/57958/138
[regexp]: https://en.wikipedia.org/wiki/Regular_expression
These settings hold one or more file glob patterns to cause Fossil to
give matching named files special treatment. Glob patterns are also
accepted in options to certain commands and as query parameters to
certain Fossil UI web pages.
Where Fossil also accepts globs in commands, this handling may interact
with your OS’s command shell or its C runtime system, because they may
have their own glob pattern handling. We will detail such interactions
below.
## Syntax
Where Fossil accepts glob patterns, it will usually accept a *list* of
such patterns, each individual pattern separated from the others
by white space or commas. If a glob must contain white spaces or
commas, it can be quoted with either single or double quotation marks.
A list is said to match if any one glob in the list
matches.
A glob pattern matches a given file name if it successfully consumes and
matches the *entire* name. Partial matches are failed matches.
Most characters in a glob pattern consume a single character of the file
name and must match it exactly. For instance, “a” in a glob simply
matches the letter “a” in the file name unless it is inside a special
character sequence.
Other characters have special meaning, and they may include otherwise
normal characters to give them special meaning:
:Pattern |:Effect
---------------------------------------------------------------------
`*` | Matches any sequence of zero or more characters
`?` | Matches exactly one character
`[...]` | Matches one character from the enclosed list of characters
`[^...]` | Matches one character *not* in the enclosed list
Note that unlike [POSIX globs][pg], these special characters and
sequences are allowed to match `/` directory separators as well as the
initial `.` in the name of a hidden file or directory. This is because
Fossil file names are stored as complete path names. The distinction
between file name and directory name is “below” Fossil in this sense.
[pg]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13
The bracket expresssions above require some additional explanation:
* A range of characters may be specified with `-`, so `[a-f]` matches
exactly the same characters as `[abcdef]`. Ranges reflect Unicode
code points without any locale-specific collation sequence.
Therefore, this particular sequence never matches the Unicode
pre-composed character `é`, for example. (U+00E9)
* This dependence on character/code point ordering may have other
effects to surprise you. For example, the glob `[A-z]` not only
matches upper and lowercase ASCII letters, it also matches several
punctuation characters placed between `Z` and `a` in both ASCII and
Unicode: `[`, `\`, `]`, `^`, `_`, and <tt>\`</tt>.
* You may include a literal `-` in a list by placing it last, just
before the `]`.
* You may include a literal `]` in a list by making the first
character after the `[` or `[^`. At any other place, `]` ends the list.
* You may include a literal `^` in a list by placing it anywhere
except after the opening `[`.
* Beware that a range must be specified from low value to high
value: `[z-a]` does not match any character at all, preventing the
entire glob from matching.
Some examples of character lists:
:Pattern |:Effect
---------------------------------------------------------------------
`[a-d]` | Matches any one of `a`, `b`, `c`, or `d` but not `ä`
`[^a-d]` | Matches exactly one character other than `a`, `b`, `c`, or `d`
`[0-9a-fA-F]` | Matches exactly one hexadecimal digit
`[a-]` | Matches either `a` or `-`
`[][]` | Matches either `]` or `[`
`[^]]` | Matches exactly one character other than `]`
`[]^]` | Matches either `]` or `^`
`[^-]` | Matches exactly one character other than `-`
White space means the specific ASCII characters TAB, LF, VT, FF, CR,
and SPACE. Note that this does not include any of the many additional
spacing characters available in Unicode such as
U+00A0, NO-BREAK SPACE.
Because both LF and CR are white space and leading and trailing spaces
are stripped from each glob in a list, a list of globs may be broken
into lines between globs when the list is stored in a file, as for a
versioned setting.
Note that 'single quotes' and "double quotes" are the ASCII straight
quote characters, not any of the other quotation marks provided in
Unicode and specifically not the "curly" quotes preferred by
typesetters and word processors.
## File Names to Match
Before it is compared to a glob pattern, each file name is transformed
to a canonical form:
* all directory separators are changed to `/`
* redundant slashes are removed
* all `.` path components are removed
* all `..` path components are resolved
(There are additional details we are ignoring here, but they cover rare
edge cases and follow the principle of least surprise.)
The glob must match the *entire* canonical file name to be considered a
match.
The goal is to have a name that is the simplest possible for each
particular file, and that will be the same regardless of the platform
you run Fossil on. This is important when you have a repository cloned
from multiple platforms and have globs in versioned settings: you want
those settings to be interpreted the same way everywhere.
Beware, however, that all glob matching in Fossil is case sensitive
regardless of host platform and file system. This will not be a surprise
on POSIX platforms where file names are usually treated case
sensitively. However, most Windows file systems are case preserving but
case insensitive. That is, on Windows, the names `ReadMe` and `README`
are usually names of the same file. The same is true in other cases,
such as by default on macOS file systems and in the file system drivers
for Windows file systems running on non-Windows systems. (e.g. exfat on
Linux.) Therefore, write your Fossil glob patterns to match the name of
the file as checked into the repository.
Some example cases:
:Pattern |:Effect
--------------------------------------------------------------------------------
`README` | Matches only a file named `README` in the root of the tree. It does not match a file named `src/README` because it does not include any characters that consume (and match) the `src/` part.
`*/README` | Matches `src/README`. Unlike Unix file globs, it also matches `src/library/README`. However it does not match the file `README` in the root of the tree.
|
| ︙ | ︙ | |||
186 187 188 189 190 191 192 193 194 195 196 197 198 199 | * [`changes`][] * [`clean`][] * [`commit`][] * [`extras`][] * [`merge`][] * [`settings`][] * [`status`][] * [`unset`][] The commands [`tarball`][] and [`zip`][] produce compressed archives of a specific checkin. They may be further restricted by options that specify glob patterns that name files to include or exclude rather than archiving the entire checkin. | > | 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | * [`changes`][] * [`clean`][] * [`commit`][] * [`extras`][] * [`merge`][] * [`settings`][] * [`status`][] * [`touch`][] * [`unset`][] The commands [`tarball`][] and [`zip`][] produce compressed archives of a specific checkin. They may be further restricted by options that specify glob patterns that name files to include or exclude rather than archiving the entire checkin. |
| ︙ | ︙ | |||
207 208 209 210 211 212 213 214 215 216 217 218 219 220 | [`changes`]: /help?cmd=changes [`clean`]: /help?cmd=clean [`commit`]: /help?cmd=commit [`extras`]: /help?cmd=extras [`merge`]: /help?cmd=merge [`settings`]: /help?cmd=settings [`status`]: /help?cmd=status [`unset`]: /help?cmd=unset [`tarball`]: /help?cmd=tarball [`zip`]: /help?cmd=zip [`http`]: /help?cmd=http [`cgi`]: /help?cmd=cgi | > | 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | [`changes`]: /help?cmd=changes [`clean`]: /help?cmd=clean [`commit`]: /help?cmd=commit [`extras`]: /help?cmd=extras [`merge`]: /help?cmd=merge [`settings`]: /help?cmd=settings [`status`]: /help?cmd=status [`touch`]: /help?cmd=touch [`unset`]: /help?cmd=unset [`tarball`]: /help?cmd=tarball [`zip`]: /help?cmd=zip [`http`]: /help?cmd=http [`cgi`]: /help?cmd=cgi |
| ︙ | ︙ | |||
257 258 259 260 261 262 263 | settings` commands. That advice does not help you when you are giving one-off glob patterns in `fossil` commands. The remainder of this section gives remedies and workarounds for these problems. | | | 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 | settings` commands. That advice does not help you when you are giving one-off glob patterns in `fossil` commands. The remainder of this section gives remedies and workarounds for these problems. ### <a name="posix"></a>POSIX Systems If you are using Fossil on a system with a POSIX-compatible shell — Linux, macOS, the BSDs, Unix, Cygwin, WSL etc. — the shell may expand the glob patterns before passing the result to the `fossil` executable. Sometimes this is exactly what you want. Consider this command for |
| ︙ | ︙ | |||
344 345 346 347 348 349 350 | above to make sure the right set of files were scheduled for insertion into the repository before checking the changes in. You never want to accidentally check something like a password, an API key, or the private half of a public cryptographic key into Fossil repository that can be read by people who should not have such secrets. | > > > > > > > > > > > > > > > > > | | > > | | > | < < | < < < < < < < < < < > | | > > > | > > > > > > > | < > | | | | > > > > > > > > | > | | > > | > > > > > > > > > > > > > > > > | | | | | | | | | | | | | | | | | 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 |
above to make sure the right set of files were scheduled for insertion
into the repository before checking the changes in. You never want to
accidentally check something like a password, an API key, or the
private half of a public cryptographic key into Fossil repository that
can be read by people who should not have such secrets.
### <a name="windows"></a>Windows
Before we get into Windows-specific details here, beware that this
section does not apply to the several Microsoft Windows extensions that
provide POSIX semantics to Windows, for which you want to use the advice
in [the POSIX section above](#posix) instead:
* the ancient and rarely-used [Microsoft POSIX subsystem][mps];
* its now-discontinued replacement feature, [Services for Unix][sfu]; or
* their modern replacement, the [Windows Subsystem for Linux][wsl]
[mps]: https://en.wikipedia.org/wiki/Microsoft_POSIX_subsystem
[sfu]: https://en.wikipedia.org/wiki/Windows_Services_for_UNIX
[wsl]: https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux
(The latter is sometimes incorrectly called "Bash on Windows" or "Ubuntu
on Windows," but the feature provides much more than just Bash or Ubuntu
for Windows.)
Neither standard Windows command shell — `cmd.exe` or PowerShell
— expands glob patterns the way POSIX shells do. Windows command
shells rely on the command itself to do the glob pattern expansion. The
way this works depends on several factors:
* the version of Windows you are using
* which OS upgrades have been applied to it
* the compiler that built your Fossil executable
* whether you are running the command interactively
* whether the command is built against a runtime system that does this
at all
* whether the Fossil command is being run from a file named `*.BAT` vs
being named `*.CMD`
Usually (but not always!) the C runtime library that your `fossil.exe`
executable is built against does this glob expansion on Windows so the
program proper does not have to. This may then interact with the way the
Windows command shell you’re using handles argument quoting. Because of
these differences, it is common to find perfectly valid Fossil command
examples that were written and tested on a POSIX system which then fail
when tried on Windows.
The most common problem is figuring out how to get a glob pattern passed
on the command line into `fossil.exe` without it being expanded by the C
runtime library that your particular Fossil executable is linked to,
which tries to act like [the POSIX systems described above](#posix). Windows is
not strongly governed by POSIX, so it has not historically hewed closely
to its strictures.
For example, consider how you would set `crlf-glob` to `*` in order to
get normal Windows text files with CR+LF line endings past Fossil's
"looks like a binary file" check. The naïve approach will not work:
C:\...> fossil setting crlf-glob *
The C runtime library will expand that to the list of all files in the
current directory, which will probably cause a Fossil error because
Fossil expects either nothing or option flags after the setting's new
value, not a list of file names. (To be fair, the same thing will happen
on POSIX systems, only at the shell level, before `.../bin/fossil` even
gets run by the shell.)
Let's try again:
C:\...> fossil setting crlf-glob '*'
Quoting the argument like that will work reliably on POSIX, but it may
or may not work on Windows. If your Windows command shell interprets the
quotes, it means `fossil.exe` will see only the bare `*` so the C
runtime library it is linked to will likely expand the list of files in
the current directory before the `setting` command gets a chance to
parse the command line arguments, causing the same failure as above.
This alternative only works if you’re using a Windows command shell that
passes the quotes through to the executable *and* you have linked Fossil
to a C runtime library that interprets the quotes properly itself,
resulting in a bare `*` getting clear down to Fossil’s `setting` command
parser.
An approach that *will* work reliably is:
C:\...> echo * | fossil setting crlf-glob --args -
This works because the built-in Windows command `echo` does not expand its
arguments, and the `--args -` option makes Fossil read further command
arguments from its standard input, which is connected to the output
of `echo` by the pipe. (`-` is a common Unix convention meaning
"standard input," which Fossil obeys.) A [batch script][fng.cmd] to automate this trick was
posted on the now-inactive Fossil Mailing List.
[fng.cmd]: https://www.mail-archive.com/fossil-users@lists.fossil-scm.org/msg25099.html
(Ironically, this method will *not* work on POSIX systems because it is
not up to the command to expand globs. The shell will expand the `*` in
the `echo` command, so the list of file names will be passed to the
`fossil` standard input, just as with the first example above!)
Another (usually) correct approach which will work on both Windows and
POSIX systems:
C:\...> fossil setting crlf-glob *,
This works because the trailing comma prevents the glob pattern from
matching any files, unless you happen to have files named with a
trailing comma in the current directory. If the pattern matches no
files, it is passed into Fossil's `main()` function as-is by the C
runtime system. Since Fossil uses commas to separate multiple glob
patterns, this means "all files from the root of the Fossil checkout
directory downward and nothing else," which is of course equivalent to
"all managed files in this repository," our original goal.
## Experimenting
To preview the effects of command line glob pattern expansion for
various glob patterns (unquoted, quoted, comma-terminated), for any
combination of command shell, OS, C run time, and Fossil version,
preceed the command you want to test with [`test-echo`][] like so:
$ fossil test-echo setting crlf-glob "*"
C:\> echo * | fossil test-echo setting crlf-glob --args -
The [`test-glob`][] command is also handy to test if a string
matches a glob pattern.
[`test-echo`]: /help?cmd=test-echo
[`test-glob`]: /help?cmd=test-glob
## Converting `.gitignore` to `ignore-glob`
Many other version control systems handle the specific case of
ignoring certain files differently from Fossil: they have you create
individual "ignore" files in each folder, which specify things ignored
in that folder and below. Usually some form of glob patterns are used
in those files, but the details differ from Fossil.
In many simple cases, you can just store a top level "ignore" file in
`.fossil-settings/ignore-glob`. But as usual, there will be lots of
edge cases.
[Git has a rich collection of ignore files][gitignore] which
accumulate rules that affect the current command. There are global
files, per-user files, per workspace unmanaged files, and fully
version controlled files. Some of the files used have no set name, but
are called out in configuration files.
[gitignore]: https://git-scm.com/docs/gitignore
In contrast, Fossil has a global setting and a local setting, but the local setting
overrides the global rather than extending it. Similarly, a Fossil
command's `--ignore` option replaces the `ignore-glob` setting rather
than extending it.
With that in mind, translating a `.gitignore` file into
`.fossil-settings/ignore-glob` may be possible in many cases. Here are
some of features of `.gitignore` and comments on how they relate to
Fossil:
* "A blank line matches no files...": same in Fossil.
* "A line starting with # serves as a comment....": not in Fossil.
* "Trailing spaces are ignored unless they are quoted..." is similar
in Fossil. All whitespace before and after a glob is trimmed in
Fossil unless quoted with single or double quotes. Git uses
backslash quoting instead, which Fossil does not.
* "An optional prefix "!" which negates the pattern...": not in
Fossil.
* Git's globs are relative to the location of the `.gitignore` file:
Fossil's globs are relative to the root of the workspace.
* Git's globs and Fossil's globs treat directory separators
differently. Git includes a notation for zero or more directories
that is not needed in Fossil.
### Example
In a project with source and documentation:
work
+-- doc
|
| ︙ | ︙ | |||
500 501 502 503 504 505 506 | ## Implementation and References | | < < < < < < < | | > > > | > > | > | < | < | < | 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 | ## Implementation and References The implementation of the Fossil-specific glob pattern handling is here: :File |:Description -------------------------------------------------------------------------------- [`src/glob.c`][] | pattern list loading, parsing, and generic matching code [`src/file.c`][] | application of glob patterns to file names [`src/glob.c`]: https://www.fossil-scm.org/index.html/file/src/glob.c [`src/file.c`]: https://www.fossil-scm.org/index.html/file/src/file.c See the [Adding Features to Fossil][aff] document for broader details about finding and working with such code. The actual pattern matching leverages the `GLOB` operator in SQLite, so you may find [its documentation][gdoc], [source code][gsrc] and [test harness][gtst] helpful. [aff]: ./adding_code.wiki [gdoc]: https://sqlite.org/lang_expr.html#like [gsrc]: https://www.sqlite.org/src/artifact?name=9d52522cc8ae7f5c&ln=570-768 [gtst]: https://www.sqlite.org/src/artifact?name=66a2c9ac34f74f03&ln=586-673 |
| ︙ | ︙ | |||
37 38 39 40 41 42 43 |
number in `fossil grep` output.
* There is no way to suppress all output, returning only a status code
to indicate whether the pattern matched, as with `grep -q`.
* There is no way to suppress error output, as with `grep -s`.
| | < | | | | | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
number in `fossil grep` output.
* There is no way to suppress all output, returning only a status code
to indicate whether the pattern matched, as with `grep -q`.
* There is no way to suppress error output, as with `grep -s`.
* Fossil `grep` does not accept a directory name for Fossil to
expand to the set of all files under that directory. This means
Fossil `grep` has no equivalent of the common POSIX `grep -R`
extension. (And if it did, it would probably have a different
option letter, since `-R` in Fossil has a different meaning, by
convention.)
* You cannot invert the match, as with `grep -v`.
Patches to remove those limitations will be thoughtfully considered.
|
| ︙ | ︙ |
|
| | | > < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <title>Fossil Developer How-To</title> The following links are of interest to programmers who want to modify or enhance Fossil itself. Ordinary users can safely ignore this information. * [./build.wiki | How To Compile And Install Fossil] * [./customskin.md | Theming Fossil] * [./adding_code.wiki#newcmd | Adding New Commands To Fossil] * [./makefile.wiki | The Fossil Build Process] * [./tech_overview.wiki | A Technical Overview of Fossil] * [./contribute.wiki|Contributing Code Or Enhancements To The Fossil Project] * [./fileformat.wiki|Fossil Artifact File Format] * [./sync.wiki|The Sync Protocol] * [./style.wiki | Coding Style Guidelines] * [./checkin.wiki | Pre-checkin Checklist] * [../test/release-checklist.wiki | Release Checklist] * [./backoffice.md | The "backoffice" subsystem] |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | # Hashes: Fossil Artifact Identification All artifacts in Fossil are identified by a unique hash, currently using [the SHA3 algorithm by default][hpol], but historically using the SHA1 algorithm: <table border="1" cellspacing="0" cellpadding="10"> <tr><th>Algorithm<</th><th>Raw Bits</th> <th>Hexadecimal digits</th></tr> <tr><td>SHA3-256</td> <td>256</td> <td>64</td></tr> <tr><td>SHA1</td> <td>160</td> <td>40</td></tr> </table> There are many types of artifacts in Fossil: commits (a.k.a. check-ins), tickets, ticket comments, wiki articles, forum postings, file data belonging to check-ins, etc. ([More info...](./concepts.wiki#artifacts)). There is a loose hierarchy of terms used instead of “hash” in various parts of the Fossil UI, which we cover in the sections below. ## Names Several Fossil interfaces accept [a wide variety of check-in names][cin]: commit artifact hashes, ISO8601 date strings, branch names, etc. Fossil interfaces that accept any of these options usually document the parameter as “NAME”, so we will use that form to refer to this specialized use. Artifact hashes are only one of many different types of NAME. We use the broad term “NAME” to refer to the whole class of options. We use more specific terms when we mean one particular type of NAME. ## Versions When an artifact hash refers to a specific commit, Fossil sometimes calls it a “VERSION,” a “commit ID,” or a “check-in ID.” We may eventually settle on one of these terms, but all three are currently in common use within Fossil’s docs, UI, and programming interfaces. A VERSION is a specific type of artifact hash, distinct from, let us say, a wiki article artifact hash. A unique prefix of a VERSION hash is itself a VERSION. That is, if your repository has exactly one commit artifact with a hash prefix of “abc123”, then that is a valid version string as long as it remains unambiguous. ## <a id="uvh"></a>UUIDs Fossil uses the term “UUID” as a short alias for “artifact hash” in its internals. There are a few places where this leaks out into external interfaces, which we cover in the sections below. Going forward, we prefer one of the terms above in public interfaces instead. Whether this short alias is correct is debateable. One argument is that since "UUID" is an acronym for “Universally Unique Identifier,” and both SHA1 and SHA3-256 are larger and stronger than the 128-bit algorithms used by “proper” UUIDs, Fossil artifact hashes are *more universally unique*. It is therefore quibbling to say that Fossil UUIDs are not actually UUIDs. One wag suggested that Fossil artifact hashes be called MUIDs: multiversally unique IDs. The common counterargument is that the acronym “UUID” was created for [a particular type of universally-unique ID][uuid], with particular ASCII and bitfield formats, and with particular meaning given to certain of its bits. In that sense, no Fossil “UUID” can be used as a proper UUID. Be warned: attempting to advance the second position on the Fossil discussion forum will get you nowhere at this late date. We’ve had the debates, we’ve done the engineering, and we’ve made our evaluation. It’s a settled matter: internally within Fossil, “UUID” is defined as in this section’s leading paragraph. To those who remain unconvinced, “fixing” this would require touching almost every source code file in Fossil in a total of about a thousand separate locations. (Not exaggeration, actual data.) This would be a massive undertaking simply to deal with a small matter of terminology, with a high risk of creating bugs and downstream incompatibilities. Therefore, we are highly unlikely to change this ourselves, and we are also unlikely to accept a patch that attempts to fix it. ### Repository DB Schema The primary place where you find "UUID" in Fossil is in the `blob.uuid` table column, in code dealing with that column, and in code manipulating *other* data that *refers* to that column. This is a key lookup column in the most important Fossil DB table, so it influences broad swaths of the Fossil internals. For example, C code that refers to SQL result data on `blob.uuid` usually calls the variable `zUuid`. That value may then be inserted into a table like `ticket.tkt_uuid`, creating a reference back to `blob.uuid`, and then be passed to a function like `uuid_to_rid()`. There is no point renaming a single one of these in isolation: it would create needless terminology conflicts, making the code hard to read and understand, risking the creation of new bugs. You may have local SQL code that digs into the repository DB using these column names. While you may rest easy, assured now that we are highly unlikely to ever rename these columns, the Fossil repository DB schema is not considered an external user interface, and internal interfaces are subject to change at any time. We suggest switching to a more stable API: [the JSON API][japi], [`timeline.rss`][trss], [TH1][th1], etc. ### TH1 Scripting Interfaces Some [TH1][th1] interfaces expose Fossil internals flowing from `blob.uuid`, so “UUID” is a short alias for “artifact hash” in TH1. For example, the `$tkt_uuid` variable — available when [customizing the ticket system][ctkt] — is a ticket artifact hash, exposing the `ticket.tkt_uuid` column, which has a SQL relation to `blob.uuid`. TH1 is a longstanding public programming interface. We cannot rename its interfaces without breaking existing TH1 Fossil customizations. We are also unlikely to provide a parallel set of variables with “better” names, since that would create a mismatch with respect to the internals they expose, creating a different sort of developer confusion in its place. ### JSON API Parameters and Outputs [The JSON API][japi] frequently uses the term “UUID” in the same sort of way, most commonly in [artifact][jart] and [timeline][jtim] APIs. As with TH1, we can’t change this without breaking code that uses the JSON API as originally designed, so we take the same stance. ### `manifest.uuid` If you have [the `manifest` setting][mset] enabled, Fossil writes a file called `manifest.uuid` at the root of the check-out tree containing the commit hash for the current checked-out version. Because this is a public interface that existing code depends on, we are unwilling to rename the file. [cin]: ./checkin_names.wiki [ctkt]: ./custom_ticket.wiki [hpol]: ./hashpolicy.wiki [japi]: ./json-api/ [jart]: ./json-api/api-artifact.md [jtim]: ./json-api/api-timeline.md [mset]: /help?cmd=manifest [th1]: ./th1.md [trss]: /help?cmd=/timeline.rss [tvb]: ./branching.wiki [uuid]: https://en.wikipedia.org/wiki/Universally_unique_identifier |
1 2 3 4 5 6 7 8 9 10 | <title>Hash Policy</title> <h2>Executive Summary</h2> <b>Or: How To Avoid Reading This Article</b> There was much angst over the [http://www.shattered.io|SHAttered attack] against SHA1 when it was announced in early 2017. If you are concerned about this and its implications for Fossil, simply [./quickstart.wiki#install|upgrade to Fossil 2.1 or later], and the | | > | > | > > > | | 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 |
<title>Hash Policy</title>
<h2>Executive Summary</h2>
<b>Or: How To Avoid Reading This Article</b>
There was much angst over the [http://www.shattered.io|SHAttered attack]
against SHA1 when it was announced in early 2017. If you are concerned
about this and its implications for Fossil, simply
[./quickstart.wiki#install|upgrade to Fossil 2.1 or later], and the
problem will go away. Everything will continue to work as before.
* Legacy repositories will continue working just as
they always have, without any conversions or upgrades.
* Historical check-ins will keep their same historical
SHA1 names.
* New check-ins will get more secure SHA3-256 hash names.
* Everything will continue as if nothing happened.
* Your workflow will be unchanged.
But if you are curious and want a deeper understanding of what is
going on, read on...
<h2>Introduction</h2>
|
| ︙ | ︙ | |||
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | Version 2.0 extended the [./fileformat.wiki|Fossil file format] to allow artifacts to be named by either SHA1 or SHA3-256 hashes. (SHA3-256 is the only variant of SHA3 that Fossil uses for artifact naming, so for the remainder of this article it will be called simply "SHA3". Similarly, "Hardened SHA1" will shortened to "SHA1" in the remaining text.) Other than permitting the use of SHA3 in addition to SHA1, there were no file format changes in Fossil version 2.0 relative to the previous version 1.37. Both Fossil 2.0 and Fossil 1.37 read and write all the same repositories and sync with one another, as long as none of the repositories contain artifacts named using SHA3. If a repository does contain artifacts named using SHA3, Fossil 1.37 will not know how to interpret those artifacts and will generate various warnings and errors. | > > > > > > > > > > | | | | > | | | | | | | > | < < | | | | > < | > | | > | < > | | < | < < < < < | 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 | Version 2.0 extended the [./fileformat.wiki|Fossil file format] to allow artifacts to be named by either SHA1 or SHA3-256 hashes. (SHA3-256 is the only variant of SHA3 that Fossil uses for artifact naming, so for the remainder of this article it will be called simply "SHA3". Similarly, "Hardened SHA1" will shortened to "SHA1" in the remaining text.) To be clear: Fossil (version 2.0 and later) allows the SHA1 and SHA3 hashes to be mixed within the same repository. Older check-ins, created years ago, continue to be named using their legacy SHA1 hashes while newer check-ins are named using modern SHA3 hashes. There is no need to "convert" a repository from SHA1 over to SHA3. You can see this in Fossil itself. The recent [9d9ef82234f63758] check-in uses a SHA3 hash whereas the older [1669115ab9d05c18] check-in uses a SHA1 hash. Other than permitting the use of SHA3 in addition to SHA1, there were no file format changes in Fossil version 2.0 relative to the previous version 1.37. Both Fossil 2.0 and Fossil 1.37 read and write all the same repositories and sync with one another, as long as none of the repositories contain artifacts named using SHA3. If a repository does contain artifacts named using SHA3, Fossil 1.37 will not know how to interpret those artifacts and will generate various warnings and errors. <h2>How Fossil Decides Which Hash Algorithm To Use For New Artifacts</h2> If newer versions of Fossil are able to use either SHA1 or SHA3 to name artifacts, which hash algorithm is actually used? That question is answered by the "hash policy". These are the supported hash policies: <table cellpadding=10> <tr> <td valign='top'>sha1</td> <td>Name all new artifacts using the (Hardened) SHA1 hash algorithm.</td> </tr> <tr> <td valign='top'>auto</td> <td>Name new artifacts using the SHA1 hash algorithm, but if any artifacts are encountered which are already named using SHA3, then automatically switch the hash policy to "sha3"</td> </tr> <tr> <td valign='top'>sha3</td> <td>Name new artifacts using the SHA3 hash algorithm if the artifact does not already have a SHA1 name. If the artifact already has a SHA1 name, then continue to use the older SHA1 name. Use SHA3 for new artifacts that have never before been encountered.</td> </tr> <tr> <td valign='top'>sha3-only</td> <td>Name new artifacts using the SHA3 hash algorithm even if the artifact already has a SHA1 name. In other words, force the use of SHA3. This can cause some artifacts to be added to the repository twice, once under their SHA1 name and again under their SHA3 name, but delta compression will prevent that from causing repository size problems.</td> </tr> <tr> <td valign='top'>shun-sha1</td> <td>Like "sha3-only" but at this level do not accept a push of SHA1-named artifacts. If another Fossil instance tries to push a SHA1-named artifact, that artifact is discarded and ignored. </tr> </table> For Fossil 2.0, and obviously also for Fossil 1.37 and before, the only hash policy supported was the one now called "sha1", meaning that all new artifacts were named using a SHA1 hash. Even though Fossil 2.0 added the capability of understanding SHA3 hashes, it never actually generates any SHA3 hashes. From Fossil 2.1 through 2.9, the default hash policy for legacy repositories changed to "auto", meaning that Fossil continued to generate only SHA1 hashes until it encountered one artifact with a SHA3 hash. Once those older versions of Fossil saw a single SHA3 hash, they automatically switched to "sha3" mode and thereafter generated only SHA3 hashes. When a new repository is created by cloning, the hash policy is copied from the parent. For new repositories created using the [/help?cmd=new|fossil new] command the default hash policy is "sha3". That means new repositories will normally hold nothing except SHA3 hashes. The hash policy for new repositories can be overridden using the "--sha1" option to the "fossil new" command. If you are still on Fossil 2.1 through 2.9 but you want Fossil to go ahead and start using SHA3 hashes, change the hash policy to "sha3" using a command like this: <blockquote><verbatim> fossil hash-policy sha3 </verbatim></blockquote> The next check-in will use a SHA3 hash, so that when that check-in is pushed to colleagues, their clones will include the new SHA3-named artifact, so their local Fossil instances will automatically convert their clones to "sha3" mode as well. Of course, if some members of your team stubbornly refuse to upgrade past Fossil 1.37, you should avoid changing the hash policy and creating artifacts with SHA3 names, because once you do that your recalcitrant coworkers will no longer be able to collaborate. <h2>A Pure SHA3 Future</h2> Fossil 2.10 changed the default hash policy to "sha3" mode even for legacy repositories, so if you upgrade to the latest version of Fossil, all of your new artifacts will use a SHA3 hash. Legacy SHA1 artifacts continue to use their original names, but new artifacts will use SHA3 names. You might not even notice this automatic change over to stronger hashes. We decided to make the change to pure SHA3 since the last known distributor of Fossil 1.x binaries — Debian 9 — was finally replaced in June 2019 by Debian 10, which included Fossil 2.8. All other known sources of Fossil 1.x binaries upgraded well before that point. |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | # The History And Purpose Of Fossil Fossil is a [distributed version control system (DVCS)][100] written beginning in [2007][105] by the [architect of SQLite][110] for the purpose of managing the [SQLite project][115]. [100]: https://en.wikipedia.org/wiki/Distributed_version_control [105]: /timeline?a=1970-01-01&n=10 [110]: https://sqlite.org/crew.html [115]: https://sqlite.org/ Though Fossil was originally written specifically to support SQLite, it is now also used by countless other projects. The SQLite architect (drh) is still the top committer to Fossil, but there are also [many other contributors][120]. [120]: /reports?type=ci&view=byuser ## History The SQLite project start out using [CVS][300], as CVS was the most commonly used version control system in that era (circa 2000). CVS was an amazing version control system for its day in that it allowed multiple developers to be editing the same file at the same time. [300]: https://en.wikipedia.org/wiki/Concurrent_Versions_System Though innovative and much loved in its time, CVS was not without problems. Among those was a lack of visibility into the project history and the lack of integrated bug tracking. To try to address these deficiencies, the SQLite author developed the [CVSTrac][305] wrapper for CVS beginning in [2002][310]. [305]: http://cvstrac.org/ [310]: http://cvstrac.org/fossil/timeline?a=19700101&n=10 CVSTrac greatly improved the usability of CVS and was adopted by other projects. CVSTrac also [inspired the design][315] of [Trac][320], which was a similar system that was (and is) far more widely used. [315]: https://trac.edgewall.org/wiki/TracHistory [320]: https://trac.edgewall.org/ Historians can see the influence of CVSTrac on the development of SQLite. [Early SQLite check-ins][325] that happened before CVSTrac often had a check-in comment that was just a "smiley". That was not an unreasonable check-in comment, as check-in comments were scarcely seen and of questionable utility in raw CVS. CVSTrac changed that, making check-in comments more visible and more useful. The SQLite developers reacted by creating [better check-in comments][330]. [325]: https://sqlite.org/src/timeline?a=19700101&n=10 [330]: https://sqlite.org/src/timeline?c=20030101&n=10&nd At about this same time, the [Monotone][335] system appeared. Monotone was one of the first distributed version control systems. As far as this author is aware, Monotone was the first VCS to make use of SHA1 to identify artifacts. Monotone stored its content in an SQLite database, which is what brought it to the attention of the SQLite architect. These design choices were a major source of inspiration for Fossil. [335]: https://www.monotone.ca/ Beginning around 2005, the need for a better version control system for SQLite began to become evident. The SQLite architect looked around for a suitable replacement. Monotone, Git, and Mercurical were all considered. But at that time, none of these supported sync over ordinary HTTP, none could be run from an inexpensive shell account on a leased server (this was before the widespread availability of affordable virtual machines), and none of them supported anything resembling the wiki and ticket features of CVSTrac that had been found to be so useful. And so, the SQLite architect began writing his own DVCS. Early prototypes were done in [TCL][340]. As experiments proceeded, however, it was found that the low-level byte manipulates needed for things like delta compression and computing diffs were better implemented in plain old C. Experiments continued. Finally, a prototype capable of self-hosting was devised on [2007-07-16][345]. [340]: https://www.tcl.tk/ [345]: https://fossil-scm.org/fossil/timeline?c=200707211410&n=10 The first project hosted by Fossil was Fossil itself. After a few months of development work, the code was considered stable enough to begin hosting the [SQLite documentation repository][350] which was split off from the main SQLite CVS repository on [2007-11-12][355]. After two years of development work on Fossil, the SQLite source code itself was transfered to Fossil on [2009-08-11][360]. [350]: https://www.sqlite.org/docsrc/doc/trunk/README.md [355]: https://www.sqlite.org/docsrc/timeline?c=200711120345&n=10 [360]: https://sqlite.org/src/timeline?c=b0848925babde524&n=12&y=ci |
| ︙ | ︙ | |||
8 9 10 11 12 13 14 |
"\n",
"## Prerequisites\n",
"\n",
"This notebook was developed with [JupyterLab][jl]. To follow in my footsteps, install that and the needed Python packages:\n",
"\n",
" $ pip install jupyterlab matplotlib pandas wand\n",
"\n",
| | | | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
"\n",
"## Prerequisites\n",
"\n",
"This notebook was developed with [JupyterLab][jl]. To follow in my footsteps, install that and the needed Python packages:\n",
"\n",
" $ pip install jupyterlab matplotlib pandas wand\n",
"\n",
"In principle, it should also work with [Anaconda Navigator][an], but because [Wand][wp] is not currently in the Anaconda base package set, you may run into difficulties making it work, as we did on macOS. There seems to be some sandboxing that causes problems with OS interaction in that environment. Therefore, we recommend using straight JupyterLab.\n",
"\n",
"This notebook was originally written for the Python 2 kernel because macOS does not include Python 3, but it was later updated for Python 3. It should still be compatible with Python 2, though.\n",
"\n",
"[an]: https://www.anaconda.com/distribution/\n",
"[jl]: https://github.com/jupyterlab/\n",
"[wp]: http://wand-py.org/\n",
"\n",
"\n",
"## Running\n",
|
| ︙ | ︙ | |||
33 34 35 36 37 38 39 |
"## Discussion\n",
"\n",
"That is kept in [a separate document](image-format-vs-repo-size.md) so we can share that document with Fossil's Markdown renderer."
]
},
{
"cell_type": "code",
| | | > > > > > > > > | 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 |
"## Discussion\n",
"\n",
"That is kept in [a separate document](image-format-vs-repo-size.md) so we can share that document with Fossil's Markdown renderer."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Experiment completed in 44.186978816986084 seconds.\n"
]
}
],
"source": [
"import os\n",
"import random\n",
"import time\n",
"\n",
"from wand.color import Color\n",
"from wand.drawing import Drawing\n",
|
| ︙ | ︙ | |||
67 68 69 70 71 72 73 |
" \n",
" def add_repo_size():\n",
" rs.append(os.path.getsize(repo) / 1024.0 / 1024.0)\n",
"\n",
" try:\n",
" # Create test repo\n",
" if not os.path.exists(tdir): os.mkdir(tdir, 0o700)\n",
| | | 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
" \n",
" def add_repo_size():\n",
" rs.append(os.path.getsize(repo) / 1024.0 / 1024.0)\n",
"\n",
" try:\n",
" # Create test repo\n",
" if not os.path.exists(tdir): os.mkdir(tdir, 0o700)\n",
" cmd = 'cd {0} ; fossil init ../{1} && fossil open --nested ../{1} && fossil set binary-glob \"*.{2}\"'.format(\n",
" tdir, repo, ext\n",
" )\n",
" if os.system(cmd) != 0:\n",
" raise RuntimeError('Failed to create test repo ' + repo)\n",
" add_repo_size()\n",
"\n",
" # Create test image and add it to the repo\n",
|
| ︙ | ︙ | |||
133 134 135 136 137 138 139 |
" if os.path.exists(repo): os.remove(repo)\n",
" \n",
"print(\"Experiment completed in \" + str(time.time() - start) + \" seconds.\")"
]
},
{
"cell_type": "code",
| | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > | 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 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 |
" if os.path.exists(repo): os.remove(repo)\n",
" \n",
"print(\"Experiment completed in \" + str(time.time() - start) + \" seconds.\")"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n",
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
"<!-- Created with matplotlib (https://matplotlib.org/) -->\n",
"<svg height=\"268.743125pt\" version=\"1.1\" viewBox=\"0 0 389.28125 268.743125\" width=\"389.28125pt\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
" <defs>\n",
" <style type=\"text/css\">\n",
"*{stroke-linecap:butt;stroke-linejoin:round;}\n",
" </style>\n",
" </defs>\n",
" <g id=\"figure_1\">\n",
" <g id=\"patch_1\">\n",
" <path d=\"M 0 268.743125 \n",
"L 389.28125 268.743125 \n",
"L 389.28125 0 \n",
"L 0 0 \n",
"z\n",
"\" style=\"fill:none;\"/>\n",
" </g>\n",
" <g id=\"axes_1\">\n",
" <g id=\"patch_2\">\n",
" <path d=\"M 43.78125 228.14 \n",
"L 378.58125 228.14 \n",
"L 378.58125 10.7 \n",
"L 43.78125 10.7 \n",
"z\n",
"\" style=\"fill:#ffffff;\"/>\n",
" </g>\n",
" <g id=\"patch_3\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 52.78125 228.14 \n",
"L 59.98125 228.14 \n",
"L 59.98125 154.732751 \n",
"L 52.78125 154.732751 \n",
"z\n",
"\" style=\"fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_4\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 88.78125 228.14 \n",
"L 95.98125 228.14 \n",
"L 95.98125 154.732751 \n",
"L 88.78125 154.732751 \n",
"z\n",
"\" style=\"fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_5\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 124.78125 228.14 \n",
"L 131.98125 228.14 \n",
"L 131.98125 144.687548 \n",
"L 124.78125 144.687548 \n",
"z\n",
"\" style=\"fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_6\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 160.78125 228.14 \n",
"L 167.98125 228.14 \n",
"L 167.98125 130.006098 \n",
"L 160.78125 130.006098 \n",
"z\n",
"\" style=\"fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_7\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 196.78125 228.14 \n",
"L 203.98125 228.14 \n",
"L 203.98125 128.460682 \n",
"L 196.78125 128.460682 \n",
"z\n",
"\" style=\"fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_8\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 232.78125 228.14 \n",
"L 239.98125 228.14 \n",
"L 239.98125 126.915267 \n",
"L 232.78125 126.915267 \n",
"z\n",
"\" style=\"fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_9\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 268.78125 228.14 \n",
"L 275.98125 228.14 \n",
"L 275.98125 126.915267 \n",
"L 268.78125 126.915267 \n",
"z\n",
"\" style=\"fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_10\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 304.78125 228.14 \n",
"L 311.98125 228.14 \n",
"L 311.98125 116.870064 \n",
"L 304.78125 116.870064 \n",
"z\n",
"\" style=\"fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_11\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 340.78125 228.14 \n",
"L 347.98125 228.14 \n",
"L 347.98125 110.688401 \n",
"L 340.78125 110.688401 \n",
"z\n",
"\" style=\"fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_12\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 59.98125 228.14 \n",
"L 67.18125 228.14 \n",
"L 67.18125 118.41548 \n",
"L 59.98125 118.41548 \n",
"z\n",
"\" style=\"fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_13\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 95.98125 228.14 \n",
"L 103.18125 228.14 \n",
"L 103.18125 118.41548 \n",
"L 95.98125 118.41548 \n",
"z\n",
"\" style=\"fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_14\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 131.98125 228.14 \n",
"L 139.18125 228.14 \n",
"L 139.18125 118.41548 \n",
"L 131.98125 118.41548 \n",
"z\n",
"\" style=\"fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_15\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 167.98125 228.14 \n",
"L 175.18125 228.14 \n",
"L 175.18125 118.41548 \n",
"L 167.98125 118.41548 \n",
"z\n",
"\" style=\"fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_16\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 203.98125 228.14 \n",
"L 211.18125 228.14 \n",
"L 211.18125 118.41548 \n",
"L 203.98125 118.41548 \n",
"z\n",
"\" style=\"fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_17\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 239.98125 228.14 \n",
"L 247.18125 228.14 \n",
"L 247.18125 118.41548 \n",
"L 239.98125 118.41548 \n",
"z\n",
"\" style=\"fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_18\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 275.98125 228.14 \n",
"L 283.18125 228.14 \n",
"L 283.18125 118.41548 \n",
"L 275.98125 118.41548 \n",
"z\n",
"\" style=\"fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_19\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 311.98125 228.14 \n",
"L 319.18125 228.14 \n",
"L 319.18125 117.642772 \n",
"L 311.98125 117.642772 \n",
"z\n",
"\" style=\"fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_20\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 347.98125 228.14 \n",
"L 355.18125 228.14 \n",
"L 355.18125 117.642772 \n",
"L 347.98125 117.642772 \n",
"z\n",
"\" style=\"fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_21\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 67.18125 228.14 \n",
"L 74.38125 228.14 \n",
"L 74.38125 118.41548 \n",
"L 67.18125 118.41548 \n",
"z\n",
"\" style=\"fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_22\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 103.18125 228.14 \n",
"L 110.38125 228.14 \n",
"L 110.38125 118.41548 \n",
"L 103.18125 118.41548 \n",
"z\n",
"\" style=\"fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_23\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 139.18125 228.14 \n",
"L 146.38125 228.14 \n",
"L 146.38125 118.41548 \n",
"L 139.18125 118.41548 \n",
"z\n",
"\" style=\"fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_24\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 175.18125 228.14 \n",
"L 182.38125 228.14 \n",
"L 182.38125 118.41548 \n",
"L 175.18125 118.41548 \n",
"z\n",
"\" style=\"fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_25\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 211.18125 228.14 \n",
"L 218.38125 228.14 \n",
"L 218.38125 118.41548 \n",
"L 211.18125 118.41548 \n",
"z\n",
"\" style=\"fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_26\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 247.18125 228.14 \n",
"L 254.38125 228.14 \n",
"L 254.38125 118.41548 \n",
"L 247.18125 118.41548 \n",
"z\n",
"\" style=\"fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_27\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 283.18125 228.14 \n",
"L 290.38125 228.14 \n",
"L 290.38125 118.41548 \n",
"L 283.18125 118.41548 \n",
"z\n",
"\" style=\"fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_28\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 319.18125 228.14 \n",
"L 326.38125 228.14 \n",
"L 326.38125 117.642772 \n",
"L 319.18125 117.642772 \n",
"z\n",
"\" style=\"fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_29\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 355.18125 228.14 \n",
"L 362.38125 228.14 \n",
"L 362.38125 117.642772 \n",
"L 355.18125 117.642772 \n",
"z\n",
"\" style=\"fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_30\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 74.38125 228.14 \n",
"L 81.58125 228.14 \n",
"L 81.58125 119.188188 \n",
"L 74.38125 119.188188 \n",
"z\n",
"\" style=\"fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_31\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 110.38125 228.14 \n",
"L 117.58125 228.14 \n",
"L 117.58125 104.506738 \n",
"L 110.38125 104.506738 \n",
"z\n",
"\" style=\"fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_32\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 146.38125 228.14 \n",
"L 153.58125 228.14 \n",
"L 153.58125 88.279872 \n",
"L 146.38125 88.279872 \n",
"z\n",
"\" style=\"fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_33\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 182.38125 228.14 \n",
"L 189.58125 228.14 \n",
"L 189.58125 77.461962 \n",
"L 182.38125 77.461962 \n",
"z\n",
"\" style=\"fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_34\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 218.38125 228.14 \n",
"L 225.58125 228.14 \n",
"L 225.58125 73.598422 \n",
"L 218.38125 73.598422 \n",
"z\n",
"\" style=\"fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_35\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 254.38125 228.14 \n",
"L 261.58125 228.14 \n",
"L 261.58125 57.371557 \n",
"L 254.38125 57.371557 \n",
"z\n",
"\" style=\"fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_36\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 290.38125 228.14 \n",
"L 297.58125 228.14 \n",
"L 297.58125 57.371557 \n",
"L 290.38125 57.371557 \n",
"z\n",
"\" style=\"fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_37\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 326.38125 228.14 \n",
"L 333.58125 228.14 \n",
"L 333.58125 31.872196 \n",
"L 326.38125 31.872196 \n",
"z\n",
"\" style=\"fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"patch_38\">\n",
" <path clip-path=\"url(#p836f19e185)\" d=\"M 362.38125 228.14 \n",
"L 369.58125 228.14 \n",
"L 369.58125 21.054286 \n",
"L 362.38125 21.054286 \n",
"z\n",
"\" style=\"fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"matplotlib.axis_1\">\n",
" <g id=\"xtick_1\">\n",
" <g id=\"line2d_1\">\n",
" <defs>\n",
" <path d=\"M 0 0 \n",
"L 0 3.5 \n",
"\" id=\"m1f6b1ebb34\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n",
" </defs>\n",
" <g>\n",
" <use style=\"stroke:#000000;stroke-width:0.8;\" x=\"67.18125\" xlink:href=\"#m1f6b1ebb34\" y=\"228.14\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_1\">\n",
" <!-- 3 -->\n",
" <defs>\n",
" <path d=\"M 40.578125 39.3125 \n",
"Q 47.65625 37.796875 51.625 33 \n",
"Q 55.609375 28.21875 55.609375 21.1875 \n",
"Q 55.609375 10.40625 48.1875 4.484375 \n",
"Q 40.765625 -1.421875 27.09375 -1.421875 \n",
"Q 22.515625 -1.421875 17.65625 -0.515625 \n",
"Q 12.796875 0.390625 7.625 2.203125 \n",
"L 7.625 11.71875 \n",
"Q 11.71875 9.328125 16.59375 8.109375 \n",
"Q 21.484375 6.890625 26.8125 6.890625 \n",
"Q 36.078125 6.890625 40.9375 10.546875 \n",
"Q 45.796875 14.203125 45.796875 21.1875 \n",
"Q 45.796875 27.640625 41.28125 31.265625 \n",
"Q 36.765625 34.90625 28.71875 34.90625 \n",
"L 20.21875 34.90625 \n",
"L 20.21875 43.015625 \n",
"L 29.109375 43.015625 \n",
"Q 36.375 43.015625 40.234375 45.921875 \n",
"Q 44.09375 48.828125 44.09375 54.296875 \n",
"Q 44.09375 59.90625 40.109375 62.90625 \n",
"Q 36.140625 65.921875 28.71875 65.921875 \n",
"Q 24.65625 65.921875 20.015625 65.03125 \n",
"Q 15.375 64.15625 9.8125 62.3125 \n",
"L 9.8125 71.09375 \n",
"Q 15.4375 72.65625 20.34375 73.4375 \n",
"Q 25.25 74.21875 29.59375 74.21875 \n",
"Q 40.828125 74.21875 47.359375 69.109375 \n",
"Q 53.90625 64.015625 53.90625 55.328125 \n",
"Q 53.90625 49.265625 50.4375 45.09375 \n",
"Q 46.96875 40.921875 40.578125 39.3125 \n",
"z\n",
"\" id=\"DejaVuSans-51\"/>\n",
" </defs>\n",
" <g transform=\"translate(69.940625 241.5025)rotate(-90)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-51\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"xtick_2\">\n",
" <g id=\"line2d_2\">\n",
" <g>\n",
" <use style=\"stroke:#000000;stroke-width:0.8;\" x=\"103.18125\" xlink:href=\"#m1f6b1ebb34\" y=\"228.14\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_2\">\n",
" <!-- 4 -->\n",
" <defs>\n",
" <path d=\"M 37.796875 64.3125 \n",
"L 12.890625 25.390625 \n",
"L 37.796875 25.390625 \n",
"z\n",
"M 35.203125 72.90625 \n",
"L 47.609375 72.90625 \n",
"L 47.609375 25.390625 \n",
"L 58.015625 25.390625 \n",
"L 58.015625 17.1875 \n",
"L 47.609375 17.1875 \n",
"L 47.609375 0 \n",
"L 37.796875 0 \n",
"L 37.796875 17.1875 \n",
"L 4.890625 17.1875 \n",
"L 4.890625 26.703125 \n",
"z\n",
"\" id=\"DejaVuSans-52\"/>\n",
" </defs>\n",
" <g transform=\"translate(105.940625 241.5025)rotate(-90)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-52\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"xtick_3\">\n",
" <g id=\"line2d_3\">\n",
" <g>\n",
" <use style=\"stroke:#000000;stroke-width:0.8;\" x=\"139.18125\" xlink:href=\"#m1f6b1ebb34\" y=\"228.14\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_3\">\n",
" <!-- 5 -->\n",
" <defs>\n",
" <path d=\"M 10.796875 72.90625 \n",
"L 49.515625 72.90625 \n",
"L 49.515625 64.59375 \n",
"L 19.828125 64.59375 \n",
"L 19.828125 46.734375 \n",
"Q 21.96875 47.46875 24.109375 47.828125 \n",
"Q 26.265625 48.1875 28.421875 48.1875 \n",
"Q 40.625 48.1875 47.75 41.5 \n",
"Q 54.890625 34.8125 54.890625 23.390625 \n",
"Q 54.890625 11.625 47.5625 5.09375 \n",
"Q 40.234375 -1.421875 26.90625 -1.421875 \n",
"Q 22.3125 -1.421875 17.546875 -0.640625 \n",
"Q 12.796875 0.140625 7.71875 1.703125 \n",
"L 7.71875 11.625 \n",
"Q 12.109375 9.234375 16.796875 8.0625 \n",
"Q 21.484375 6.890625 26.703125 6.890625 \n",
"Q 35.15625 6.890625 40.078125 11.328125 \n",
"Q 45.015625 15.765625 45.015625 23.390625 \n",
"Q 45.015625 31 40.078125 35.4375 \n",
"Q 35.15625 39.890625 26.703125 39.890625 \n",
"Q 22.75 39.890625 18.8125 39.015625 \n",
"Q 14.890625 38.140625 10.796875 36.28125 \n",
"z\n",
"\" id=\"DejaVuSans-53\"/>\n",
" </defs>\n",
" <g transform=\"translate(141.940625 241.5025)rotate(-90)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-53\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"xtick_4\">\n",
" <g id=\"line2d_4\">\n",
" <g>\n",
" <use style=\"stroke:#000000;stroke-width:0.8;\" x=\"175.18125\" xlink:href=\"#m1f6b1ebb34\" y=\"228.14\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_4\">\n",
" <!-- 6 -->\n",
" <defs>\n",
" <path d=\"M 33.015625 40.375 \n",
"Q 26.375 40.375 22.484375 35.828125 \n",
"Q 18.609375 31.296875 18.609375 23.390625 \n",
"Q 18.609375 15.53125 22.484375 10.953125 \n",
"Q 26.375 6.390625 33.015625 6.390625 \n",
"Q 39.65625 6.390625 43.53125 10.953125 \n",
"Q 47.40625 15.53125 47.40625 23.390625 \n",
"Q 47.40625 31.296875 43.53125 35.828125 \n",
"Q 39.65625 40.375 33.015625 40.375 \n",
"z\n",
"M 52.59375 71.296875 \n",
"L 52.59375 62.3125 \n",
"Q 48.875 64.0625 45.09375 64.984375 \n",
"Q 41.3125 65.921875 37.59375 65.921875 \n",
"Q 27.828125 65.921875 22.671875 59.328125 \n",
"Q 17.53125 52.734375 16.796875 39.40625 \n",
"Q 19.671875 43.65625 24.015625 45.921875 \n",
"Q 28.375 48.1875 33.59375 48.1875 \n",
"Q 44.578125 48.1875 50.953125 41.515625 \n",
"Q 57.328125 34.859375 57.328125 23.390625 \n",
"Q 57.328125 12.15625 50.6875 5.359375 \n",
"Q 44.046875 -1.421875 33.015625 -1.421875 \n",
"Q 20.359375 -1.421875 13.671875 8.265625 \n",
"Q 6.984375 17.96875 6.984375 36.375 \n",
"Q 6.984375 53.65625 15.1875 63.9375 \n",
"Q 23.390625 74.21875 37.203125 74.21875 \n",
"Q 40.921875 74.21875 44.703125 73.484375 \n",
"Q 48.484375 72.75 52.59375 71.296875 \n",
"z\n",
"\" id=\"DejaVuSans-54\"/>\n",
" </defs>\n",
" <g transform=\"translate(177.940625 241.5025)rotate(-90)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-54\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"xtick_5\">\n",
" <g id=\"line2d_5\">\n",
" <g>\n",
" <use style=\"stroke:#000000;stroke-width:0.8;\" x=\"211.18125\" xlink:href=\"#m1f6b1ebb34\" y=\"228.14\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_5\">\n",
" <!-- 7 -->\n",
" <defs>\n",
" <path d=\"M 8.203125 72.90625 \n",
"L 55.078125 72.90625 \n",
"L 55.078125 68.703125 \n",
"L 28.609375 0 \n",
"L 18.3125 0 \n",
"L 43.21875 64.59375 \n",
"L 8.203125 64.59375 \n",
"z\n",
"\" id=\"DejaVuSans-55\"/>\n",
" </defs>\n",
" <g transform=\"translate(213.940625 241.5025)rotate(-90)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-55\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"xtick_6\">\n",
" <g id=\"line2d_6\">\n",
" <g>\n",
" <use style=\"stroke:#000000;stroke-width:0.8;\" x=\"247.18125\" xlink:href=\"#m1f6b1ebb34\" y=\"228.14\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_6\">\n",
" <!-- 8 -->\n",
" <defs>\n",
" <path d=\"M 31.78125 34.625 \n",
"Q 24.75 34.625 20.71875 30.859375 \n",
"Q 16.703125 27.09375 16.703125 20.515625 \n",
"Q 16.703125 13.921875 20.71875 10.15625 \n",
"Q 24.75 6.390625 31.78125 6.390625 \n",
"Q 38.8125 6.390625 42.859375 10.171875 \n",
"Q 46.921875 13.96875 46.921875 20.515625 \n",
"Q 46.921875 27.09375 42.890625 30.859375 \n",
"Q 38.875 34.625 31.78125 34.625 \n",
"z\n",
"M 21.921875 38.8125 \n",
"Q 15.578125 40.375 12.03125 44.71875 \n",
"Q 8.5 49.078125 8.5 55.328125 \n",
"Q 8.5 64.0625 14.71875 69.140625 \n",
"Q 20.953125 74.21875 31.78125 74.21875 \n",
"Q 42.671875 74.21875 48.875 69.140625 \n",
"Q 55.078125 64.0625 55.078125 55.328125 \n",
"Q 55.078125 49.078125 51.53125 44.71875 \n",
"Q 48 40.375 41.703125 38.8125 \n",
"Q 48.828125 37.15625 52.796875 32.3125 \n",
"Q 56.78125 27.484375 56.78125 20.515625 \n",
"Q 56.78125 9.90625 50.3125 4.234375 \n",
"Q 43.84375 -1.421875 31.78125 -1.421875 \n",
"Q 19.734375 -1.421875 13.25 4.234375 \n",
"Q 6.78125 9.90625 6.78125 20.515625 \n",
"Q 6.78125 27.484375 10.78125 32.3125 \n",
"Q 14.796875 37.15625 21.921875 38.8125 \n",
"z\n",
"M 18.3125 54.390625 \n",
"Q 18.3125 48.734375 21.84375 45.5625 \n",
"Q 25.390625 42.390625 31.78125 42.390625 \n",
"Q 38.140625 42.390625 41.71875 45.5625 \n",
"Q 45.3125 48.734375 45.3125 54.390625 \n",
"Q 45.3125 60.0625 41.71875 63.234375 \n",
"Q 38.140625 66.40625 31.78125 66.40625 \n",
"Q 25.390625 66.40625 21.84375 63.234375 \n",
"Q 18.3125 60.0625 18.3125 54.390625 \n",
"z\n",
"\" id=\"DejaVuSans-56\"/>\n",
" </defs>\n",
" <g transform=\"translate(249.940625 241.5025)rotate(-90)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-56\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"xtick_7\">\n",
" <g id=\"line2d_7\">\n",
" <g>\n",
" <use style=\"stroke:#000000;stroke-width:0.8;\" x=\"283.18125\" xlink:href=\"#m1f6b1ebb34\" y=\"228.14\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_7\">\n",
" <!-- 9 -->\n",
" <defs>\n",
" <path d=\"M 10.984375 1.515625 \n",
"L 10.984375 10.5 \n",
"Q 14.703125 8.734375 18.5 7.8125 \n",
"Q 22.3125 6.890625 25.984375 6.890625 \n",
"Q 35.75 6.890625 40.890625 13.453125 \n",
"Q 46.046875 20.015625 46.78125 33.40625 \n",
"Q 43.953125 29.203125 39.59375 26.953125 \n",
"Q 35.25 24.703125 29.984375 24.703125 \n",
"Q 19.046875 24.703125 12.671875 31.3125 \n",
"Q 6.296875 37.9375 6.296875 49.421875 \n",
"Q 6.296875 60.640625 12.9375 67.421875 \n",
"Q 19.578125 74.21875 30.609375 74.21875 \n",
"Q 43.265625 74.21875 49.921875 64.515625 \n",
"Q 56.59375 54.828125 56.59375 36.375 \n",
"Q 56.59375 19.140625 48.40625 8.859375 \n",
"Q 40.234375 -1.421875 26.421875 -1.421875 \n",
"Q 22.703125 -1.421875 18.890625 -0.6875 \n",
"Q 15.09375 0.046875 10.984375 1.515625 \n",
"z\n",
"M 30.609375 32.421875 \n",
"Q 37.25 32.421875 41.125 36.953125 \n",
"Q 45.015625 41.5 45.015625 49.421875 \n",
"Q 45.015625 57.28125 41.125 61.84375 \n",
"Q 37.25 66.40625 30.609375 66.40625 \n",
"Q 23.96875 66.40625 20.09375 61.84375 \n",
"Q 16.21875 57.28125 16.21875 49.421875 \n",
"Q 16.21875 41.5 20.09375 36.953125 \n",
"Q 23.96875 32.421875 30.609375 32.421875 \n",
"z\n",
"\" id=\"DejaVuSans-57\"/>\n",
" </defs>\n",
" <g transform=\"translate(285.940625 241.5025)rotate(-90)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-57\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"xtick_8\">\n",
" <g id=\"line2d_8\">\n",
" <g>\n",
" <use style=\"stroke:#000000;stroke-width:0.8;\" x=\"319.18125\" xlink:href=\"#m1f6b1ebb34\" y=\"228.14\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_8\">\n",
" <!-- 10 -->\n",
" <defs>\n",
" <path d=\"M 12.40625 8.296875 \n",
"L 28.515625 8.296875 \n",
"L 28.515625 63.921875 \n",
"L 10.984375 60.40625 \n",
"L 10.984375 69.390625 \n",
"L 28.421875 72.90625 \n",
"L 38.28125 72.90625 \n",
"L 38.28125 8.296875 \n",
"L 54.390625 8.296875 \n",
"L 54.390625 0 \n",
"L 12.40625 0 \n",
"z\n",
"\" id=\"DejaVuSans-49\"/>\n",
" <path d=\"M 31.78125 66.40625 \n",
"Q 24.171875 66.40625 20.328125 58.90625 \n",
"Q 16.5 51.421875 16.5 36.375 \n",
"Q 16.5 21.390625 20.328125 13.890625 \n",
"Q 24.171875 6.390625 31.78125 6.390625 \n",
"Q 39.453125 6.390625 43.28125 13.890625 \n",
"Q 47.125 21.390625 47.125 36.375 \n",
"Q 47.125 51.421875 43.28125 58.90625 \n",
"Q 39.453125 66.40625 31.78125 66.40625 \n",
"z\n",
"M 31.78125 74.21875 \n",
"Q 44.046875 74.21875 50.515625 64.515625 \n",
"Q 56.984375 54.828125 56.984375 36.375 \n",
"Q 56.984375 17.96875 50.515625 8.265625 \n",
"Q 44.046875 -1.421875 31.78125 -1.421875 \n",
"Q 19.53125 -1.421875 13.0625 8.265625 \n",
"Q 6.59375 17.96875 6.59375 36.375 \n",
"Q 6.59375 54.828125 13.0625 64.515625 \n",
"Q 19.53125 74.21875 31.78125 74.21875 \n",
"z\n",
"\" id=\"DejaVuSans-48\"/>\n",
" </defs>\n",
" <g transform=\"translate(321.940625 247.865)rotate(-90)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-49\"/>\n",
" <use x=\"63.623047\" xlink:href=\"#DejaVuSans-48\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"xtick_9\">\n",
" <g id=\"line2d_9\">\n",
" <g>\n",
" <use style=\"stroke:#000000;stroke-width:0.8;\" x=\"355.18125\" xlink:href=\"#m1f6b1ebb34\" y=\"228.14\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_9\">\n",
" <!-- 11 -->\n",
" <g transform=\"translate(357.940625 247.865)rotate(-90)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-49\"/>\n",
" <use x=\"63.623047\" xlink:href=\"#DejaVuSans-49\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_10\">\n",
" <!-- Checkin index -->\n",
" <defs>\n",
" <path d=\"M 64.40625 67.28125 \n",
"L 64.40625 56.890625 \n",
"Q 59.421875 61.53125 53.78125 63.8125 \n",
"Q 48.140625 66.109375 41.796875 66.109375 \n",
"Q 29.296875 66.109375 22.65625 58.46875 \n",
"Q 16.015625 50.828125 16.015625 36.375 \n",
"Q 16.015625 21.96875 22.65625 14.328125 \n",
"Q 29.296875 6.6875 41.796875 6.6875 \n",
"Q 48.140625 6.6875 53.78125 8.984375 \n",
"Q 59.421875 11.28125 64.40625 15.921875 \n",
"L 64.40625 5.609375 \n",
"Q 59.234375 2.09375 53.4375 0.328125 \n",
"Q 47.65625 -1.421875 41.21875 -1.421875 \n",
"Q 24.65625 -1.421875 15.125 8.703125 \n",
"Q 5.609375 18.84375 5.609375 36.375 \n",
"Q 5.609375 53.953125 15.125 64.078125 \n",
"Q 24.65625 74.21875 41.21875 74.21875 \n",
"Q 47.75 74.21875 53.53125 72.484375 \n",
"Q 59.328125 70.75 64.40625 67.28125 \n",
"z\n",
"\" id=\"DejaVuSans-67\"/>\n",
" <path d=\"M 54.890625 33.015625 \n",
"L 54.890625 0 \n",
"L 45.90625 0 \n",
"L 45.90625 32.71875 \n",
"Q 45.90625 40.484375 42.875 44.328125 \n",
"Q 39.84375 48.1875 33.796875 48.1875 \n",
"Q 26.515625 48.1875 22.3125 43.546875 \n",
"Q 18.109375 38.921875 18.109375 30.90625 \n",
"L 18.109375 0 \n",
"L 9.078125 0 \n",
"L 9.078125 75.984375 \n",
"L 18.109375 75.984375 \n",
"L 18.109375 46.1875 \n",
"Q 21.34375 51.125 25.703125 53.5625 \n",
"Q 30.078125 56 35.796875 56 \n",
"Q 45.21875 56 50.046875 50.171875 \n",
"Q 54.890625 44.34375 54.890625 33.015625 \n",
"z\n",
"\" id=\"DejaVuSans-104\"/>\n",
" <path d=\"M 56.203125 29.59375 \n",
"L 56.203125 25.203125 \n",
"L 14.890625 25.203125 \n",
"Q 15.484375 15.921875 20.484375 11.0625 \n",
"Q 25.484375 6.203125 34.421875 6.203125 \n",
"Q 39.59375 6.203125 44.453125 7.46875 \n",
"Q 49.3125 8.734375 54.109375 11.28125 \n",
"L 54.109375 2.78125 \n",
"Q 49.265625 0.734375 44.1875 -0.34375 \n",
"Q 39.109375 -1.421875 33.890625 -1.421875 \n",
"Q 20.796875 -1.421875 13.15625 6.1875 \n",
"Q 5.515625 13.8125 5.515625 26.8125 \n",
"Q 5.515625 40.234375 12.765625 48.109375 \n",
"Q 20.015625 56 32.328125 56 \n",
"Q 43.359375 56 49.78125 48.890625 \n",
"Q 56.203125 41.796875 56.203125 29.59375 \n",
"z\n",
"M 47.21875 32.234375 \n",
"Q 47.125 39.59375 43.09375 43.984375 \n",
"Q 39.0625 48.390625 32.421875 48.390625 \n",
"Q 24.90625 48.390625 20.390625 44.140625 \n",
"Q 15.875 39.890625 15.1875 32.171875 \n",
"z\n",
"\" id=\"DejaVuSans-101\"/>\n",
" <path d=\"M 48.78125 52.59375 \n",
"L 48.78125 44.1875 \n",
"Q 44.96875 46.296875 41.140625 47.34375 \n",
"Q 37.3125 48.390625 33.40625 48.390625 \n",
"Q 24.65625 48.390625 19.8125 42.84375 \n",
"Q 14.984375 37.3125 14.984375 27.296875 \n",
"Q 14.984375 17.28125 19.8125 11.734375 \n",
"Q 24.65625 6.203125 33.40625 6.203125 \n",
"Q 37.3125 6.203125 41.140625 7.25 \n",
"Q 44.96875 8.296875 48.78125 10.40625 \n",
"L 48.78125 2.09375 \n",
"Q 45.015625 0.34375 40.984375 -0.53125 \n",
"Q 36.96875 -1.421875 32.421875 -1.421875 \n",
"Q 20.0625 -1.421875 12.78125 6.34375 \n",
"Q 5.515625 14.109375 5.515625 27.296875 \n",
"Q 5.515625 40.671875 12.859375 48.328125 \n",
"Q 20.21875 56 33.015625 56 \n",
"Q 37.15625 56 41.109375 55.140625 \n",
"Q 45.0625 54.296875 48.78125 52.59375 \n",
"z\n",
"\" id=\"DejaVuSans-99\"/>\n",
" <path d=\"M 9.078125 75.984375 \n",
"L 18.109375 75.984375 \n",
"L 18.109375 31.109375 \n",
"L 44.921875 54.6875 \n",
"L 56.390625 54.6875 \n",
"L 27.390625 29.109375 \n",
"L 57.625 0 \n",
"L 45.90625 0 \n",
"L 18.109375 26.703125 \n",
"L 18.109375 0 \n",
"L 9.078125 0 \n",
"z\n",
"\" id=\"DejaVuSans-107\"/>\n",
" <path d=\"M 9.421875 54.6875 \n",
"L 18.40625 54.6875 \n",
"L 18.40625 0 \n",
"L 9.421875 0 \n",
"z\n",
"M 9.421875 75.984375 \n",
"L 18.40625 75.984375 \n",
"L 18.40625 64.59375 \n",
"L 9.421875 64.59375 \n",
"z\n",
"\" id=\"DejaVuSans-105\"/>\n",
" <path d=\"M 54.890625 33.015625 \n",
"L 54.890625 0 \n",
"L 45.90625 0 \n",
"L 45.90625 32.71875 \n",
"Q 45.90625 40.484375 42.875 44.328125 \n",
"Q 39.84375 48.1875 33.796875 48.1875 \n",
"Q 26.515625 48.1875 22.3125 43.546875 \n",
"Q 18.109375 38.921875 18.109375 30.90625 \n",
"L 18.109375 0 \n",
"L 9.078125 0 \n",
"L 9.078125 54.6875 \n",
"L 18.109375 54.6875 \n",
"L 18.109375 46.1875 \n",
"Q 21.34375 51.125 25.703125 53.5625 \n",
"Q 30.078125 56 35.796875 56 \n",
"Q 45.21875 56 50.046875 50.171875 \n",
"Q 54.890625 44.34375 54.890625 33.015625 \n",
"z\n",
"\" id=\"DejaVuSans-110\"/>\n",
" <path id=\"DejaVuSans-32\"/>\n",
" <path d=\"M 45.40625 46.390625 \n",
"L 45.40625 75.984375 \n",
"L 54.390625 75.984375 \n",
"L 54.390625 0 \n",
"L 45.40625 0 \n",
"L 45.40625 8.203125 \n",
"Q 42.578125 3.328125 38.25 0.953125 \n",
"Q 33.9375 -1.421875 27.875 -1.421875 \n",
"Q 17.96875 -1.421875 11.734375 6.484375 \n",
"Q 5.515625 14.40625 5.515625 27.296875 \n",
"Q 5.515625 40.1875 11.734375 48.09375 \n",
"Q 17.96875 56 27.875 56 \n",
"Q 33.9375 56 38.25 53.625 \n",
"Q 42.578125 51.265625 45.40625 46.390625 \n",
"z\n",
"M 14.796875 27.296875 \n",
"Q 14.796875 17.390625 18.875 11.75 \n",
"Q 22.953125 6.109375 30.078125 6.109375 \n",
"Q 37.203125 6.109375 41.296875 11.75 \n",
"Q 45.40625 17.390625 45.40625 27.296875 \n",
"Q 45.40625 37.203125 41.296875 42.84375 \n",
"Q 37.203125 48.484375 30.078125 48.484375 \n",
"Q 22.953125 48.484375 18.875 42.84375 \n",
"Q 14.796875 37.203125 14.796875 27.296875 \n",
"z\n",
"\" id=\"DejaVuSans-100\"/>\n",
" <path d=\"M 54.890625 54.6875 \n",
"L 35.109375 28.078125 \n",
"L 55.90625 0 \n",
"L 45.3125 0 \n",
"L 29.390625 21.484375 \n",
"L 13.484375 0 \n",
"L 2.875 0 \n",
"L 24.125 28.609375 \n",
"L 4.6875 54.6875 \n",
"L 15.28125 54.6875 \n",
"L 29.78125 35.203125 \n",
"L 44.28125 54.6875 \n",
"z\n",
"\" id=\"DejaVuSans-120\"/>\n",
" </defs>\n",
" <g transform=\"translate(175.885938 259.463437)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-67\"/>\n",
" <use x=\"69.824219\" xlink:href=\"#DejaVuSans-104\"/>\n",
" <use x=\"133.203125\" xlink:href=\"#DejaVuSans-101\"/>\n",
" <use x=\"194.726562\" xlink:href=\"#DejaVuSans-99\"/>\n",
" <use x=\"249.707031\" xlink:href=\"#DejaVuSans-107\"/>\n",
" <use x=\"307.617188\" xlink:href=\"#DejaVuSans-105\"/>\n",
" <use x=\"335.400391\" xlink:href=\"#DejaVuSans-110\"/>\n",
" <use x=\"398.779297\" xlink:href=\"#DejaVuSans-32\"/>\n",
" <use x=\"430.566406\" xlink:href=\"#DejaVuSans-105\"/>\n",
" <use x=\"458.349609\" xlink:href=\"#DejaVuSans-110\"/>\n",
" <use x=\"521.728516\" xlink:href=\"#DejaVuSans-100\"/>\n",
" <use x=\"585.205078\" xlink:href=\"#DejaVuSans-101\"/>\n",
" <use x=\"646.712891\" xlink:href=\"#DejaVuSans-120\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"matplotlib.axis_2\">\n",
" <g id=\"ytick_1\">\n",
" <g id=\"line2d_10\">\n",
" <defs>\n",
" <path d=\"M 0 0 \n",
"L -3.5 0 \n",
"\" id=\"mc174bd9713\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n",
" </defs>\n",
" <g>\n",
" <use style=\"stroke:#000000;stroke-width:0.8;\" x=\"43.78125\" xlink:href=\"#mc174bd9713\" y=\"228.14\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_11\">\n",
" <!-- 0.0 -->\n",
" <defs>\n",
" <path d=\"M 10.6875 12.40625 \n",
"L 21 12.40625 \n",
"L 21 0 \n",
"L 10.6875 0 \n",
"z\n",
"\" id=\"DejaVuSans-46\"/>\n",
" </defs>\n",
" <g transform=\"translate(20.878125 231.939219)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-48\"/>\n",
" <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
" <use x=\"95.410156\" xlink:href=\"#DejaVuSans-48\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"ytick_2\">\n",
" <g id=\"line2d_11\">\n",
" <g>\n",
" <use style=\"stroke:#000000;stroke-width:0.8;\" x=\"43.78125\" xlink:href=\"#mc174bd9713\" y=\"188.577356\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_12\">\n",
" <!-- 0.2 -->\n",
" <defs>\n",
" <path d=\"M 19.1875 8.296875 \n",
"L 53.609375 8.296875 \n",
"L 53.609375 0 \n",
"L 7.328125 0 \n",
"L 7.328125 8.296875 \n",
"Q 12.9375 14.109375 22.625 23.890625 \n",
"Q 32.328125 33.6875 34.8125 36.53125 \n",
"Q 39.546875 41.84375 41.421875 45.53125 \n",
"Q 43.3125 49.21875 43.3125 52.78125 \n",
"Q 43.3125 58.59375 39.234375 62.25 \n",
"Q 35.15625 65.921875 28.609375 65.921875 \n",
"Q 23.96875 65.921875 18.8125 64.3125 \n",
"Q 13.671875 62.703125 7.8125 59.421875 \n",
"L 7.8125 69.390625 \n",
"Q 13.765625 71.78125 18.9375 73 \n",
"Q 24.125 74.21875 28.421875 74.21875 \n",
"Q 39.75 74.21875 46.484375 68.546875 \n",
"Q 53.21875 62.890625 53.21875 53.421875 \n",
"Q 53.21875 48.921875 51.53125 44.890625 \n",
"Q 49.859375 40.875 45.40625 35.40625 \n",
"Q 44.1875 33.984375 37.640625 27.21875 \n",
"Q 31.109375 20.453125 19.1875 8.296875 \n",
"z\n",
"\" id=\"DejaVuSans-50\"/>\n",
" </defs>\n",
" <g transform=\"translate(20.878125 192.376575)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-48\"/>\n",
" <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
" <use x=\"95.410156\" xlink:href=\"#DejaVuSans-50\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"ytick_3\">\n",
" <g id=\"line2d_12\">\n",
" <g>\n",
" <use style=\"stroke:#000000;stroke-width:0.8;\" x=\"43.78125\" xlink:href=\"#mc174bd9713\" y=\"149.014712\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_13\">\n",
" <!-- 0.4 -->\n",
" <g transform=\"translate(20.878125 152.813931)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-48\"/>\n",
" <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
" <use x=\"95.410156\" xlink:href=\"#DejaVuSans-52\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"ytick_4\">\n",
" <g id=\"line2d_13\">\n",
" <g>\n",
" <use style=\"stroke:#000000;stroke-width:0.8;\" x=\"43.78125\" xlink:href=\"#mc174bd9713\" y=\"109.452068\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_14\">\n",
" <!-- 0.6 -->\n",
" <g transform=\"translate(20.878125 113.251287)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-48\"/>\n",
" <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
" <use x=\"95.410156\" xlink:href=\"#DejaVuSans-54\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"ytick_5\">\n",
" <g id=\"line2d_14\">\n",
" <g>\n",
" <use style=\"stroke:#000000;stroke-width:0.8;\" x=\"43.78125\" xlink:href=\"#mc174bd9713\" y=\"69.889424\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_15\">\n",
" <!-- 0.8 -->\n",
" <g transform=\"translate(20.878125 73.688643)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-48\"/>\n",
" <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
" <use x=\"95.410156\" xlink:href=\"#DejaVuSans-56\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"ytick_6\">\n",
" <g id=\"line2d_15\">\n",
" <g>\n",
" <use style=\"stroke:#000000;stroke-width:0.8;\" x=\"43.78125\" xlink:href=\"#mc174bd9713\" y=\"30.32678\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_16\">\n",
" <!-- 1.0 -->\n",
" <g transform=\"translate(20.878125 34.125999)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-49\"/>\n",
" <use x=\"63.623047\" xlink:href=\"#DejaVuSans-46\"/>\n",
" <use x=\"95.410156\" xlink:href=\"#DejaVuSans-48\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"text_17\">\n",
" <!-- Repo size (MiB) -->\n",
" <defs>\n",
" <path d=\"M 44.390625 34.1875 \n",
"Q 47.5625 33.109375 50.5625 29.59375 \n",
"Q 53.5625 26.078125 56.59375 19.921875 \n",
"L 66.609375 0 \n",
"L 56 0 \n",
"L 46.6875 18.703125 \n",
"Q 43.0625 26.03125 39.671875 28.421875 \n",
"Q 36.28125 30.8125 30.421875 30.8125 \n",
"L 19.671875 30.8125 \n",
"L 19.671875 0 \n",
"L 9.8125 0 \n",
"L 9.8125 72.90625 \n",
"L 32.078125 72.90625 \n",
"Q 44.578125 72.90625 50.734375 67.671875 \n",
"Q 56.890625 62.453125 56.890625 51.90625 \n",
"Q 56.890625 45.015625 53.6875 40.46875 \n",
"Q 50.484375 35.9375 44.390625 34.1875 \n",
"z\n",
"M 19.671875 64.796875 \n",
"L 19.671875 38.921875 \n",
"L 32.078125 38.921875 \n",
"Q 39.203125 38.921875 42.84375 42.21875 \n",
"Q 46.484375 45.515625 46.484375 51.90625 \n",
"Q 46.484375 58.296875 42.84375 61.546875 \n",
"Q 39.203125 64.796875 32.078125 64.796875 \n",
"z\n",
"\" id=\"DejaVuSans-82\"/>\n",
" <path d=\"M 18.109375 8.203125 \n",
"L 18.109375 -20.796875 \n",
"L 9.078125 -20.796875 \n",
"L 9.078125 54.6875 \n",
"L 18.109375 54.6875 \n",
"L 18.109375 46.390625 \n",
"Q 20.953125 51.265625 25.265625 53.625 \n",
"Q 29.59375 56 35.59375 56 \n",
"Q 45.5625 56 51.78125 48.09375 \n",
"Q 58.015625 40.1875 58.015625 27.296875 \n",
"Q 58.015625 14.40625 51.78125 6.484375 \n",
"Q 45.5625 -1.421875 35.59375 -1.421875 \n",
"Q 29.59375 -1.421875 25.265625 0.953125 \n",
"Q 20.953125 3.328125 18.109375 8.203125 \n",
"z\n",
"M 48.6875 27.296875 \n",
"Q 48.6875 37.203125 44.609375 42.84375 \n",
"Q 40.53125 48.484375 33.40625 48.484375 \n",
"Q 26.265625 48.484375 22.1875 42.84375 \n",
"Q 18.109375 37.203125 18.109375 27.296875 \n",
"Q 18.109375 17.390625 22.1875 11.75 \n",
"Q 26.265625 6.109375 33.40625 6.109375 \n",
"Q 40.53125 6.109375 44.609375 11.75 \n",
"Q 48.6875 17.390625 48.6875 27.296875 \n",
"z\n",
"\" id=\"DejaVuSans-112\"/>\n",
" <path d=\"M 30.609375 48.390625 \n",
"Q 23.390625 48.390625 19.1875 42.75 \n",
"Q 14.984375 37.109375 14.984375 27.296875 \n",
"Q 14.984375 17.484375 19.15625 11.84375 \n",
"Q 23.34375 6.203125 30.609375 6.203125 \n",
"Q 37.796875 6.203125 41.984375 11.859375 \n",
"Q 46.1875 17.53125 46.1875 27.296875 \n",
"Q 46.1875 37.015625 41.984375 42.703125 \n",
"Q 37.796875 48.390625 30.609375 48.390625 \n",
"z\n",
"M 30.609375 56 \n",
"Q 42.328125 56 49.015625 48.375 \n",
"Q 55.71875 40.765625 55.71875 27.296875 \n",
"Q 55.71875 13.875 49.015625 6.21875 \n",
"Q 42.328125 -1.421875 30.609375 -1.421875 \n",
"Q 18.84375 -1.421875 12.171875 6.21875 \n",
"Q 5.515625 13.875 5.515625 27.296875 \n",
"Q 5.515625 40.765625 12.171875 48.375 \n",
"Q 18.84375 56 30.609375 56 \n",
"z\n",
"\" id=\"DejaVuSans-111\"/>\n",
" <path d=\"M 44.28125 53.078125 \n",
"L 44.28125 44.578125 \n",
"Q 40.484375 46.53125 36.375 47.5 \n",
"Q 32.28125 48.484375 27.875 48.484375 \n",
"Q 21.1875 48.484375 17.84375 46.4375 \n",
"Q 14.5 44.390625 14.5 40.28125 \n",
"Q 14.5 37.15625 16.890625 35.375 \n",
"Q 19.28125 33.59375 26.515625 31.984375 \n",
"L 29.59375 31.296875 \n",
"Q 39.15625 29.25 43.1875 25.515625 \n",
"Q 47.21875 21.78125 47.21875 15.09375 \n",
"Q 47.21875 7.46875 41.1875 3.015625 \n",
"Q 35.15625 -1.421875 24.609375 -1.421875 \n",
"Q 20.21875 -1.421875 15.453125 -0.5625 \n",
"Q 10.6875 0.296875 5.421875 2 \n",
"L 5.421875 11.28125 \n",
"Q 10.40625 8.6875 15.234375 7.390625 \n",
"Q 20.0625 6.109375 24.8125 6.109375 \n",
"Q 31.15625 6.109375 34.5625 8.28125 \n",
"Q 37.984375 10.453125 37.984375 14.40625 \n",
"Q 37.984375 18.0625 35.515625 20.015625 \n",
"Q 33.0625 21.96875 24.703125 23.78125 \n",
"L 21.578125 24.515625 \n",
"Q 13.234375 26.265625 9.515625 29.90625 \n",
"Q 5.8125 33.546875 5.8125 39.890625 \n",
"Q 5.8125 47.609375 11.28125 51.796875 \n",
"Q 16.75 56 26.8125 56 \n",
"Q 31.78125 56 36.171875 55.265625 \n",
"Q 40.578125 54.546875 44.28125 53.078125 \n",
"z\n",
"\" id=\"DejaVuSans-115\"/>\n",
" <path d=\"M 5.515625 54.6875 \n",
"L 48.1875 54.6875 \n",
"L 48.1875 46.484375 \n",
"L 14.40625 7.171875 \n",
"L 48.1875 7.171875 \n",
"L 48.1875 0 \n",
"L 4.296875 0 \n",
"L 4.296875 8.203125 \n",
"L 38.09375 47.515625 \n",
"L 5.515625 47.515625 \n",
"z\n",
"\" id=\"DejaVuSans-122\"/>\n",
" <path d=\"M 31 75.875 \n",
"Q 24.46875 64.65625 21.28125 53.65625 \n",
"Q 18.109375 42.671875 18.109375 31.390625 \n",
"Q 18.109375 20.125 21.3125 9.0625 \n",
"Q 24.515625 -2 31 -13.1875 \n",
"L 23.1875 -13.1875 \n",
"Q 15.875 -1.703125 12.234375 9.375 \n",
"Q 8.59375 20.453125 8.59375 31.390625 \n",
"Q 8.59375 42.28125 12.203125 53.3125 \n",
"Q 15.828125 64.359375 23.1875 75.875 \n",
"z\n",
"\" id=\"DejaVuSans-40\"/>\n",
" <path d=\"M 9.8125 72.90625 \n",
"L 24.515625 72.90625 \n",
"L 43.109375 23.296875 \n",
"L 61.8125 72.90625 \n",
"L 76.515625 72.90625 \n",
"L 76.515625 0 \n",
"L 66.890625 0 \n",
"L 66.890625 64.015625 \n",
"L 48.09375 14.015625 \n",
"L 38.1875 14.015625 \n",
"L 19.390625 64.015625 \n",
"L 19.390625 0 \n",
"L 9.8125 0 \n",
"z\n",
"\" id=\"DejaVuSans-77\"/>\n",
" <path d=\"M 19.671875 34.8125 \n",
"L 19.671875 8.109375 \n",
"L 35.5 8.109375 \n",
"Q 43.453125 8.109375 47.28125 11.40625 \n",
"Q 51.125 14.703125 51.125 21.484375 \n",
"Q 51.125 28.328125 47.28125 31.5625 \n",
"Q 43.453125 34.8125 35.5 34.8125 \n",
"z\n",
"M 19.671875 64.796875 \n",
"L 19.671875 42.828125 \n",
"L 34.28125 42.828125 \n",
"Q 41.5 42.828125 45.03125 45.53125 \n",
"Q 48.578125 48.25 48.578125 53.8125 \n",
"Q 48.578125 59.328125 45.03125 62.0625 \n",
"Q 41.5 64.796875 34.28125 64.796875 \n",
"z\n",
"M 9.8125 72.90625 \n",
"L 35.015625 72.90625 \n",
"Q 46.296875 72.90625 52.390625 68.21875 \n",
"Q 58.5 63.53125 58.5 54.890625 \n",
"Q 58.5 48.1875 55.375 44.234375 \n",
"Q 52.25 40.28125 46.1875 39.3125 \n",
"Q 53.46875 37.75 57.5 32.78125 \n",
"Q 61.53125 27.828125 61.53125 20.40625 \n",
"Q 61.53125 10.640625 54.890625 5.3125 \n",
"Q 48.25 0 35.984375 0 \n",
"L 9.8125 0 \n",
"z\n",
"\" id=\"DejaVuSans-66\"/>\n",
" <path d=\"M 8.015625 75.875 \n",
"L 15.828125 75.875 \n",
"Q 23.140625 64.359375 26.78125 53.3125 \n",
"Q 30.421875 42.28125 30.421875 31.390625 \n",
"Q 30.421875 20.453125 26.78125 9.375 \n",
"Q 23.140625 -1.703125 15.828125 -13.1875 \n",
"L 8.015625 -13.1875 \n",
"Q 14.5 -2 17.703125 9.0625 \n",
"Q 20.90625 20.125 20.90625 31.390625 \n",
"Q 20.90625 42.671875 17.703125 53.65625 \n",
"Q 14.5 64.65625 8.015625 75.875 \n",
"z\n",
"\" id=\"DejaVuSans-41\"/>\n",
" </defs>\n",
" <g transform=\"translate(14.798438 158.109062)rotate(-90)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-82\"/>\n",
" <use x=\"69.419922\" xlink:href=\"#DejaVuSans-101\"/>\n",
" <use x=\"130.943359\" xlink:href=\"#DejaVuSans-112\"/>\n",
" <use x=\"194.419922\" xlink:href=\"#DejaVuSans-111\"/>\n",
" <use x=\"255.601562\" xlink:href=\"#DejaVuSans-32\"/>\n",
" <use x=\"287.388672\" xlink:href=\"#DejaVuSans-115\"/>\n",
" <use x=\"339.488281\" xlink:href=\"#DejaVuSans-105\"/>\n",
" <use x=\"367.271484\" xlink:href=\"#DejaVuSans-122\"/>\n",
" <use x=\"419.761719\" xlink:href=\"#DejaVuSans-101\"/>\n",
" <use x=\"481.285156\" xlink:href=\"#DejaVuSans-32\"/>\n",
" <use x=\"513.072266\" xlink:href=\"#DejaVuSans-40\"/>\n",
" <use x=\"552.085938\" xlink:href=\"#DejaVuSans-77\"/>\n",
" <use x=\"638.365234\" xlink:href=\"#DejaVuSans-105\"/>\n",
" <use x=\"666.148438\" xlink:href=\"#DejaVuSans-66\"/>\n",
" <use x=\"734.751953\" xlink:href=\"#DejaVuSans-41\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <g id=\"patch_39\">\n",
" <path d=\"M 43.78125 228.14 \n",
"L 43.78125 10.7 \n",
"\" style=\"fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;\"/>\n",
" </g>\n",
" <g id=\"patch_40\">\n",
" <path d=\"M 378.58125 228.14 \n",
"L 378.58125 10.7 \n",
"\" style=\"fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;\"/>\n",
" </g>\n",
" <g id=\"patch_41\">\n",
" <path d=\"M 43.78125 228.14 \n",
"L 378.58125 228.14 \n",
"\" style=\"fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;\"/>\n",
" </g>\n",
" <g id=\"patch_42\">\n",
" <path d=\"M 43.78125 10.7 \n",
"L 378.58125 10.7 \n",
"\" style=\"fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;\"/>\n",
" </g>\n",
" <g id=\"legend_1\">\n",
" <g id=\"patch_43\">\n",
" <path d=\"M 50.78125 77.4125 \n",
"L 105.828125 77.4125 \n",
"Q 107.828125 77.4125 107.828125 75.4125 \n",
"L 107.828125 17.7 \n",
"Q 107.828125 15.7 105.828125 15.7 \n",
"L 50.78125 15.7 \n",
"Q 48.78125 15.7 48.78125 17.7 \n",
"L 48.78125 75.4125 \n",
"Q 48.78125 77.4125 50.78125 77.4125 \n",
"z\n",
"\" style=\"fill:#ffffff;opacity:0.8;stroke:#cccccc;stroke-linejoin:miter;\"/>\n",
" </g>\n",
" <g id=\"patch_44\">\n",
" <path d=\"M 52.78125 27.298437 \n",
"L 72.78125 27.298437 \n",
"L 72.78125 20.298437 \n",
"L 52.78125 20.298437 \n",
"z\n",
"\" style=\"fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"text_18\">\n",
" <!-- JPEG -->\n",
" <defs>\n",
" <path d=\"M 9.8125 72.90625 \n",
"L 19.671875 72.90625 \n",
"L 19.671875 5.078125 \n",
"Q 19.671875 -8.109375 14.671875 -14.0625 \n",
"Q 9.671875 -20.015625 -1.421875 -20.015625 \n",
"L -5.171875 -20.015625 \n",
"L -5.171875 -11.71875 \n",
"L -2.09375 -11.71875 \n",
"Q 4.4375 -11.71875 7.125 -8.046875 \n",
"Q 9.8125 -4.390625 9.8125 5.078125 \n",
"z\n",
"\" id=\"DejaVuSans-74\"/>\n",
" <path d=\"M 19.671875 64.796875 \n",
"L 19.671875 37.40625 \n",
"L 32.078125 37.40625 \n",
"Q 38.96875 37.40625 42.71875 40.96875 \n",
"Q 46.484375 44.53125 46.484375 51.125 \n",
"Q 46.484375 57.671875 42.71875 61.234375 \n",
"Q 38.96875 64.796875 32.078125 64.796875 \n",
"z\n",
"M 9.8125 72.90625 \n",
"L 32.078125 72.90625 \n",
"Q 44.34375 72.90625 50.609375 67.359375 \n",
"Q 56.890625 61.8125 56.890625 51.125 \n",
"Q 56.890625 40.328125 50.609375 34.8125 \n",
"Q 44.34375 29.296875 32.078125 29.296875 \n",
"L 19.671875 29.296875 \n",
"L 19.671875 0 \n",
"L 9.8125 0 \n",
"z\n",
"\" id=\"DejaVuSans-80\"/>\n",
" <path d=\"M 9.8125 72.90625 \n",
"L 55.90625 72.90625 \n",
"L 55.90625 64.59375 \n",
"L 19.671875 64.59375 \n",
"L 19.671875 43.015625 \n",
"L 54.390625 43.015625 \n",
"L 54.390625 34.71875 \n",
"L 19.671875 34.71875 \n",
"L 19.671875 8.296875 \n",
"L 56.78125 8.296875 \n",
"L 56.78125 0 \n",
"L 9.8125 0 \n",
"z\n",
"\" id=\"DejaVuSans-69\"/>\n",
" <path d=\"M 59.515625 10.40625 \n",
"L 59.515625 29.984375 \n",
"L 43.40625 29.984375 \n",
"L 43.40625 38.09375 \n",
"L 69.28125 38.09375 \n",
"L 69.28125 6.78125 \n",
"Q 63.578125 2.734375 56.6875 0.65625 \n",
"Q 49.8125 -1.421875 42 -1.421875 \n",
"Q 24.90625 -1.421875 15.25 8.5625 \n",
"Q 5.609375 18.5625 5.609375 36.375 \n",
"Q 5.609375 54.25 15.25 64.234375 \n",
"Q 24.90625 74.21875 42 74.21875 \n",
"Q 49.125 74.21875 55.546875 72.453125 \n",
"Q 61.96875 70.703125 67.390625 67.28125 \n",
"L 67.390625 56.78125 \n",
"Q 61.921875 61.421875 55.765625 63.765625 \n",
"Q 49.609375 66.109375 42.828125 66.109375 \n",
"Q 29.4375 66.109375 22.71875 58.640625 \n",
"Q 16.015625 51.171875 16.015625 36.375 \n",
"Q 16.015625 21.625 22.71875 14.15625 \n",
"Q 29.4375 6.6875 42.828125 6.6875 \n",
"Q 48.046875 6.6875 52.140625 7.59375 \n",
"Q 56.25 8.5 59.515625 10.40625 \n",
"z\n",
"\" id=\"DejaVuSans-71\"/>\n",
" </defs>\n",
" <g transform=\"translate(80.78125 27.298437)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-74\"/>\n",
" <use x=\"29.492188\" xlink:href=\"#DejaVuSans-80\"/>\n",
" <use x=\"89.794922\" xlink:href=\"#DejaVuSans-69\"/>\n",
" <use x=\"152.978516\" xlink:href=\"#DejaVuSans-71\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"patch_45\">\n",
" <path d=\"M 52.78125 41.976562 \n",
"L 72.78125 41.976562 \n",
"L 72.78125 34.976562 \n",
"L 52.78125 34.976562 \n",
"z\n",
"\" style=\"fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"text_19\">\n",
" <!-- BMP -->\n",
" <g transform=\"translate(80.78125 41.976562)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-66\"/>\n",
" <use x=\"68.603516\" xlink:href=\"#DejaVuSans-77\"/>\n",
" <use x=\"154.882812\" xlink:href=\"#DejaVuSans-80\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"patch_46\">\n",
" <path d=\"M 52.78125 56.654687 \n",
"L 72.78125 56.654687 \n",
"L 72.78125 49.654687 \n",
"L 52.78125 49.654687 \n",
"z\n",
"\" style=\"fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"text_20\">\n",
" <!-- TIFF -->\n",
" <defs>\n",
" <path d=\"M -0.296875 72.90625 \n",
"L 61.375 72.90625 \n",
"L 61.375 64.59375 \n",
"L 35.5 64.59375 \n",
"L 35.5 0 \n",
"L 25.59375 0 \n",
"L 25.59375 64.59375 \n",
"L -0.296875 64.59375 \n",
"z\n",
"\" id=\"DejaVuSans-84\"/>\n",
" <path d=\"M 9.8125 72.90625 \n",
"L 19.671875 72.90625 \n",
"L 19.671875 0 \n",
"L 9.8125 0 \n",
"z\n",
"\" id=\"DejaVuSans-73\"/>\n",
" <path d=\"M 9.8125 72.90625 \n",
"L 51.703125 72.90625 \n",
"L 51.703125 64.59375 \n",
"L 19.671875 64.59375 \n",
"L 19.671875 43.109375 \n",
"L 48.578125 43.109375 \n",
"L 48.578125 34.8125 \n",
"L 19.671875 34.8125 \n",
"L 19.671875 0 \n",
"L 9.8125 0 \n",
"z\n",
"\" id=\"DejaVuSans-70\"/>\n",
" </defs>\n",
" <g transform=\"translate(80.78125 56.654687)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-84\"/>\n",
" <use x=\"61.083984\" xlink:href=\"#DejaVuSans-73\"/>\n",
" <use x=\"90.576172\" xlink:href=\"#DejaVuSans-70\"/>\n",
" <use x=\"148.095703\" xlink:href=\"#DejaVuSans-70\"/>\n",
" </g>\n",
" </g>\n",
" <g id=\"patch_47\">\n",
" <path d=\"M 52.78125 71.332812 \n",
"L 72.78125 71.332812 \n",
"L 72.78125 64.332812 \n",
"L 52.78125 64.332812 \n",
"z\n",
"\" style=\"fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;\"/>\n",
" </g>\n",
" <g id=\"text_21\">\n",
" <!-- PNG -->\n",
" <defs>\n",
" <path d=\"M 9.8125 72.90625 \n",
"L 23.09375 72.90625 \n",
"L 55.421875 11.921875 \n",
"L 55.421875 72.90625 \n",
"L 64.984375 72.90625 \n",
"L 64.984375 0 \n",
"L 51.703125 0 \n",
"L 19.390625 60.984375 \n",
"L 19.390625 0 \n",
"L 9.8125 0 \n",
"z\n",
"\" id=\"DejaVuSans-78\"/>\n",
" </defs>\n",
" <g transform=\"translate(80.78125 71.332812)scale(0.1 -0.1)\">\n",
" <use xlink:href=\"#DejaVuSans-80\"/>\n",
" <use x=\"60.302734\" xlink:href=\"#DejaVuSans-78\"/>\n",
" <use x=\"135.107422\" xlink:href=\"#DejaVuSans-71\"/>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" </g>\n",
" <defs>\n",
" <clipPath id=\"p836f19e185\">\n",
" <rect height=\"217.44\" width=\"334.8\" x=\"43.78125\" y=\"10.7\"/>\n",
" </clipPath>\n",
" </defs>\n",
"</svg>\n"
],
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"%config InlineBackend.figure_formats = ['svg']\n",
"\n",
"import matplotlib as mpl\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# Merge per-format test data into a single DataFrame without the first\n",
|
| ︙ | ︙ | |||
167 168 169 170 171 172 173 |
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
| | | | | | | 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 |
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
|
| ︙ | ︙ | |||
104 105 106 107 108 109 110 | We chose to use Jupyter for this because it makes it easy for you to modify the notebook to try different things. Want to see how the results change with a different image size? Easy, change the `size` value in the second cell of the notebook. Want to try more image formats? You can put anything ImageMagick can recognize into the `formats` list. Want to find the break-even point for images like those | | | 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | We chose to use Jupyter for this because it makes it easy for you to modify the notebook to try different things. Want to see how the results change with a different image size? Easy, change the `size` value in the second cell of the notebook. Want to try more image formats? You can put anything ImageMagick can recognize into the `formats` list. Want to find the break-even point for images like those in your own repository? Easily done with a small amount of code. [im]: https://www.imagemagick.org/ [jp]: https://jupyter.org/ [nbd]: ./image-format-vs-repo-size.ipynb [nbp]: https://nbviewer.jupyter.org/urls/fossil-scm.org/fossil/doc/trunk/www/image-format-vs-repo-size.ipynb [wp]: http://wand-py.org/ |
| ︙ | ︙ | |||
199 200 201 202 203 204 205 | build system might include some kind of bootstrapping or auto-configuration step that you could attach this to, so that it doesn’t need to be run by hand. This `Makefile` illustrates two primary strategies: | | | 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | build system might include some kind of bootstrapping or auto-configuration step that you could attach this to, so that it doesn’t need to be run by hand. This `Makefile` illustrates two primary strategies: ### Input and Output File Formats Differ by Extension In the case of SVG and the bitmap image formats, the file name extension differs between the cases, so we can use `make` suffix rules to get the behavior we want. The top half of the `Makefile` just tells `make` how to map from `*.svg` to `*.svgz` and vice versa, and the same for `*.bmp` to/from `*.png`. |
| ︙ | ︙ | |||
254 255 256 257 258 259 260 |
3. We're using *uncompressed* TIFF here, not [LZW][lzw]- or
Zip-compressed TIFF, either of which would give similar results to
PNG, which is always zlib-compressed.
4. The raw data changes somewhat from one run to the next due to the
use of random noise in the image to make the zlib/PNG compression
more difficult, and the random pixel changes. Those test design
| | | 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 |
3. We're using *uncompressed* TIFF here, not [LZW][lzw]- or
Zip-compressed TIFF, either of which would give similar results to
PNG, which is always zlib-compressed.
4. The raw data changes somewhat from one run to the next due to the
use of random noise in the image to make the zlib/PNG compression
more difficult, and the random pixel changes. Those test design
choices make this a [Monte Carlo experiment][mce]. We’ve found that
the overall character of the results doesn’t change from one run to
the next.
The code in the notebook’s third cell drops the first three columns
of data because the first column (the empty repository size) is
boring, and the subsequent two checkins show the SQLite DB file
format settling in with its first few checkins. There’s a single
|
| ︙ | ︙ |
1 2 3 | <?xml version="1.0" encoding="utf-8" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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 |
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with matplotlib (https://matplotlib.org/) -->
<svg height="288pt" version="1.1" viewBox="0 0 432 288" width="432pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
*{stroke-linecap:butt;stroke-linejoin:round;}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 288
L 432 288
L 432 0
L 0 0
z
" style="fill:none;"/>
</g>
<g id="axes_1">
<g id="patch_2">
<path d="M 54 252
L 388.8 252
L 388.8 34.56
L 54 34.56
z
" style="fill:none;"/>
</g>
<g id="patch_3">
<path clip-path="url(#pb43dd894ca)" d="M 63 252
L 70.2 252
L 70.2 178.592751
L 63 178.592751
z
" style="fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_4">
<path clip-path="url(#pb43dd894ca)" d="M 99 252
L 106.2 252
L 106.2 178.592751
L 99 178.592751
z
" style="fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_5">
<path clip-path="url(#pb43dd894ca)" d="M 135 252
L 142.2 252
L 142.2 168.547548
L 135 168.547548
z
" style="fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_6">
<path clip-path="url(#pb43dd894ca)" d="M 171 252
L 178.2 252
L 178.2 153.866098
L 171 153.866098
z
" style="fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_7">
<path clip-path="url(#pb43dd894ca)" d="M 207 252
L 214.2 252
L 214.2 152.320682
L 207 152.320682
z
" style="fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_8">
<path clip-path="url(#pb43dd894ca)" d="M 243 252
L 250.2 252
L 250.2 150.775267
L 243 150.775267
z
" style="fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_9">
<path clip-path="url(#pb43dd894ca)" d="M 279 252
L 286.2 252
L 286.2 150.775267
L 279 150.775267
z
" style="fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_10">
<path clip-path="url(#pb43dd894ca)" d="M 315 252
L 322.2 252
L 322.2 140.730064
L 315 140.730064
z
" style="fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_11">
<path clip-path="url(#pb43dd894ca)" d="M 351 252
L 358.2 252
L 358.2 134.548401
L 351 134.548401
z
" style="fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_12">
<path clip-path="url(#pb43dd894ca)" d="M 70.2 252
L 77.4 252
L 77.4 142.27548
L 70.2 142.27548
z
" style="fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_13">
<path clip-path="url(#pb43dd894ca)" d="M 106.2 252
L 113.4 252
L 113.4 142.27548
L 106.2 142.27548
z
" style="fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_14">
<path clip-path="url(#pb43dd894ca)" d="M 142.2 252
L 149.4 252
L 149.4 142.27548
L 142.2 142.27548
z
" style="fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_15">
<path clip-path="url(#pb43dd894ca)" d="M 178.2 252
L 185.4 252
L 185.4 142.27548
L 178.2 142.27548
z
" style="fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_16">
<path clip-path="url(#pb43dd894ca)" d="M 214.2 252
L 221.4 252
L 221.4 142.27548
L 214.2 142.27548
z
" style="fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_17">
<path clip-path="url(#pb43dd894ca)" d="M 250.2 252
L 257.4 252
L 257.4 142.27548
L 250.2 142.27548
z
" style="fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_18">
<path clip-path="url(#pb43dd894ca)" d="M 286.2 252
L 293.4 252
L 293.4 142.27548
L 286.2 142.27548
z
" style="fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_19">
<path clip-path="url(#pb43dd894ca)" d="M 322.2 252
L 329.4 252
L 329.4 141.502772
L 322.2 141.502772
z
" style="fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_20">
<path clip-path="url(#pb43dd894ca)" d="M 358.2 252
L 365.4 252
L 365.4 141.502772
L 358.2 141.502772
z
" style="fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_21">
<path clip-path="url(#pb43dd894ca)" d="M 77.4 252
L 84.6 252
L 84.6 142.27548
L 77.4 142.27548
z
" style="fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_22">
<path clip-path="url(#pb43dd894ca)" d="M 113.4 252
L 120.6 252
L 120.6 142.27548
L 113.4 142.27548
z
" style="fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_23">
<path clip-path="url(#pb43dd894ca)" d="M 149.4 252
L 156.6 252
L 156.6 142.27548
L 149.4 142.27548
z
" style="fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_24">
<path clip-path="url(#pb43dd894ca)" d="M 185.4 252
L 192.6 252
L 192.6 142.27548
L 185.4 142.27548
z
" style="fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_25">
<path clip-path="url(#pb43dd894ca)" d="M 221.4 252
L 228.6 252
L 228.6 142.27548
L 221.4 142.27548
z
" style="fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_26">
<path clip-path="url(#pb43dd894ca)" d="M 257.4 252
L 264.6 252
L 264.6 142.27548
L 257.4 142.27548
z
" style="fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_27">
<path clip-path="url(#pb43dd894ca)" d="M 293.4 252
L 300.6 252
L 300.6 142.27548
L 293.4 142.27548
z
" style="fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_28">
<path clip-path="url(#pb43dd894ca)" d="M 329.4 252
L 336.6 252
L 336.6 141.502772
L 329.4 141.502772
z
" style="fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_29">
<path clip-path="url(#pb43dd894ca)" d="M 365.4 252
L 372.6 252
L 372.6 141.502772
L 365.4 141.502772
z
" style="fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_30">
<path clip-path="url(#pb43dd894ca)" d="M 84.6 252
L 91.8 252
L 91.8 143.048188
L 84.6 143.048188
z
" style="fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_31">
<path clip-path="url(#pb43dd894ca)" d="M 120.6 252
L 127.8 252
L 127.8 128.366738
L 120.6 128.366738
z
" style="fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_32">
<path clip-path="url(#pb43dd894ca)" d="M 156.6 252
L 163.8 252
L 163.8 112.139872
L 156.6 112.139872
z
" style="fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_33">
<path clip-path="url(#pb43dd894ca)" d="M 192.6 252
L 199.8 252
L 199.8 101.321962
L 192.6 101.321962
z
" style="fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_34">
<path clip-path="url(#pb43dd894ca)" d="M 228.6 252
L 235.8 252
L 235.8 97.458422
L 228.6 97.458422
z
" style="fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_35">
<path clip-path="url(#pb43dd894ca)" d="M 264.6 252
L 271.8 252
L 271.8 81.231557
L 264.6 81.231557
z
" style="fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_36">
<path clip-path="url(#pb43dd894ca)" d="M 300.6 252
L 307.8 252
L 307.8 81.231557
L 300.6 81.231557
z
" style="fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_37">
<path clip-path="url(#pb43dd894ca)" d="M 336.6 252
L 343.8 252
L 343.8 55.732196
L 336.6 55.732196
z
" style="fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="patch_38">
<path clip-path="url(#pb43dd894ca)" d="M 372.6 252
L 379.8 252
L 379.8 44.914286
L 372.6 44.914286
z
" style="fill:#b40426;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path d="M 0 0
L 0 3.5
" id="m4ac62df1f0" style="stroke:#000000;stroke-width:0.8;"/>
</defs>
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="77.4" xlink:href="#m4ac62df1f0" y="252"/>
</g>
</g>
<g id="text_1">
<!-- 3 -->
<defs>
<path d="M 40.578125 39.3125
Q 47.65625 37.796875 51.625 33
|
| ︙ | ︙ | |||
356 357 358 359 360 361 362 | Q 15.4375 72.65625 20.34375 73.4375 Q 25.25 74.21875 29.59375 74.21875 Q 40.828125 74.21875 47.359375 69.109375 Q 53.90625 64.015625 53.90625 55.328125 Q 53.90625 49.265625 50.4375 45.09375 Q 46.96875 40.921875 40.578125 39.3125 z | | | | | | 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 |
Q 15.4375 72.65625 20.34375 73.4375
Q 25.25 74.21875 29.59375 74.21875
Q 40.828125 74.21875 47.359375 69.109375
Q 53.90625 64.015625 53.90625 55.328125
Q 53.90625 49.265625 50.4375 45.09375
Q 46.96875 40.921875 40.578125 39.3125
z
" id="DejaVuSans-51"/>
</defs>
<g transform="translate(80.159375 265.3625)rotate(-90)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-51"/>
</g>
</g>
</g>
<g id="xtick_2">
<g id="line2d_2">
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="113.4" xlink:href="#m4ac62df1f0" y="252"/>
</g>
</g>
<g id="text_2">
<!-- 4 -->
<defs>
<path d="M 37.796875 64.3125
L 12.890625 25.390625
|
| ︙ | ︙ | |||
388 389 390 391 392 393 394 | L 47.609375 17.1875 L 47.609375 0 L 37.796875 0 L 37.796875 17.1875 L 4.890625 17.1875 L 4.890625 26.703125 z | | | | | | 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 |
L 47.609375 17.1875
L 47.609375 0
L 37.796875 0
L 37.796875 17.1875
L 4.890625 17.1875
L 4.890625 26.703125
z
" id="DejaVuSans-52"/>
</defs>
<g transform="translate(116.159375 265.3625)rotate(-90)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-52"/>
</g>
</g>
</g>
<g id="xtick_3">
<g id="line2d_3">
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="149.4" xlink:href="#m4ac62df1f0" y="252"/>
</g>
</g>
<g id="text_3">
<!-- 5 -->
<defs>
<path d="M 10.796875 72.90625
L 49.515625 72.90625
|
| ︙ | ︙ | |||
427 428 429 430 431 432 433 | Q 35.15625 6.890625 40.078125 11.328125 Q 45.015625 15.765625 45.015625 23.390625 Q 45.015625 31 40.078125 35.4375 Q 35.15625 39.890625 26.703125 39.890625 Q 22.75 39.890625 18.8125 39.015625 Q 14.890625 38.140625 10.796875 36.28125 z | | | | | | 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 |
Q 35.15625 6.890625 40.078125 11.328125
Q 45.015625 15.765625 45.015625 23.390625
Q 45.015625 31 40.078125 35.4375
Q 35.15625 39.890625 26.703125 39.890625
Q 22.75 39.890625 18.8125 39.015625
Q 14.890625 38.140625 10.796875 36.28125
z
" id="DejaVuSans-53"/>
</defs>
<g transform="translate(152.159375 265.3625)rotate(-90)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-53"/>
</g>
</g>
</g>
<g id="xtick_4">
<g id="line2d_4">
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="185.4" xlink:href="#m4ac62df1f0" y="252"/>
</g>
</g>
<g id="text_4">
<!-- 6 -->
<defs>
<path d="M 33.015625 40.375
Q 26.375 40.375 22.484375 35.828125
|
| ︙ | ︙ | |||
472 473 474 475 476 477 478 | Q 20.359375 -1.421875 13.671875 8.265625 Q 6.984375 17.96875 6.984375 36.375 Q 6.984375 53.65625 15.1875 63.9375 Q 23.390625 74.21875 37.203125 74.21875 Q 40.921875 74.21875 44.703125 73.484375 Q 48.484375 72.75 52.59375 71.296875 z | | | | | | | | | | 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 |
Q 20.359375 -1.421875 13.671875 8.265625
Q 6.984375 17.96875 6.984375 36.375
Q 6.984375 53.65625 15.1875 63.9375
Q 23.390625 74.21875 37.203125 74.21875
Q 40.921875 74.21875 44.703125 73.484375
Q 48.484375 72.75 52.59375 71.296875
z
" id="DejaVuSans-54"/>
</defs>
<g transform="translate(188.159375 265.3625)rotate(-90)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-54"/>
</g>
</g>
</g>
<g id="xtick_5">
<g id="line2d_5">
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="221.4" xlink:href="#m4ac62df1f0" y="252"/>
</g>
</g>
<g id="text_5">
<!-- 7 -->
<defs>
<path d="M 8.203125 72.90625
L 55.078125 72.90625
L 55.078125 68.703125
L 28.609375 0
L 18.3125 0
L 43.21875 64.59375
L 8.203125 64.59375
z
" id="DejaVuSans-55"/>
</defs>
<g transform="translate(224.159375 265.3625)rotate(-90)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-55"/>
</g>
</g>
</g>
<g id="xtick_6">
<g id="line2d_6">
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="257.4" xlink:href="#m4ac62df1f0" y="252"/>
</g>
</g>
<g id="text_6">
<!-- 8 -->
<defs>
<path d="M 31.78125 34.625
Q 24.75 34.625 20.71875 30.859375
|
| ︙ | ︙ | |||
550 551 552 553 554 555 556 | Q 38.140625 42.390625 41.71875 45.5625 Q 45.3125 48.734375 45.3125 54.390625 Q 45.3125 60.0625 41.71875 63.234375 Q 38.140625 66.40625 31.78125 66.40625 Q 25.390625 66.40625 21.84375 63.234375 Q 18.3125 60.0625 18.3125 54.390625 z | | | | | | 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 |
Q 38.140625 42.390625 41.71875 45.5625
Q 45.3125 48.734375 45.3125 54.390625
Q 45.3125 60.0625 41.71875 63.234375
Q 38.140625 66.40625 31.78125 66.40625
Q 25.390625 66.40625 21.84375 63.234375
Q 18.3125 60.0625 18.3125 54.390625
z
" id="DejaVuSans-56"/>
</defs>
<g transform="translate(260.159375 265.3625)rotate(-90)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-56"/>
</g>
</g>
</g>
<g id="xtick_7">
<g id="line2d_7">
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="293.4" xlink:href="#m4ac62df1f0" y="252"/>
</g>
</g>
<g id="text_7">
<!-- 9 -->
<defs>
<path d="M 10.984375 1.515625
L 10.984375 10.5
|
| ︙ | ︙ | |||
595 596 597 598 599 600 601 | Q 45.015625 57.28125 41.125 61.84375 Q 37.25 66.40625 30.609375 66.40625 Q 23.96875 66.40625 20.09375 61.84375 Q 16.21875 57.28125 16.21875 49.421875 Q 16.21875 41.5 20.09375 36.953125 Q 23.96875 32.421875 30.609375 32.421875 z | | | | | | | | | | | | | | | 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 |
Q 45.015625 57.28125 41.125 61.84375
Q 37.25 66.40625 30.609375 66.40625
Q 23.96875 66.40625 20.09375 61.84375
Q 16.21875 57.28125 16.21875 49.421875
Q 16.21875 41.5 20.09375 36.953125
Q 23.96875 32.421875 30.609375 32.421875
z
" id="DejaVuSans-57"/>
</defs>
<g transform="translate(296.159375 265.3625)rotate(-90)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-57"/>
</g>
</g>
</g>
<g id="xtick_8">
<g id="line2d_8">
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="329.4" xlink:href="#m4ac62df1f0" y="252"/>
</g>
</g>
<g id="text_8">
<!-- 10 -->
<defs>
<path d="M 12.40625 8.296875
L 28.515625 8.296875
L 28.515625 63.921875
L 10.984375 60.40625
L 10.984375 69.390625
L 28.421875 72.90625
L 38.28125 72.90625
L 38.28125 8.296875
L 54.390625 8.296875
L 54.390625 0
L 12.40625 0
z
" id="DejaVuSans-49"/>
<path d="M 31.78125 66.40625
Q 24.171875 66.40625 20.328125 58.90625
Q 16.5 51.421875 16.5 36.375
Q 16.5 21.390625 20.328125 13.890625
Q 24.171875 6.390625 31.78125 6.390625
Q 39.453125 6.390625 43.28125 13.890625
Q 47.125 21.390625 47.125 36.375
Q 47.125 51.421875 43.28125 58.90625
Q 39.453125 66.40625 31.78125 66.40625
z
M 31.78125 74.21875
Q 44.046875 74.21875 50.515625 64.515625
Q 56.984375 54.828125 56.984375 36.375
Q 56.984375 17.96875 50.515625 8.265625
Q 44.046875 -1.421875 31.78125 -1.421875
Q 19.53125 -1.421875 13.0625 8.265625
Q 6.59375 17.96875 6.59375 36.375
Q 6.59375 54.828125 13.0625 64.515625
Q 19.53125 74.21875 31.78125 74.21875
z
" id="DejaVuSans-48"/>
</defs>
<g transform="translate(332.159375 271.725)rotate(-90)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-49"/>
<use x="63.623047" xlink:href="#DejaVuSans-48"/>
</g>
</g>
</g>
<g id="xtick_9">
<g id="line2d_9">
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="365.4" xlink:href="#m4ac62df1f0" y="252"/>
</g>
</g>
<g id="text_9">
<!-- 11 -->
<g transform="translate(368.159375 271.725)rotate(-90)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-49"/>
<use x="63.623047" xlink:href="#DejaVuSans-49"/>
</g>
</g>
</g>
<g id="text_10">
<!-- Checkin index -->
<defs>
<path d="M 64.40625 67.28125
|
| ︙ | ︙ | |||
689 690 691 692 693 694 695 | Q 24.65625 -1.421875 15.125 8.703125 Q 5.609375 18.84375 5.609375 36.375 Q 5.609375 53.953125 15.125 64.078125 Q 24.65625 74.21875 41.21875 74.21875 Q 47.75 74.21875 53.53125 72.484375 Q 59.328125 70.75 64.40625 67.28125 z | | | | 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 |
Q 24.65625 -1.421875 15.125 8.703125
Q 5.609375 18.84375 5.609375 36.375
Q 5.609375 53.953125 15.125 64.078125
Q 24.65625 74.21875 41.21875 74.21875
Q 47.75 74.21875 53.53125 72.484375
Q 59.328125 70.75 64.40625 67.28125
z
" id="DejaVuSans-67"/>
<path d="M 54.890625 33.015625
L 54.890625 0
L 45.90625 0
L 45.90625 32.71875
Q 45.90625 40.484375 42.875 44.328125
Q 39.84375 48.1875 33.796875 48.1875
Q 26.515625 48.1875 22.3125 43.546875
Q 18.109375 38.921875 18.109375 30.90625
L 18.109375 0
L 9.078125 0
L 9.078125 75.984375
L 18.109375 75.984375
L 18.109375 46.1875
Q 21.34375 51.125 25.703125 53.5625
Q 30.078125 56 35.796875 56
Q 45.21875 56 50.046875 50.171875
Q 54.890625 44.34375 54.890625 33.015625
z
" id="DejaVuSans-104"/>
<path d="M 56.203125 29.59375
L 56.203125 25.203125
L 14.890625 25.203125
Q 15.484375 15.921875 20.484375 11.0625
Q 25.484375 6.203125 34.421875 6.203125
Q 39.59375 6.203125 44.453125 7.46875
Q 49.3125 8.734375 54.109375 11.28125
|
| ︙ | ︙ | |||
732 733 734 735 736 737 738 | z M 47.21875 32.234375 Q 47.125 39.59375 43.09375 43.984375 Q 39.0625 48.390625 32.421875 48.390625 Q 24.90625 48.390625 20.390625 44.140625 Q 15.875 39.890625 15.1875 32.171875 z | | | | | | | | 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 |
z
M 47.21875 32.234375
Q 47.125 39.59375 43.09375 43.984375
Q 39.0625 48.390625 32.421875 48.390625
Q 24.90625 48.390625 20.390625 44.140625
Q 15.875 39.890625 15.1875 32.171875
z
" id="DejaVuSans-101"/>
<path d="M 48.78125 52.59375
L 48.78125 44.1875
Q 44.96875 46.296875 41.140625 47.34375
Q 37.3125 48.390625 33.40625 48.390625
Q 24.65625 48.390625 19.8125 42.84375
Q 14.984375 37.3125 14.984375 27.296875
Q 14.984375 17.28125 19.8125 11.734375
Q 24.65625 6.203125 33.40625 6.203125
Q 37.3125 6.203125 41.140625 7.25
Q 44.96875 8.296875 48.78125 10.40625
L 48.78125 2.09375
Q 45.015625 0.34375 40.984375 -0.53125
Q 36.96875 -1.421875 32.421875 -1.421875
Q 20.0625 -1.421875 12.78125 6.34375
Q 5.515625 14.109375 5.515625 27.296875
Q 5.515625 40.671875 12.859375 48.328125
Q 20.21875 56 33.015625 56
Q 37.15625 56 41.109375 55.140625
Q 45.0625 54.296875 48.78125 52.59375
z
" id="DejaVuSans-99"/>
<path d="M 9.078125 75.984375
L 18.109375 75.984375
L 18.109375 31.109375
L 44.921875 54.6875
L 56.390625 54.6875
L 27.390625 29.109375
L 57.625 0
L 45.90625 0
L 18.109375 26.703125
L 18.109375 0
L 9.078125 0
z
" id="DejaVuSans-107"/>
<path d="M 9.421875 54.6875
L 18.40625 54.6875
L 18.40625 0
L 9.421875 0
z
M 9.421875 75.984375
L 18.40625 75.984375
L 18.40625 64.59375
L 9.421875 64.59375
z
" id="DejaVuSans-105"/>
<path d="M 54.890625 33.015625
L 54.890625 0
L 45.90625 0
L 45.90625 32.71875
Q 45.90625 40.484375 42.875 44.328125
Q 39.84375 48.1875 33.796875 48.1875
Q 26.515625 48.1875 22.3125 43.546875
Q 18.109375 38.921875 18.109375 30.90625
L 18.109375 0
L 9.078125 0
L 9.078125 54.6875
L 18.109375 54.6875
L 18.109375 46.1875
Q 21.34375 51.125 25.703125 53.5625
Q 30.078125 56 35.796875 56
Q 45.21875 56 50.046875 50.171875
Q 54.890625 44.34375 54.890625 33.015625
z
" id="DejaVuSans-110"/>
<path id="DejaVuSans-32"/>
<path d="M 45.40625 46.390625
L 45.40625 75.984375
L 54.390625 75.984375
L 54.390625 0
L 45.40625 0
L 45.40625 8.203125
Q 42.578125 3.328125 38.25 0.953125
|
| ︙ | ︙ | |||
823 824 825 826 827 828 829 | Q 37.203125 6.109375 41.296875 11.75 Q 45.40625 17.390625 45.40625 27.296875 Q 45.40625 37.203125 41.296875 42.84375 Q 37.203125 48.484375 30.078125 48.484375 Q 22.953125 48.484375 18.875 42.84375 Q 14.796875 37.203125 14.796875 27.296875 z | | | | | | | | | | | | | | | | | | | | | | | | | | 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 |
Q 37.203125 6.109375 41.296875 11.75
Q 45.40625 17.390625 45.40625 27.296875
Q 45.40625 37.203125 41.296875 42.84375
Q 37.203125 48.484375 30.078125 48.484375
Q 22.953125 48.484375 18.875 42.84375
Q 14.796875 37.203125 14.796875 27.296875
z
" id="DejaVuSans-100"/>
<path d="M 54.890625 54.6875
L 35.109375 28.078125
L 55.90625 0
L 45.3125 0
L 29.390625 21.484375
L 13.484375 0
L 2.875 0
L 24.125 28.609375
L 4.6875 54.6875
L 15.28125 54.6875
L 29.78125 35.203125
L 44.28125 54.6875
z
" id="DejaVuSans-120"/>
</defs>
<g transform="translate(186.104688 283.323438)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-67"/>
<use x="69.824219" xlink:href="#DejaVuSans-104"/>
<use x="133.203125" xlink:href="#DejaVuSans-101"/>
<use x="194.726562" xlink:href="#DejaVuSans-99"/>
<use x="249.707031" xlink:href="#DejaVuSans-107"/>
<use x="307.617188" xlink:href="#DejaVuSans-105"/>
<use x="335.400391" xlink:href="#DejaVuSans-110"/>
<use x="398.779297" xlink:href="#DejaVuSans-32"/>
<use x="430.566406" xlink:href="#DejaVuSans-105"/>
<use x="458.349609" xlink:href="#DejaVuSans-110"/>
<use x="521.728516" xlink:href="#DejaVuSans-100"/>
<use x="585.205078" xlink:href="#DejaVuSans-101"/>
<use x="646.712891" xlink:href="#DejaVuSans-120"/>
</g>
</g>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1">
<g id="line2d_10">
<defs>
<path d="M 0 0
L -3.5 0
" id="m78b7b2ee23" style="stroke:#000000;stroke-width:0.8;"/>
</defs>
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="54" xlink:href="#m78b7b2ee23" y="252"/>
</g>
</g>
<g id="text_11">
<!-- 0.0 -->
<defs>
<path d="M 10.6875 12.40625
L 21 12.40625
L 21 0
L 10.6875 0
z
" id="DejaVuSans-46"/>
</defs>
<g transform="translate(31.096875 255.799219)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-48"/>
<use x="63.623047" xlink:href="#DejaVuSans-46"/>
<use x="95.410156" xlink:href="#DejaVuSans-48"/>
</g>
</g>
</g>
<g id="ytick_2">
<g id="line2d_11">
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="54" xlink:href="#m78b7b2ee23" y="212.437356"/>
</g>
</g>
<g id="text_12">
<!-- 0.2 -->
<defs>
<path d="M 19.1875 8.296875
L 53.609375 8.296875
|
| ︙ | ︙ | |||
917 918 919 920 921 922 923 | Q 39.75 74.21875 46.484375 68.546875 Q 53.21875 62.890625 53.21875 53.421875 Q 53.21875 48.921875 51.53125 44.890625 Q 49.859375 40.875 45.40625 35.40625 Q 44.1875 33.984375 37.640625 27.21875 Q 31.109375 20.453125 19.1875 8.296875 z | | | | | | | | | | | | | | | | | | | | | | | | | | | 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 |
Q 39.75 74.21875 46.484375 68.546875
Q 53.21875 62.890625 53.21875 53.421875
Q 53.21875 48.921875 51.53125 44.890625
Q 49.859375 40.875 45.40625 35.40625
Q 44.1875 33.984375 37.640625 27.21875
Q 31.109375 20.453125 19.1875 8.296875
z
" id="DejaVuSans-50"/>
</defs>
<g transform="translate(31.096875 216.236575)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-48"/>
<use x="63.623047" xlink:href="#DejaVuSans-46"/>
<use x="95.410156" xlink:href="#DejaVuSans-50"/>
</g>
</g>
</g>
<g id="ytick_3">
<g id="line2d_12">
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="54" xlink:href="#m78b7b2ee23" y="172.874712"/>
</g>
</g>
<g id="text_13">
<!-- 0.4 -->
<g transform="translate(31.096875 176.673931)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-48"/>
<use x="63.623047" xlink:href="#DejaVuSans-46"/>
<use x="95.410156" xlink:href="#DejaVuSans-52"/>
</g>
</g>
</g>
<g id="ytick_4">
<g id="line2d_13">
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="54" xlink:href="#m78b7b2ee23" y="133.312068"/>
</g>
</g>
<g id="text_14">
<!-- 0.6 -->
<g transform="translate(31.096875 137.111287)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-48"/>
<use x="63.623047" xlink:href="#DejaVuSans-46"/>
<use x="95.410156" xlink:href="#DejaVuSans-54"/>
</g>
</g>
</g>
<g id="ytick_5">
<g id="line2d_14">
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="54" xlink:href="#m78b7b2ee23" y="93.749424"/>
</g>
</g>
<g id="text_15">
<!-- 0.8 -->
<g transform="translate(31.096875 97.548643)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-48"/>
<use x="63.623047" xlink:href="#DejaVuSans-46"/>
<use x="95.410156" xlink:href="#DejaVuSans-56"/>
</g>
</g>
</g>
<g id="ytick_6">
<g id="line2d_15">
<g>
<use style="stroke:#000000;stroke-width:0.8;" x="54" xlink:href="#m78b7b2ee23" y="54.18678"/>
</g>
</g>
<g id="text_16">
<!-- 1.0 -->
<g transform="translate(31.096875 57.985999)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-49"/>
<use x="63.623047" xlink:href="#DejaVuSans-46"/>
<use x="95.410156" xlink:href="#DejaVuSans-48"/>
</g>
</g>
</g>
<g id="text_17">
<!-- Repo size (MiB) -->
<defs>
<path d="M 44.390625 34.1875
|
| ︙ | ︙ | |||
1015 1016 1017 1018 1019 1020 1021 | L 19.671875 38.921875 L 32.078125 38.921875 Q 39.203125 38.921875 42.84375 42.21875 Q 46.484375 45.515625 46.484375 51.90625 Q 46.484375 58.296875 42.84375 61.546875 Q 39.203125 64.796875 32.078125 64.796875 z | | | 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 |
L 19.671875 38.921875
L 32.078125 38.921875
Q 39.203125 38.921875 42.84375 42.21875
Q 46.484375 45.515625 46.484375 51.90625
Q 46.484375 58.296875 42.84375 61.546875
Q 39.203125 64.796875 32.078125 64.796875
z
" id="DejaVuSans-82"/>
<path d="M 18.109375 8.203125
L 18.109375 -20.796875
L 9.078125 -20.796875
L 9.078125 54.6875
L 18.109375 54.6875
L 18.109375 46.390625
Q 20.953125 51.265625 25.265625 53.625
|
| ︙ | ︙ | |||
1041 1042 1043 1044 1045 1046 1047 | Q 26.265625 48.484375 22.1875 42.84375 Q 18.109375 37.203125 18.109375 27.296875 Q 18.109375 17.390625 22.1875 11.75 Q 26.265625 6.109375 33.40625 6.109375 Q 40.53125 6.109375 44.609375 11.75 Q 48.6875 17.390625 48.6875 27.296875 z | | | | 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 |
Q 26.265625 48.484375 22.1875 42.84375
Q 18.109375 37.203125 18.109375 27.296875
Q 18.109375 17.390625 22.1875 11.75
Q 26.265625 6.109375 33.40625 6.109375
Q 40.53125 6.109375 44.609375 11.75
Q 48.6875 17.390625 48.6875 27.296875
z
" id="DejaVuSans-112"/>
<path d="M 30.609375 48.390625
Q 23.390625 48.390625 19.1875 42.75
Q 14.984375 37.109375 14.984375 27.296875
Q 14.984375 17.484375 19.15625 11.84375
Q 23.34375 6.203125 30.609375 6.203125
Q 37.796875 6.203125 41.984375 11.859375
Q 46.1875 17.53125 46.1875 27.296875
Q 46.1875 37.015625 41.984375 42.703125
Q 37.796875 48.390625 30.609375 48.390625
z
M 30.609375 56
Q 42.328125 56 49.015625 48.375
Q 55.71875 40.765625 55.71875 27.296875
Q 55.71875 13.875 49.015625 6.21875
Q 42.328125 -1.421875 30.609375 -1.421875
Q 18.84375 -1.421875 12.171875 6.21875
Q 5.515625 13.875 5.515625 27.296875
Q 5.515625 40.765625 12.171875 48.375
Q 18.84375 56 30.609375 56
z
" id="DejaVuSans-111"/>
<path d="M 44.28125 53.078125
L 44.28125 44.578125
Q 40.484375 46.53125 36.375 47.5
Q 32.28125 48.484375 27.875 48.484375
Q 21.1875 48.484375 17.84375 46.4375
Q 14.5 44.390625 14.5 40.28125
Q 14.5 37.15625 16.890625 35.375
|
| ︙ | ︙ | |||
1093 1094 1095 1096 1097 1098 1099 | Q 13.234375 26.265625 9.515625 29.90625 Q 5.8125 33.546875 5.8125 39.890625 Q 5.8125 47.609375 11.28125 51.796875 Q 16.75 56 26.8125 56 Q 31.78125 56 36.171875 55.265625 Q 40.578125 54.546875 44.28125 53.078125 z | | | | | | 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 |
Q 13.234375 26.265625 9.515625 29.90625
Q 5.8125 33.546875 5.8125 39.890625
Q 5.8125 47.609375 11.28125 51.796875
Q 16.75 56 26.8125 56
Q 31.78125 56 36.171875 55.265625
Q 40.578125 54.546875 44.28125 53.078125
z
" id="DejaVuSans-115"/>
<path d="M 5.515625 54.6875
L 48.1875 54.6875
L 48.1875 46.484375
L 14.40625 7.171875
L 48.1875 7.171875
L 48.1875 0
L 4.296875 0
L 4.296875 8.203125
L 38.09375 47.515625
L 5.515625 47.515625
z
" id="DejaVuSans-122"/>
<path d="M 31 75.875
Q 24.46875 64.65625 21.28125 53.65625
Q 18.109375 42.671875 18.109375 31.390625
Q 18.109375 20.125 21.3125 9.0625
Q 24.515625 -2 31 -13.1875
L 23.1875 -13.1875
Q 15.875 -1.703125 12.234375 9.375
Q 8.59375 20.453125 8.59375 31.390625
Q 8.59375 42.28125 12.203125 53.3125
Q 15.828125 64.359375 23.1875 75.875
z
" id="DejaVuSans-40"/>
<path d="M 9.8125 72.90625
L 24.515625 72.90625
L 43.109375 23.296875
L 61.8125 72.90625
L 76.515625 72.90625
L 76.515625 0
L 66.890625 0
L 66.890625 64.015625
L 48.09375 14.015625
L 38.1875 14.015625
L 19.390625 64.015625
L 19.390625 0
L 9.8125 0
z
" id="DejaVuSans-77"/>
<path d="M 19.671875 34.8125
L 19.671875 8.109375
L 35.5 8.109375
Q 43.453125 8.109375 47.28125 11.40625
Q 51.125 14.703125 51.125 21.484375
Q 51.125 28.328125 47.28125 31.5625
Q 43.453125 34.8125 35.5 34.8125
|
| ︙ | ︙ | |||
1161 1162 1163 1164 1165 1166 1167 | Q 52.25 40.28125 46.1875 39.3125 Q 53.46875 37.75 57.5 32.78125 Q 61.53125 27.828125 61.53125 20.40625 Q 61.53125 10.640625 54.890625 5.3125 Q 48.25 0 35.984375 0 L 9.8125 0 z | | | | | | | | | | | | | | | | | | | | | | | | 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 |
Q 52.25 40.28125 46.1875 39.3125
Q 53.46875 37.75 57.5 32.78125
Q 61.53125 27.828125 61.53125 20.40625
Q 61.53125 10.640625 54.890625 5.3125
Q 48.25 0 35.984375 0
L 9.8125 0
z
" id="DejaVuSans-66"/>
<path d="M 8.015625 75.875
L 15.828125 75.875
Q 23.140625 64.359375 26.78125 53.3125
Q 30.421875 42.28125 30.421875 31.390625
Q 30.421875 20.453125 26.78125 9.375
Q 23.140625 -1.703125 15.828125 -13.1875
L 8.015625 -13.1875
Q 14.5 -2 17.703125 9.0625
Q 20.90625 20.125 20.90625 31.390625
Q 20.90625 42.671875 17.703125 53.65625
Q 14.5 64.65625 8.015625 75.875
z
" id="DejaVuSans-41"/>
</defs>
<g transform="translate(25.017187 181.969063)rotate(-90)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-82"/>
<use x="69.419922" xlink:href="#DejaVuSans-101"/>
<use x="130.943359" xlink:href="#DejaVuSans-112"/>
<use x="194.419922" xlink:href="#DejaVuSans-111"/>
<use x="255.601562" xlink:href="#DejaVuSans-32"/>
<use x="287.388672" xlink:href="#DejaVuSans-115"/>
<use x="339.488281" xlink:href="#DejaVuSans-105"/>
<use x="367.271484" xlink:href="#DejaVuSans-122"/>
<use x="419.761719" xlink:href="#DejaVuSans-101"/>
<use x="481.285156" xlink:href="#DejaVuSans-32"/>
<use x="513.072266" xlink:href="#DejaVuSans-40"/>
<use x="552.085938" xlink:href="#DejaVuSans-77"/>
<use x="638.365234" xlink:href="#DejaVuSans-105"/>
<use x="666.148438" xlink:href="#DejaVuSans-66"/>
<use x="734.751953" xlink:href="#DejaVuSans-41"/>
</g>
</g>
</g>
<g id="patch_39">
<path d="M 54 252
L 54 34.56
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;"/>
</g>
<g id="patch_40">
<path d="M 388.8 252
L 388.8 34.56
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;"/>
</g>
<g id="patch_41">
<path d="M 54 252
L 388.8 252
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;"/>
</g>
<g id="patch_42">
<path d="M 54 34.56
L 388.8 34.56
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;"/>
</g>
|
| ︙ | ︙ | |||
1230 1231 1232 1233 1234 1235 1236 |
Q 59 39.56 59 41.56
L 59 99.2725
Q 59 101.2725 61 101.2725
z
" style="fill:#ffffff;opacity:0.8;stroke:#cccccc;stroke-linejoin:miter;"/>
</g>
<g id="patch_44">
| | | | | | | | | 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 |
Q 59 39.56 59 41.56
L 59 99.2725
Q 59 101.2725 61 101.2725
z
" style="fill:#ffffff;opacity:0.8;stroke:#cccccc;stroke-linejoin:miter;"/>
</g>
<g id="patch_44">
<path d="M 63 51.158437
L 83 51.158437
L 83 44.158437
L 63 44.158437
z
" style="fill:#3b4cc0;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="text_18">
<!-- JPEG -->
<defs>
<path d="M 9.8125 72.90625
L 19.671875 72.90625
L 19.671875 5.078125
Q 19.671875 -8.109375 14.671875 -14.0625
Q 9.671875 -20.015625 -1.421875 -20.015625
L -5.171875 -20.015625
L -5.171875 -11.71875
L -2.09375 -11.71875
Q 4.4375 -11.71875 7.125 -8.046875
Q 9.8125 -4.390625 9.8125 5.078125
z
" id="DejaVuSans-74"/>
<path d="M 19.671875 64.796875
L 19.671875 37.40625
L 32.078125 37.40625
Q 38.96875 37.40625 42.71875 40.96875
Q 46.484375 44.53125 46.484375 51.125
Q 46.484375 57.671875 42.71875 61.234375
Q 38.96875 64.796875 32.078125 64.796875
z
M 9.8125 72.90625
L 32.078125 72.90625
Q 44.34375 72.90625 50.609375 67.359375
Q 56.890625 61.8125 56.890625 51.125
Q 56.890625 40.328125 50.609375 34.8125
Q 44.34375 29.296875 32.078125 29.296875
L 19.671875 29.296875
L 19.671875 0
L 9.8125 0
z
" id="DejaVuSans-80"/>
<path d="M 9.8125 72.90625
L 55.90625 72.90625
L 55.90625 64.59375
L 19.671875 64.59375
L 19.671875 43.015625
L 54.390625 43.015625
L 54.390625 34.71875
L 19.671875 34.71875
L 19.671875 8.296875
L 56.78125 8.296875
L 56.78125 0
L 9.8125 0
z
" id="DejaVuSans-69"/>
<path d="M 59.515625 10.40625
L 59.515625 29.984375
L 43.40625 29.984375
L 43.40625 38.09375
L 69.28125 38.09375
L 69.28125 6.78125
Q 63.578125 2.734375 56.6875 0.65625
|
| ︙ | ︙ | |||
1309 1310 1311 1312 1313 1314 1315 | Q 29.4375 66.109375 22.71875 58.640625 Q 16.015625 51.171875 16.015625 36.375 Q 16.015625 21.625 22.71875 14.15625 Q 29.4375 6.6875 42.828125 6.6875 Q 48.046875 6.6875 52.140625 7.59375 Q 56.25 8.5 59.515625 10.40625 z | | | | | | | | | | | | | | | | | | | | | | | | | | | | 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 |
Q 29.4375 66.109375 22.71875 58.640625
Q 16.015625 51.171875 16.015625 36.375
Q 16.015625 21.625 22.71875 14.15625
Q 29.4375 6.6875 42.828125 6.6875
Q 48.046875 6.6875 52.140625 7.59375
Q 56.25 8.5 59.515625 10.40625
z
" id="DejaVuSans-71"/>
</defs>
<g transform="translate(91 51.158437)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-74"/>
<use x="29.492188" xlink:href="#DejaVuSans-80"/>
<use x="89.794922" xlink:href="#DejaVuSans-69"/>
<use x="152.978516" xlink:href="#DejaVuSans-71"/>
</g>
</g>
<g id="patch_45">
<path d="M 63 65.836562
L 83 65.836562
L 83 58.836562
L 63 58.836562
z
" style="fill:#aac7fd;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="text_19">
<!-- BMP -->
<g transform="translate(91 65.836562)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-66"/>
<use x="68.603516" xlink:href="#DejaVuSans-77"/>
<use x="154.882812" xlink:href="#DejaVuSans-80"/>
</g>
</g>
<g id="patch_46">
<path d="M 63 80.514687
L 83 80.514687
L 83 73.514687
L 63 73.514687
z
" style="fill:#f7b89c;stroke:#ffffff;stroke-linejoin:miter;stroke-width:2;"/>
</g>
<g id="text_20">
<!-- TIFF -->
<defs>
<path d="M -0.296875 72.90625
L 61.375 72.90625
L 61.375 64.59375
L 35.5 64.59375
L 35.5 0
L 25.59375 0
L 25.59375 64.59375
L -0.296875 64.59375
z
" id="DejaVuSans-84"/>
<path d="M 9.8125 72.90625
L 19.671875 72.90625
L 19.671875 0
L 9.8125 0
z
" id="DejaVuSans-73"/>
<path d="M 9.8125 72.90625
L 51.703125 72.90625
L 51.703125 64.59375
L 19.671875 64.59375
L 19.671875 43.109375
L 48.578125 43.109375
L 48.578125 34.8125
L 19.671875 34.8125
L 19.671875 0
L 9.8125 0
z
" id="DejaVuSans-70"/>
</defs>
<g transform="translate(91 80.514687)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-84"/>
<use x="61.083984" xlink:href="#DejaVuSans-73"/>
<use x="90.576172" xlink:href="#DejaVuSans-70"/>
<use x="148.095703" xlink:href="#DejaVuSans-70"/>
</g>
</g>
<g id="patch_47">
<path d="M 63 95.192813
L 83 95.192813
L 83 88.192813
L 63 88.192813
|
| ︙ | ︙ | |||
1403 1404 1405 1406 1407 1408 1409 | L 64.984375 72.90625 L 64.984375 0 L 51.703125 0 L 19.390625 60.984375 L 19.390625 0 L 9.8125 0 z | | | | | | | | 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 |
L 64.984375 72.90625
L 64.984375 0
L 51.703125 0
L 19.390625 60.984375
L 19.390625 0
L 9.8125 0
z
" id="DejaVuSans-78"/>
</defs>
<g transform="translate(91 95.192813)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-80"/>
<use x="60.302734" xlink:href="#DejaVuSans-78"/>
<use x="135.107422" xlink:href="#DejaVuSans-71"/>
</g>
</g>
</g>
</g>
</g>
<defs>
<clipPath id="pb43dd894ca">
<rect height="217.44" width="334.8" x="54" y="34.56"/>
</clipPath>
</defs>
</svg>
|
1 2 3 4 5 6 7 8 9 | <title>Home</title> <h3>What Is Fossil?</h3> <div style='width:200px;float:right;border:2px solid #446979;padding:10px;margin:0px 10px;'> <ul> <li> [/uv/download.html | Download] <li> [./quickstart.wiki | Quick Start] <li> [./build.wiki | Install] | | | > > < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <title>Home</title> <h3>What Is Fossil?</h3> <div style='width:200px;float:right;border:2px solid #446979;padding:10px;margin:0px 10px;'> <ul> <li> [/uv/download.html | Download] <li> [./quickstart.wiki | Quick Start] <li> [./build.wiki | Install] <li> [https://fossil-scm.org/forum | Support/Forum ] <li> [./hints.wiki | Tips & Hints] <li> [./changes.wiki | Change Log] <li> [../COPYRIGHT-BSD2.txt | License] <li> [./userlinks.wiki | User Links] <li> [./hacker-howto.wiki | Hacker How-To] <li> [./fossil-v-git.wiki | Fossil vs. Git] <li> [./permutedindex.html | Documentation Index] </ul> <img src="fossil3.gif" align="center"> </div> <p>Fossil is a simple, high-reliability, distributed software configuration management system with these advanced features: |
| ︙ | ︙ | |||
59 60 61 62 63 64 65 |
for network communications, so it works fine from behind
restrictive firewalls, including [./quickstart.wiki#proxy|proxies].
The protocol is
[./stats.wiki | bandwidth efficient] to the point that Fossil can be
used comfortably over dial-up or over the exceedingly slow Wifi on
airliners.
| | | > | | < < < < < < < < < < < < < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < | < < < > | < < < > | | < | < < < < > > > > | | 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 |
for network communications, so it works fine from behind
restrictive firewalls, including [./quickstart.wiki#proxy|proxies].
The protocol is
[./stats.wiki | bandwidth efficient] to the point that Fossil can be
used comfortably over dial-up or over the exceedingly slow Wifi on
airliners.
5. <b>Simple Server Setup</b> - No server is required, but if you want to
set one up, Fossil supports [./server/ | several different server
configurations] including CGI, SCGI, and direct HTTP.
You can also easily set up your Fossil repository to automatically
[./mirrortogithub.md | mirror content on GitHub].
6. <b>Autosync</b> -
Fossil supports [./concepts.wiki#workflow | "autosync" mode]
which helps to keep projects moving
forward by reducing the amount of needless
[./branching.wiki | forking and merging] often
associated with distributed projects.
7. <b>Robust & Reliable</b> -
Fossil stores content using an [./fileformat.wiki | enduring file format]
in an SQLite database so that transactions are
atomic even if interrupted by a power loss or system crash.
Automatic [./selfcheck.wiki | self-checks] verify that all aspects of
the repository are consistent prior to each commit.
8. <b>Free and Open-Source</b> - Uses the [../COPYRIGHT-BSD2.txt|2-clause BSD license].
<hr>
<h3>[/timeline?t=release|Latest Release]: 2.11.1 (2020-06-08)</h3>
* [/uv/download.html|Download]
* [./changes.wiki#v2_11|Change Summary]
<hr>
<h3>Quick Start</h3>
1. [/uv/download.html|Download] or install using a package manager or
[./build.wiki|compile from sources].
2. <tt>fossil init</tt> <i>new-repository</i>
3. <tt>fossil open</tt> <i>new-repository</i>
4. <tt>fossil add</tt> <i>files-or-directories</i>
5. <tt>fossil commit -m</tt> "<i>commit message</i>"
6. <tt>fossil ui</tt>
7. Repeat steps 4, 5, and 6, in any order, as necessary.
See the [./quickstart.wiki|Quick Start Guide] for more detail.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | # Use of JavaScript in Fossil ## Philosophy The Fossil development project’s policy is to use JavaScript where it helps make its web UI better, but to offer graceful fallbacks wherever practical. The intent is that the UI be usable with JavaScript entirely disabled. In every place where Fossil uses JavaScript, it is an enhancement to provided functionality, and there is always another way to accomplish a given end without using JavaScript. This is not to say that Fossil’s fall-backs for such cases are always as elegant and functional as a no-JS purist might wish. That is simply because [the vast majority of web users run with JS enabled](#stats), and a minority of those run with some kind of conditional JavaScript blocking in place. Fossil’s active developers do not deviate from that norm enough that we have many no-JS purists among us, so the no-JS case doesn’t get as much attention as some might want. We do [accept code contributions][cg], and we are philosophically in favor of graceful fall-backs, so you are welcome to appoint yourself the position of no-JS czar for the Fossil project! Evil is in actions, not in nouns, so we do not believe JavaScript *can* be evil. It is an active technology, but the actions that matter here are those of writing the code and checking it into the Fossil project repository. None of the JavaScript code in Fossil is evil, a fact we enforce by being careful about who we give check-in rights on the repository to and by policing what code does get contributed. The Fossil project does not accept non-trivial outside contributions. We think it’s better to ask not whether Fossil requires JavaScript but whether Fossil uses JavaScript *well*, so that [you can decide](#block) to block or allow Fossil’s use of JavaScript. [cg]: ./contribute.wiki ## <a id="block"></a>Blocking JavaScript Rather than either block JavaScript wholesale or give up on blocking JavaScript entirely, we recommend that you use tools like [NoScript][ns] or [uBlock Origin][ub] to selectively block problematic uses of JavaScript so the rest of the web can use the technology productively, as it was intended. There are doubtless other useful tools of this sort; we recommend only these two due to our limited experience, not out of any wish to exclude other tools. The primary difference between these two for our purposes is that NoScript lets you select scripts to run on a page on a case-by-case basis, whereas uBlock Origin delegates those choices to a group of motivated volunteers who maintain whitelists and blacklists to control all of this; you can then override UBO’s stock rules as needed. [ns]: https://noscript.net/ [ub]: https://github.com/gorhill/uBlock/ ## <a id="stats"></a>How Many Users Run with JavaScript Disabled Anyway? There are several studies that have directly measured the web audience to answer this question: * [What percentage of browsers with javascript disabled?][s1] * [How many people are missing out on JavaScript enhancement?][s2] * [Just how many web users really disable cookies or JavaScript?][s3] Our sense of this data is that only about 0.2% of web users had JavaScript disabled while participating in these studies. The Fossil user community is not typical of the wider web, but if we were able to comprehensively survey our users, we’d expect to find an interesting dichotomy. Because Fossil is targeted at software developers, who in turn are more likely to be power-users, we’d expect to find Fossil users to be more in favor of some amount of JavaScript blocking than the average web user. Yet, we’d also expect to find that our user base has a disproportionately high number who run [powerful conditional blocking plugins](#block) in their browsers, rather than block JS entirely. We suspect that between these two forces, the number of no-JS purists among Fossil’s user base is still a tiny minority. [s1]: https://blockmetry.com/blog/javascript-disabled [s2]: https://gds.blog.gov.uk/2013/10/21/how-many-people-are-missing-out-on-javascript-enhancement/ [s3]: https://w3techs.com/technologies/overview/client_side_language/all ## <a id="3pjs"></a>No Third-Party JavaScript in Fossil Fossil does not use any third-party JavaScript libraries, not even very common ones like jQuery. Every bit of JavaScript served by the stock version of Fossil was written specifically for the Fossil project and is stored [in its code repository](https://fossil-scm.org/fossil/file). Therefore, if you want to hack on the JavaScript code served by Fossil and mechanisms like [skin editing][cs] don’t suffice for your purposes, you can hack on the JavaScript in your local instance directly, just as you can hack on its C, SQL, and Tcl code. Fossil is free and open source software, under [a single license][2cbsd]. [2cbsd]: https://fossil-scm.org/home/doc/trunk/COPYRIGHT-BSD2.txt [cs]: ./customskin.md ## <a id="snoop"></a>Fossil Does Not Snoop On You There is no tracking or other snooping technology in Fossil other than that necessary for basic security, such as IP address logging on check-ins. (This is in part why we have no [comprehensive user statistics](#stats)!) Fossil attempts to set two cookies on all web clients: a login session cookie and a display preferences cookie. These cookies are restricted to the Fossil instance, so even this limited data cannot leak between Fossil instances or into other web sites. There is some server-side event logging, but that is done entirely without JavaScript, so it’s off-topic here. ## <a id="uses"></a>Places Where Fossil’s Web UI Uses JavaScript The remainder of this document will explain how Fossil currently uses JavaScript and what it does when these uses are blocked. ### <a id="timeline"></a>Timeline Graph Fossil’s [web timeline][wt] uses JavaScript to render the graph connecting the visible check-ins to each other, so you can visualize parent/child relationships, merge actions, etc. We’re not sure it’s even possible to render this in static HTML, even with the aid of SVG, due to the vagaries of web layout among browser engines, screen sizes, etc. Fossil also uses JavaScript to handle clicks on the graph nodes to allow diffs between versions, to display tooltips showing local context, etc. _Graceful Fallback:_ When JavaScript is disabled, this column of the timeline simply collapses to zero width. All of the information you can get from the timeline can be retrieved from Fossil in other ways not using JavaScript: the “`fossil timeline`” command, the “`fossil info`” command, by clicking around within the web UI, etc. _Potential Workaround:_ The timeline could be enhanced with `<noscript>` tags that replace the graph with a column of checkboxes that control what a series of form submit buttons do when clicked, replicating the current JS-based features of the graph using client-server round-trips. For example, you could click two of those checkboxes and then a button labeled “Diff Selected” to replicate the current “click two nodes to diff them” feature. [wt]: https://fossil-scm.org/fossil/timeline ### <a id="wedit"></a>WYSIWYG Wiki Editor The Admin → Wiki → “Enable WYSIWYG Wiki Editing” toggle switches the default plaintext editor for [Fossil wiki][fw] documents to one that works like a basic word processor. This feature requires JavaScript in order to react to editor button clicks like the “**B**” button, meaning “make \[selected\] text boldface.” There is no standard WYSIWYG editor component in browsers, doubtless because it’s relatively straightforward to create one using JavaScript. _Graceful Fallback:_ Edit your wiki documents in the default plain text wiki editor. Fossil’s wiki and Markdown language processors were designed to be edited that way. [fw]: ./wikitheory.wiki ### <a id="ln"></a>Line Numbering When viewing source files, Fossil offers to show line numbers in some cases. Toggling them on and off is currently handled in JavaScript. ([Example][mainc].) _Workaround:_ Edit the URL to give the “`ln`” query parameter per [the `/file` docs](/help?cmd=/file), or provide a patch to reload the page with this parameter included/excluded to implement the toggle via a server round-trip. [mainc]: https://fossil-scm.org/fossil/artifact?ln&name=87d67e745 ### <a id="sxsdiff"></a>Side-by-Side Diff Mode The default “diff” view is a side-by-side mode. If either of the boxes of output — the “from” and “to” versions of the repo contents for that check-in — requires a horizontal scroll bar given the box content, font size, browser window width, etc., both boxes will usually end up needing to scroll since they should contain roughly similar content. Fossil therefore scrolls both boxes when you drag the scroll bar on one because if you want to examine part of a line scrolled out of the HTML element in one box, you probably want to examine the same point on that line in the other box. _Graceful Fallback:_ Manually scroll both boxes to sync their views. ### <a id="sort"></a>Table Sorting On pages showing a data table, the column headers may be clickable to do a client-side sort of the data on that column. _Potential Workaround:_ This feature could be enhanced to do the sort on the server side using a page re-load. ### <a id="tree"></a>File Browser Tree View The [file browser’s tree view mode][tv] uses JavaScript to handle clicks on folders so they fold and unfold without needing to reload the entire page. _Graceful Fallback:_ When JavaScript is disabled, clicks on folders reload the page showing the folder contents instead. You then have to use the browser’s Back button to return to the higher folder level. [tv]: https://www.fossil-scm.org/fossil/dir?type=tree ### <a id="hash"></a>Version Hashes In several places where the Fossil web UI shows a check-in hash or similar, hovering over that check-in shows a tooltip with details about the type of artifact the hash refers to and allows you to click to copy the hash to the clipboard. _Graceful Fallback:_ When JavaScript is disabled, these tooltips simply don’t appear. You can then select and copy the hash using your browser, make “`fossil info`” queries on those hashes, etc. ### <a id="bots"></a>Anti-Bot Defenses Fossil has [anti-bot defenses][abd], and it has some JavaScript code that, if run, can drop some of these defenses if it decides a given page was loaded on behalf of a human, rather than a bot. _Graceful Fallback:_ You can use Fossil’s anonymous login feature to convince the remote Fossil instance that you are not a bot. Coupled with [the Fossil user capability system][caps], you can restore all functionality that Fossil’s anti-bot defenses deny to random web clients by default. [abd]: ./antibot.wiki [caps]: ./caps/ ### <a id="hbm"></a>Hamburger Menu The default skin includes a “hamburger menu” (☰) which uses JavaScript to show a simplified version of the Fossil UI site map using an animated-in dropdown. _Graceful Fallback:_ Clicking the hamburger menu button with JavaScript disabled will take you to the `/sitemap` page instead of showing a simplified version of that page’s content in a drop-down. _Workaround:_ You can remove this button by [editing the skin][cs] header. ### <a id="clock"></a>Clock Some stock Fossil skins include JavaScript-based features such as the current time of day. The Xekri skin includes this in its header, for example. A clock feature requires JavaScript not only to get the time and update inline on the page once a minute, but also so it displays *in the local time zone.* Since none of this code provides a necessary Fossil feature, the core developers are unlikely to try to make these features work better in the absence of JavaScript. However, we are willing to study patches to make this better. For example, the wall clock displays could include the page load time in the dynamically generated HTML shipped from the remote Fossil server, so that in the absence of JavaScript, you at least get the page generation time, expressed in the server’s time zone. |
> > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # JSON API: X ([⬑JSON API Index](index.md)) Jump to: * [Foo](#foo) * [Bar](#bar) * [Baz](#baz) --- <a id="foo"></a> # Foo <a id="bar"></a> # Bar <a id="baz"></a> # Baz # Footnotes |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: /artifact
([⬑JSON API Index](index.md))
Jump to:
* [Checkin Artifacts (Commits)](#checkin)
* [File Artifacts](#file)
* [Wiki Artifacts](#wiki)
---
<a id="checkin"></a>
# Checkin Artifacts (Commits)
Returns information about checkin artifacts (commits).
**Status:** implemented 201110xx
**Request:** `/json/artifact/COMMIT_HASH`
**Required permissions:** "o" (was "h" prior to 20120408)
**Response payload example: (CHANGED SIGNIFICANTLY ON 20120713)**
```json
{
"type":"checkin",
"name":"18dd383e5e7684e", // as given on CLI
"uuid":"18dd383e5e7684ecee327d3de7d3ff846069d1b2",
"isLeaf":false,
"user":"drh",
"comment":"Merge wideAnnotateUser and jsonWarnings into trunk.",
"timestamp":1330090810,
"parents":[
// 1st entry is primary parent hash:
"3a44f95f40a193739aaafc2409f155df43e74a6f",
// Remaining entries are merged-in branch hashes:
"86f6e675eb3f8761d70d8b82b052ce2b297fffd2",\
"dbf4ecf414881c9aad6f4f125dab9762589ef3d7"\
],
"tags":["trunk"],
"files":[{
"name":"src/diff.c",
// BLOB hashes, NOT commit hashes:
"uuid":"78c74c3b37e266f8f7e570d5cf476854b7af9d76",
"parent":"b1fa7e636cf4e7b6ed20bba2d2680397f80c096a",
"state":"modified",
"downloadPath":"/raw/src/diff.c?name=78c74c3b37e266f8f7e570d5cf476854b7af9d76"
},
...]
}
```
The "parents" property lists the parent hashes of the checkin. The
"parent" property of file entries refers to the parent hash of that
file. In the case of a merge there may be essentially an arbitrary
number. The first entry in the list is the "primary" parent. The primary
parent is the parent which was not pulled in via a merge operation. The
ordering of remaining entries is unspecified and may even change between
calls. For example: if, from branch C, we merge in A and B and then
commit, then in the artifact response for that commit the hash of branch
C will be in the first (primary) position, with the hashes for branches A
and B in the following entries (in an unspecified, and possibly
unstable, order).
Note that the "uuid" and "parent" properties of the "files" entries
refer to raw blob hashes, not commit (a.k.a. check-in) hashes. See also
[the UUID vs. Hash discussion][uvh].
<a id="file"></a>
# File Artifacts
Fetches information about file artifacts.
**FIXME:** the content type guessing is currently very primitive, and
may (but i haven't seen this) mis-diagnose some non-binary files as
binary. Fossil doesn't yet have a mechanism for mime-type mappings.
**Status:** implemented 20111020
**Required permissions:** "o"
**Request:** `/json/artifact/FILE_HASH`
**Request options:**
- `format=(raw|html|none)` (default=none). If set, the contents of the
artifact are included if they are text, else they are not (JSON does
not do binary). The "html" flag runs it through the wiki parser. The
results of doing so are unspecified for non-embedded-doc files. The
"raw" format means to return the content as-is. "none" is the same
as not specifying this flag, and elides the content from the
response.
- DEPRECATED (use format instead): `includeContent=bool` (=false) (CLI:
`--content|-c`). If true, the full content of the artifact is returned
for text-only artifacts (but not for non-text artifacts). As of
20120713 this option is only inspected if "format" is not specified.
**Response payload example: (CHANGED SIGNIFICANTLY ON 20120713)**
```json
{
"type":"file",
"name":"same name specified as FILE_HASH argument",
"size": 12345, // in bytes, independent of format=...
"parent": "hash of parent file blob. Not set for first generation.",
"checkins":[{
"name":"src/json_detail.h",
"timestamp":1319058803,
"comment":"...",
"user":"stephan",
"checkin":"d2c1ae23a90b24f6ca1d7637193a59d5ecf3e680",
"branch":"json",
"state":"added|modified|removed"
},
...],
/* The following "content" properties are only set if format=raw|html */
"content": "file contents",
"contentSize": "size of content field, in bytes. Affected by the format option!",
"contentType": "text/plain", /* currently always text/plain */
"contentFormat": "html|raw"
}
```
The "checkins" array lists all checkins which include this file, and a
file might have different names across different branches. The size and
hash, however, are the same across all checkins for a given blob.
<a id="wiki"></a>
# Wiki Artifacts
Returns information about wiki artifacts.
**Status:** implemented 20111020, fixed to return the requested version
(instead of the latest) on 20120302.
**Request:** `/json/artifact/WIKI_HASH`
**Required permissions:** "j"
**Options:**
- DEPRECATED (use format instead): `bool includeContent` (=false). If
true then the raw content is returned with the wiki page, else no
content is returned.\
CLI: `--includeContent|-c`
- The `--format` option works as for
[`/json/wiki/get`](api-wiki.md#get), and if set then it
implies the `includeContent` option.
**Response payload example:**
Currently the same as [`/json/wiki/get`](api-wiki.md#get).
[uvh]: ../hashes.md#uvh
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: X
([⬑JSON API Index](index.md))
Jump to:
* [Introduction](#intro)
* [Capabilities (Access Rights)](#capabilities)
* [Login](#login)
* [Anonymous User Logins](#login-anonymous)
* [Logout](#logout)
* [Whoami](#whoami)
---
<a id="intro"></a>
# Introduction
**FIXME:** Ross found a bug:
[](/info/479aadb1d2645601)
(sending the authToken via `POST.envelope` isn't working. It should be.)
The authentication-related operations are described in this section
(ordered alphabetically by operation name).
The JSON API ties in to/piggybacks on fossil's existing cookie-based
login mechanism, but we must also keep in mind that not all JSON-using
clients support cookies. Cookie-capable clients can rely on cookies for
authentication, whereas non-cookie-aware clients will need to keep track
of the so-called "auth token" which is created via a login request, and
pass it in with each request (either as a GET parameter or a property of
the top-level POST request envelope) to prove to fossil that they are
authorized to access the requested resources. For most intents and
purposes, the "auth token" and the "login cookie" are the same thing (or
serve the same purpose), and the auth token is in fact just the value
part of the login cookie (which has a project-specific key).
Note that fossil has two conventional user names which can show up in
various response but do not refer to specific people: nobody and
anonymous. The nobody user is anyone who is not logged in. The anonymous
user is logged in but has no persistent user data (no associated user
name, email address, or similar). Normally the guest (nobody) user has
more access restrictions. The distinction between the two is largely
historical - it is a mechanism to keep bots from following the
multitudes of links generated by the HTML interface (especially the ZIP
files), while allowing interested users to do so by logging in as the
anonymous user (which bots have (or *had*, at the time) a harder time
doing because the password is randomly generated and protected from
brute-force attacks by hashing). In the JSON API, the distinction
between anonymous and nobody is not expected to be as prominent as it is
in the HTML interface because the reasons for the distinction don't
apply in *quite* the same ways to the JSON interface. It is possible,
however, that a given repo locks out guest access to, e.g. the wiki or
tickets, while still allowing anonymous (logged in) access.
<a id="capabilities"></a>
# Capabilities (Access Rights)
The "cap" request returns information about the so-called "capabilities"
(access rights) of the currently logged in user. This command is
basically the same as the "whoami" command, but returns capabilities in
a more exanded form (same data, different representation) and does not
include the authToken in the response.
TODO: consider combining this and [whoami](#whoami) into a single
whoami response. We don't need both. We also don't really need the
permissionFlags member - the same info is already \[in a more cryptic form\]
in the capabilities string.
**Status:** implemented 201109xx.
**Required privileges:** none
**Request:** `/json/cap`
In CLI mode, permissions are not used/honored, and this command will
report that the caller has all permissions (which he effectively does).
**Response payload example:**
```json
{
"userName":"json-demo",
"capabilities":"hgjorxz", /* raw fossil permissions list */
"permissionFlags":{ /* Same info in a somewhat friendlier form */
"setup": false,
"admin": false,
"delete": false,
"password": false,
"query": false,
"checkin": false,
"checkout": true,
"history": true,
"clone": false,
"readWiki": true,
"createWiki": false,
"appendWiki": false,
"editWiki": false,
"readTicket": true,
"createTicket": false,
"appendTicket": false,
"editTicket": false,
"attachFile": false,
"createTicketReport": false,
"readPrivate": false,
"zip": true,
"xferPrivate": false
}
}
```
**FIXME:** several new permissions have been added to fossil since
this API was implemented.
<a id="login"></a>
# Login
**Status:** implemented 20110915 (anonymous login 20110918)
**Required privileges:** none
**Request:** (see below for Anonymous user special case)
- `/json/login?name=...&password=...`
- `/json/login?n=...&p=...` (for symmetry with existing login mechanism)
Or `POST.payload`: `{ "name": ..., "password": ...}`
(POST is highly preferred because it keeps the password out of web
server logs!)
**Response payload example:** (structure changed 20111001)
```json
{
"authToken":"...",
"name":"json-demo",
"capabilities":"hgjorxz",
"loginCookieName": "fossil-XXXXX" /*project-specific cookie name*/
/* TODO: add authTokenExpiry timestamp (cookie expiry time) */
}
```
We "should" be able to inherit fossil's `REMOTE_USER` handling without
any special support, but that is untested so far. (If you happen to test
this, please update this doc with (or otherwise report) your results!)
The response *also* sets the conventional fossil login cookie (for
clients which can make use of cookies), using fossil's existing
mechanism for this.
Further requests which require authentication must include the
`authToken` (from the returned payload value) in the request (or it must
be available via fossil's standard cookie) or access may (depending on
the request) be denied. The `authToken` may optionally be set in the
request envelope or as a GET parameter, and it *must* be given if the
request requires restricted access to a resource. e.g. if reading
tickets is disabled for the guest user then all non-guest users must
send authentication info in their requests in order to be able to fetch
ticket info.
Cookie-aware clients should send the login-generated cookie with each
request, in which case they do not need explicitly include the
`authToken` in the JSON envelope/GET arguments. If submitted, the
`authToken` is used, otherwise the cookie, if set, is used. Note that
fossil uses a project-dependent cookie name in order to help thwart
attacks, so there is no simple mapping of cookie *name* to auth
token. That said, the cookie's *value* is also the auth token's value.
> Special case: when accessing fossil over a local server instance
which was started with the `--localauth` flag, the `authToken` is ignored
(neither validated nor used for any form of authentication).
<a id="login-anonymous"></a>
## Anonymous User Logins
The Anonymous user requires special handling because he has a random
password.
First fetch the password and the so-called "captcha seed" via this
request:
`/json/anonymousPassword`
It will return a payload in the form:
```json
{
"seed": a_32_bit_unsigned_integer,
"password": "1234abcd" /*hexadecimal STRING*/
}
```
The "seed" and "password" values of the response payload must be set as
the "anonymousSeed" and "password" fields (respectively) of the
subsequent login request. The login request is identical to
non-anonymous login except that extra "anonymousSeed" property is
required.
The password value *may* be time-limited, and *may* eventually become
invalidated due to old age. This is unspecified.
***Potential***** (low-probability) bug regarding the seed value:** from
what i hear, some unusual JSON platforms don't support full 32-bit
precision. If absolutely necessary we could chop off a bit or two from
the seed value (*if* it ever becomes a problem and if DRH blesses it).
Or we could just make it a double.
<a id="logout"></a>
# Logout
**Status:** implemented 20110916
**Required privileges:** none, but must be logged in
**Request:**
- `/json/logout?authToken=...token fetched by /login`
Or: set the `authToken` property of the POST envelope (as opposed to the
`POST.payload`)
Or: fossil's normal cookie mechanism is the fallback for the auth token.
**Response payload:** The same as the "whoami" response, containing the
info for the "nobody" user.
This request requires the authentication token, and subsequent logouts
without an intervening login will fail with the "auth token not
provided" error. In effect this request removes the login entry from the
user account, making the token invalid for future requests. In HTTP
mode, on success fossil's login cookie is unset by this call.
<a id="whoami"></a>
# Whoami
This request fetches the current user's login name, capabilities, and
auth token. This can be used to check whether a login is active when the
client has not explicitly logged in (e.g. was logged in automatically
via a pre-existing cookie).
**Status:** implemented 20110922
**Required privileges:** none
**Request:** `/json/whoami`
**Response payload example:**
```json
{
"name": "nobody",
"capabilities": "o",
"authToken": "fossil auth token (only for logged-in users)"
}
```
The reason authToken is included in the response is because it gives
client-side JavaScript code a way of fetching/checking for the auth
token at app startup. The token is normally sent as a cookie but parsing
the cookies in the browser is tedious, and fossil has a
project-dependent cookie name (which complicates parsing a bit). If
client code digs the cookie out of the browser, the app still wouldn't
know if the token is still valid, whereas whoami won't (or shouldn't!)
return an expired auth token. If the request does not include
authentication info (via the cookie, GET param, or request envelope)
then the response will not contain the authToken property and the user's
name will be "nobody".
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: /branch
([⬑JSON API Index](index.md))
Jump to:
* [Branch List](#list)
* [Create Branch](#create)
---
<a id="list"></a>
# Branch List
**Status:** implemented, at least in draft form, on 20110921.
**Required privileges:** "o"
**Request:** `/json/branch/list`
**Response payload example:**
```json
{
"range":"closed",
"current":"json", /* only when there is a local opened checkout */
"branches":[
"artifact_description",
"bch",
"ben-changes-report",
"ben-safe-make",
"ben-security",
"ben-testing",
…
]
}
```
*Potential* TODO: add "tip" property which names the most recently
modified branch? (How to get this?)
HTTP GET/`POST.payload` options:
- `range`: a string in the set ("open", "closed", "all"),
case-sensitive, but only the first letter is actually evaluated.
Default="open". Only branches with this state are returned.
CLI mode options (same semantics as HTTP equivalents), must come last on
the CLI:
- `-r|--range all|closed|open`
- `-a` (equivalent to `-r all`)
- `-c` (equivalent to `-r closed`). Only one of `-a`/`-c` may be specified,
and if both are specified then which one takes precedence is
unspecified.
<a id="create"></a>
# Create Branch
**Status:** implemented 20111002
**Required privileges:** "w"
**Request:** `/json/branch/create`
**Request options:**
- `name=string` (REQUIRED) Name of new branch
- `basis=string` (default=trunk) Name of parent branch to branch from.
- `bgColor=string` (default=something ugly) In `#RRGGBB` form. (FIXME:
change the default to use fossil's random bgcolor technique.)
- `private=bool` (default=false) Determines whether the branch is
private or not.
**Response payload example:**
```json
{
"name":"my-branch",
"basis":"my-other-branch",
"uuid":"de8115db4ce388ed8d0af666ae7d90e1410be4ca",
"isPrivate":true,
"bgColor":"#ff0000"
}
```
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: /status
([⬑JSON API Index](index.md))
# Status of Local Checkout
**Status:** implemented 20130223
**Required permissions:** n/a (local access only)
**Request:** `/json/status`
This command requires a local checkout and is analog to the "fossil
status" command.
**Request Options:** currently none.
Payload example:
```json
{
"repository":"/home/stephan/cvs/fossil/fossil.fsl",
"localRoot":"/home/stephan/cvs/fossil/fossil/",
"checkout":{
"uuid":"b38bb4f9bd27d0347b62ecfac63c4b8f57b5c93b",
"tags":["trunk"],
"datetime":"2013-02-22 17:34:19 UTC",
"timestamp":1361554459
},
/* "parent" info is currently missing. */
"files":[
{"name":"src/checkin.c", "status":"edited"}
...],
"errorCount":0 /* see notes below */
}
```
Notes:
- The `checkout.tags` property follows the framework-wide convention
that the first tag in the list is the primary branch and any others
are secondary.
- `errorCount` is +1 for each missing file. Conflicts are not treated as
errors because the CLI 'status' command does not treat them as such.
- TODO: Info for the parent version is currently missing.
- TODO: "merged with" info for the checkout is currently missing.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: /config
([⬑JSON API Index](index.md))
Jump to:
* [Get Config](#get)
* [Set Config](#set)
---
<a id="get"></a>
# Fetch Config
**Status:** Implemented 20120217
**Required permissions:** "s"
**Request:** `/json/config/get/Area[/Area2/...AreaN]`
Where "Area" can be any combination of: *skin*, *ticket*, *project*,
*all*, or *skin-backup* (which is not included in "all" by default).
**Response payload example:**
```json
{
"ignore-glob":"*~",
"project-description":"For testing Fossil's JSON API.",
"project-name":"fossil-json-tests"
}
```
<a id="set"></a>
# Set/Modify Config
Not implemented.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: /diff
([⬑JSON API Index](index.md))
# Diffs
**Status:** implemented 20111007
**Required permissions:** "o"
**Request:** `/json/diff[/version1[/version2]]`
**Request options:**
- `v1=string` Is the "from" version. It may optionally be the first
positional parameter/path element after the command name.
- `v2=string` Is the "to" version. It may optionally be the first
positional parameter/path element after the v1 part.
- `context=integer` (default=unspecified) Defines the number of context
lines to display in the diff.\
CLI: `--context|-c`
- `sbs=bool` (default=false) Generates "side-by-side" diffs, but their
utility in JSON mode is questionable.
- `html=bool` (default=false) causes the output to be marked up with
HTML in the same manner as it is in the HTML interface.
**Response payload example:**
```json
{
"from":"7a83a5cbd0424cefa2cdc001de60153aede530f5",
"to":"96920e7c04746c55ceed6e24fc82879886cb8197",
"diff":"@@ -1,7 +1,7 @@\\n-C factored\\\\sout..."
}
```
TODOs:
- Unlike the standard diff command, which apparently requires a commit
hash, this one diffs individual file versions. If a commit hash is
provided, a diff of the manifests is returned. (That should be
considered a bug - we should return a combined diff in that case.)
- If hashes from two different types of artifacts are given, results
are unspecified. Garbage in, garbage out, and all that.
- For file diffs, add the file name(s) to the response payload.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: /dir
([⬑JSON API Index](index.md))
# Directory Listing
**Status:** implemented 20120316
**Required privileges:** "o". Was "h" prior to 20120720, but the HTML
version of /dir changed permissions and this API was modified to match
it.
**Request:** `/json/dir`
Options:
- `checkin=commit` (use "tip" for the latest). If checkin is not set
then all files from all versions of the tree are returned (but only
once per file - not with complete version info for each file in all
branches).\
CLI: `--checkin|-ci CHECKIN`
- `name=subdirectory` name. To fetch the root directory, don't pass this
option, or use an empty value or "/".\
CLI: use `--name|-n NAME` or pass it as the first argument after
the `dir` subcommand.
**Response payload example:**
```json
{
"name":"/", /* dir name */
"uuid":"ac6366218035ed62254c8d458f30801273e5d4fc",
"checkin":"tip",
"entries":[{
"name": "ajax", /* file name/dir name fragment */
"isDir": true, /* only set for directories */
/* The following properties are ONLY set if
the 'checkin' parameter is provided.
*/
"uuid": "..." /*only for files, not dirs*/,
"size": number,
"timestamp": number
},...]
}
```
The checkin property is only set if the request includes it. The
entry-specific uuid and size properties (e.g. `entries[0].uuid`) are
only set if the checkin request property is set and they refer to the
latest version of that file for the given checkin. The `isDir` property is
only set on directory entries.
This command does not recurse into subdirectories, though it "should be"
simple enough to add the option to do so.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: /finfo
([⬑JSON API Index](index.md))
# File Information
**Status:** implemented 2012-something, but output structure is likely
to change.
**Required privileges:** "o"
**Request:** `/json/finfo?name=path/to/file`
Options:
- `name=string`. Required. Use the absolute name of the file, including
any directory parts, and without a leading slash. e.g.
`"path/to/my.c"`.\
CLI mode: `--name` or positional argument.
- `checkin=string`. Only return info related to this specific checkin,
as opposed to listing all checkins. If set, neither "before" nor
"after" have any effect.\
CLI mode: `--checkin|-ci`
- `before=DATETIME` only lists checkins from on or before this time.\
CLI mode: `--before|-b`
- `after=DATETIME` only lists checkins from on or before this time.
Using this option swaps the sort order, to provide reasonable
behaviour in conjunction with the limit option.\
Only one of "before" and "after" may be specified, and if both are
specified then which one takes precedence is unspecified.\
CLI mode: `--after|-a`
- `limit=integer` limits the output to (at most) the given number of
associated checkins.\
CLI mode: `--limit|-n`
**Response payload example (very likely to change!):**
```json
{
"name":"ajax/i-test/rhino-shell.js",
"checkins":[{
"checkin":"6b7ddfefbfb871f793378d8f276fe829106ca49b",
"uuid":"2b735676d55e3d06d670ffbc643e5d3f748b95ea",
"timestamp":1329514170,
"user":"viriketo",
"comment":"<...snip...>",
"size":6293,
"state":"added|modified|removed"
},…]
}
```
**FIXME:** there is a semantic discrepancy between `/json/artifact`'s
`payload.checkins[N].uuid` and this command's.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: Misc. APIs
([⬑JSON API Index](index.md))
Some operations simply don't fit into a specific category (well, none
except "misc")...
Jump to:
* [Global State ("g")](#g)
* [Rebuild Repository](#rebuild)
* [Result Code Descriptions](#result-codes)
---
<a id="g"></a>
# Global State ("g")
Fossil's internal state is maintained in a global object called "g". And
thus this command is named "g"...
**Status:** implemented 20111009
**Required permissions:** "a" or "s"
**Request:** `/json/g`
**Response payload example:** this is a debugging-only request, and has
no stable response payload structure. The result object contains all
kinds of details gleaned from the fossil environment.
Easter egg: this output can be added to ANY response by passing the
`debugFossilG` boolean in the POST envelope or GET parameters, or as the
`--json-debug-g` flag in CLI mode. This requires admin or setup
privileges, though, and it is silently ignored for other users.
<a id="rebuild"></a>
# Rebuild Repository
This request does the same as the "fossil rebuild" command, rebuilding
the repo-internal structure. This is often required after upgrading the
fossil binary on a system. There "are very probably" cases where calling
this over HTTP will not work (e.g. if the user table has changed enough
that the access rights cannot be validated without a rebuild, i.e. a
chicken/egg scenario). Another consideration is that this request can
take a long time to run - rebuilding the fossil repo on my laptop takes
about 21 seconds, which is likely longer than the timeout set on an XHR
request, meaning that the rebuild transaction will fail. It can safely
be run in CLI mode, where timeouts are not normally a concern. As a
preliminary benchmark: rebuilding the fossil repo (as of late 2011)
takes just over 21 seconds on my 32-bit 1.6GHz netbook. That said, most
repos are much smaller and rebuild in under a few seconds.
**Status:** implemented 20110929
**Required privileges:** "a"
**Request:** `/json/rebuild`
Requires admin access rights.
**Response payload:** none (response envelope `resultCode` reports failure).
Potential TODO: return the amount of time it took to rebuild.
<a id="result-codes"></a>
# Result Code Descriptions
This request returns the full list of result codes documented for
Fossil's JSON API. Each result code is returned as an object containing
metadata about the result code.
**Status:** implemented 20111006
**Required permissions:** none
**Request:** `/json/resultCodes`
**Response payload example:**
```json
[{
"resultCode":"FOSSIL-1000",
"cSymbol":"FSL_JSON_E_GENERIC",
"number":1000,
"description":"Generic error"
},
… many more objects with the same 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 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 |
# JSON API: /query
([⬑JSON API Index](index.md))
# SQL Query
**Status:** implemented 20111008
**Required privileges:** "a" or "s"
**Request:** `/json/query`
Potential FIXME: restrict this to queries which return results, as opposed
to those which may modify data.
Options:
- `sql=string` The SQL code to run. It is expected that it be a SELECT
statement, but that is not enforced. This parameter may be set as a
POST.payload property, as the POST.payload itself, GET, or as a
positional parameter coming after the command name (CLI and HTTP
modes, though the escaping would be unsightly in HTTP mode).
- `format=string` (default="o"). "o" specifies that each result row
should be in the form of key/value pairs (o=object). "a" means each
row should be an array of values.
**Example request:**
POST to: `/json/query`
```json
{
"authToken": "...",
"payload": {
"sql": "SELECT * FROM reportfmt",
"format": "o"
}
}
```
**Response payload example:** (assuming the above example)
```
{
"columns":[
"rn",
"owner",
"title",
"mtime",
"cols",
"sqlcode"
],
"rows":[
{
"rn":1,
"owner":"drh",
"title":"All Tickets",
"mtime":1303870798,
},
…
]
}
```
The column names are provided in a separate field is because their order
is guaranteed to match the order of the query columns, whereas object
key/value pairs might get reordered (typically sorted by key) when
travelling through different JSON implementations. In this manner,
clients can e.g. be sure to render the columns in the proper
(query-specified) order.
When in "array" mode the "rows" results will be an array of arrays. For
example, the above "rows" property would instead look like:
`[ [1, "drh", "All Tickets", 1303870798, … ], … ]`
Note the column *names* are never *guaranteed* to be exactly as they
appear in the SQL *unless* they are qualified with an AS, e.g. `SELECT
foo AS foo...`. When generating reports which need fixed column names, it
is highly recommended to use an AS qualifier for every column, even if
they use the same name as the column. This is the only way to guaranty
that the result column names will be stable. (FYI: that behaviour comes
from sqlite3, not the JSON bits, and this behaviour *has* been known to
change between sqlite3 versions (so this is not just an idle threat of
*potential* future incompatibility).)
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: /stat
([⬑JSON API Index](index.md))
# Repository Stats
**Status:** implemented
**Required privileges:** "o"
**Request:** `/json/stat`
**Response payload example:** (fields marked with `*` are omitted in
"brief" mode)
```json
{
"projectName":"Fossil",
"projectDescription":"Fossil SCM", /* added 20120217 */
"repositorySize":24464384,
* "blobCount":13612,
* "deltaCount":9348,
* "uncompressedArtifactSize":589205834,
* "averageArtifactSize":43292,
* "maxArtifactSize":4620758,
* "compressionRatio":"24:1",
* "checkinCount":3150,
* "fileCount":456,
* "wikiPageCount":23,
* "ticketCount":940,
"ageDays":1512,
"ageYears":4.139744,
"projectCode":"25d3a4b83202c0d616a5ed17334f180dac4434dc",
"compiler":"gcc-4.1.2 20080704 (Red Hat 4.1.2-50)",
"sqlite":{
"version":"2011-09-04 01:27:00 [6b657ae750] (3.7.8)",
"pageCount":23891,
"pageSize":1024,
"freeList":2705,
"encoding":"UTF-8",
"journalMode":"delete"
}
}
```
**Options:**
- "Full detail" mode:\
**HTTP/payload parameter:** full=bool\
**CLI MODE:** -f|--full with no value.\
If true then all properties are included, else certain properties
are omitted from the payload (because they take a relatively long
time to calculate).\
**TODO:** rename this to verbose, for consistency.\
**Default=false**. *This is in contrast to the HTML interface*,
which defaults to full detail mode. Testing shows stat to have a
relatively high per-call cost/run time, so it defaults
to "brief" mode by default. Full-detail mode can, on slow hardware,
take half a minute to respond, whereas non-full mode takes well
under one second.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: /tag
([⬑JSON API Index](index.md))
Jump to:
* [Add](#add)
* [Cancel](#cancel)
* [Find](#find)
* [List](#list)
---
<a id="add"></a>
# Add Tag
**Status:** implemented 20111006
**Required permissions:** "i"
**Request:** `/json/tag/add[/name[/checkin[/value]]]`
**Request options:**
- `name=string` The tag name.
- `checkin=string` The checkin to tag. May be a symbolic branch name.
- raw=bool (=false) If true, then the name is set as it is provided by
the client, else it gets "sym-" prefixed to it. Do not use this
unless you really know what you're doing.
- `value=string` (=null) An optional value. While tags *may* have values
in fossil, it is unusual for them to have a value. (This probably
has some interesting uses in custom UIs.)
- `propagate=bool` (=false) Sets the tag to propagate to all descendants
of the given checkin.
In CLI modes, the name, checkin, and value parameters may optionally be
supplied as positional parameters (in that order, after the command
name). In HTTP mode they may optionally be the 4th-6th path elements or
specified via GET/`POST.envelope` parameters.
**Response payload example:**
```json
{
"name":"my-tag",
"value":"abc",
"propagate":true,
"raw":false,
"appliedTo":"626ab2f3743543122cc11bc082a0603d2b5b2b1b"
}
```
The `appliedTo` property is the hash of the check-in to which the tag was
applied. This is the "resolved" version of the check-in name provided by
the client.
<a id="cancel"></a>
# Cancel Tag
**Status:** implemented 20111006
**Required permissions:** "i"
**Request:** `/json/tag/cancel[/name[/checkin]]`
**Request options:**
- `name=string` The tag name. May optionally be provided as the 4th path
element.
- `checkin=string` The checkin to untag. May be a symbolic branch name.
May optionally be provided as the 5th path element.
In CLI modes, the name and checkin parameters may optionally be supplied
as positional parameters (in that order, after the command name) or
using the `-name NAME` and `-checkin NAME` options. In HTTP mode they may
optionally be the 4th and 5th path elements.
**Response payload:** none (resultCode indicates failure)
<a id="find"></a>
# Find Tag
Fetches information about artifacts having a particular tag.
Achtung: the output of this response is based on the HTML-mode
implementation, but it's not yet certain that it's exactly what we want
for JSON mode. The request options and response format may change.
**Status:** implemented 20111006
**Required permissions:** "o"
**Request:** `/json/tag/find[/tagName]`
The response format differs somewhat depending on the options:
- `name=string` The tag name to search for. Can optionally be the 3rd
path element.
- `limit=int` (defalt=0) Limits the number of results (0=no limit).
Since they are ordered from oldest to newest, the newest N results
will be returned.
- `type=string` (default=`*`) Searches only for the given type of
artifact (using fossil's conventional type naming: ci, e, t, w.
- `raw=bool` (=false) If enabled, the response is an array of hashes
of the requested artifact type; otherwise,
it is an array of higher-level objects. If this is
true, the "name" property is interpretted as-is. If it is false, the
name is automatically prepended with "sym-" (meaning a branch).
(FIXME: the current semantics are confusing and hard to remember.
Re-do them.)
**Response payload example, in RAW mode: (expect this format to change
at some point!)**
```json
{
"name":"sym-trunk"
"raw":true,
"type":"*",
"limit":2,
"artifacts":[
"a28c83647dfa805f05f3204a7e146eb1f0d90505",
"dbda8d6ce9ddf01a16f6c81db2883f546298efb7"
]
}
```
Very likely todo: return more information with that (at least the
artifact type and timestamp). Once the `/json/artifact` family of bits is
finished we could use that to return artifact-type-dependent values
here.
**Response payload example, in non-raw mode:**
```
{
"name":"trunk",
"raw":false,
"type":"*",
"limit":1,
"artifacts":[{
"uuid":"4b0f813b8c59ac8f7fbbe33c0a369acc65407841",
"timestamp":1317833899,
"comment":"fixed [fc825dcf52]",
"user":"ron",
"eventType":"checkin"
}]
}
```
<a id="list"></a>
# List Tags
**Status:** implemented 20111006
**Required permissions:** "o"
**Request:** `/json/tag/list[/checkinName]`
Potential fixme: we probably have too many different response formats
here. We should probably break this into multiple subcommands.
The response format differs somewhat depending on the options:
- `checkin=string` Lists the tags only for the given CHECKIN (can be a
branch name). If set, includeTickets is ignored (meaningless in this
combination). This option can be set as either a GET/`POST.payload`
option, as the last element of the request path, e.g.
`/json/tag/list/MYBRANCH` *or* with `POST.payload` set to a string
value.
- `raw=bool` (default=false) uses "raw" tag names
- `includeTickets=bool` (default=false) Determines whether `tkt-` tags
are included. There is one of these for each ticket, so there can be
many of them (over 900 in the main fossil repo as of this writing).
**Response format when raw=false and no checkin is specified:**
```json
{
"raw":false,
"includeTickets":false,
"tags":[
"bgcolor",
"branch",
"closed",
…all tag names...
]
}
```
Enabling the `raw` option will leave the internal `sym-` prefix on tags
which have them but will not change the structure. If `includeTickets` is
true then `tkt-` entries (possibly very many!) will be included in the
output, else they are elided.
**General notes:**
The `tags` property will be null if there are no tags, every non-empty
repo has at least one tag (for the trunk branch).
**Response format when raw=false and checkin is specified:**
```json
{
"raw":false,
"tags":{
"json":null,
"json-multitag-test":null
}
}
```
The `null`s there are the tag values (most tags don't have values).
If `raw=true` then the tags property changes slightly:
```json
{
"raw":true,
"tags":{
"branch":"json",
"sym-json":null,
"sym-json-multitag-test":null
}
}
```
TODO?: change the tag values to objects in the form
`{value:..., tipUuid:string, propagating:bool}`.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: Tickets
([⬑JSON API Index](index.md))
Jump to:
* [Ticket Reports](#reports)
* [Fetch a Report](#report-get)
* [List Reports](#report-list)
* [Run a Report](#report-run)
---
# Tickets
This API is incomplete. It is missing at least the following features:
- Content for a given ticket ID
- History for a given ticket ID?
- An option to enable/disable the generation of hyperlinks, as links
won't be useful in most non-browser clients.
<a id="reports"></a>
# Ticket Reports
<a id="report-get"></a>
## Fetch a Report
**Status:** implemented 20111008
**Required privileges:** "t" (the thinking being that only those
permitted to create reports should be able to read their SQL code)
**Request:** `/json/report/get[/REPORT_NUMBER]`
**Request options:**
- `report=number` The report number to fetch.\
CLI: `-report|-r` \
(Design note: `--number/-n` was not used because that parameter has a
different meaning (limit response count) in several commands.) May
optionally be defined via the 4th GET path element or CLI arg.
**Response payload example:**
```json
{
"report":1,
"owner":"drh",
"title":"All Tickets",
"timestamp":"112443570187200",
"columns":"#ffffff Key:\r\n#f2dcdc Active\r\n...",
"sqlCode":"..."
}
```
<a id="report-list"></a>
## List Reports
**Status:** implemented 20111008
**Required privileges:** "r" or "n"
**Request:** `/json/report/list`
**Response payload example:**
```json
[
{
"report":1,
"title":"All Tickets",
"owner":"drh"
},
…
]
```
<a id="report-run"></a>
## Run a Report
**Status:** implemented 20111008
**Required privileges:** "r" or "n"
**Request:** `/json/report/run[/REPORT_NUMBER]`
Request options:
- `report|-r=int` Specifies which report to run. May optionally be
supplied as the 4th CLI arg or URL path element.
- `format|-f=string` (default="o") Specifies "array" or "object" output
format.
**Response payload example:**
```json
{
"report":1,
"title":"All Tickets",
"sqlcode": "only set if requester has 't' privileges.",
"columnNames":[ … list of column names … ],
"tickets":[
{
"bgcolor":"#cfe8bd",
"#":"fc825dcf52",
"timestamp":"112443570187200",
"type":"Code_Defect",
"status":"Fixed",
"subsystem":null,
"title":"\"config pull all\" asks to approve ssl cert"
},
…
]
}
```
Note that the column names of ticket reports are determined by the
reports themselves, and not C code. That means that we cannot return a
standard set of column names here. Fossil requires certain column naming
conventions for purposes of styling the HTML interface, e.g. the "\#"
column is always the uuid of the record and "bgcolor" (note the
different casing than bgColor used throughout the rest of this API!) has
a specific meaning to the HTML report browser. Fossil also allows the
tickets to be extended with client-specified fields, so we cannot
generically make these results fit into the API-wide naming scheme. Full
details are here:
[](/doc/trunk/www/custom_ticket.wiki)
and:
[](/rptsql?rn=1)
(That one may require non-default permission.)
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: /timeline
([⬑JSON API Index](index.md))
Jump to:
* [Introduction](#intro)
* [Branch Timeline](#branch)
* [Technote (formerly Event) Timeline](#technote)
* [Ticket Timeline](#ticket)
* [Wiki Timeline](#wiki)
---
<a id="intro"></a>
# Introduction
These requests return overview-level information about various types of
changes. The response payload differs for each artifact type, and the
current structures are almost certainly not "final" (e.g. we are still
undecided on how/whether to handle artifact links within commit messages
and whatnot).
By default the entries are returned in chronological order from newest
to oldest, but some options might change that.
FIXME (20120623): we have some inconsistent `type` vs. `eventType` in
the result sets. `type` is the current preferred choice (and it seems
unlikely that `eventType` is actually used in any client code). We
don't actually need either one (but a use for `type` is easily
envisioned), and we may get rid of both.
**Common request options (via CLI, GET or POST.payload):**
- `limit=integer` limits the number of entries in the response. Default
is unspecified (but is "quite possibly 20 or so"). A limit value of
0 disables any limit, fetching all entries (which can take a really
long time and might overload clients which have very limited
memory).\
CLI mode: `--limit|-n #`
- `after="YYYY-MM-DD[ HH:mm:ss]"` limits the search to items on or
after the given date string. Reverses the normal timeline sort
order. Alias: "a". Only one of "after" or "before" can be used, and
if both are specified then which one takes precedence is
unspecified.\
CLI mode: `--after|-a "DATE[ TIME]"`
- `before="YYYY-MM-DD[ HH:mm:ss]"` limits the search to items on or
before the given date string.\
CLI mode: `--before|-b "DATE[ TIME]"
- TODOs, still to be ported from the HTML-mode timeline:
- circa=DATETIME
- tag=string
- related=tag name
- string=search string
<a id="branch"></a>
# Branch Timeline
**Status:** partially implemented but undocumented because the utility
of the current impl is under question. It also doesn't understand most
of the common timeline options.
<a id="checkin"></a>
# Checkin Timeline
**Status:** implemented 201109xx
**Required privileges:** "o"
**Request:** `/json/timeline/checkin`
**Response payload example:**
```json
{
"limit": number, /* if not set, all records are returned */
"timeline":[{
"uuid":"be700e84336941ef1bcd08d676310b75b9070f43",
"timestamp":1317094090,
"comment":"Added /json/timeline/ci showFiles to ajax test page.",
"user":"stephan",
"isLeaf":true,
"bgColor":null, /* not quite sure why this is null? */
"type":"ci",
"parents": ["primary parent hash", "...other parent hashes"],
"tags":["json"],
"files":[{
"name":"ajax/index.html",
"uuid":"9f00773a94cea6191dc3289aa24c0811b6d0d8fe",
"parent":"50e337c33c27529e08a7037a8679fb84b976ad0b",
"state":"modified"
}]
},...]
}
```
(Achtung: the `parents` property was called `prevUuid` prior to 20120316.)
The `parents` property lists the checkins which were parents of this
commit. The first entry in the array is the "primary parent" - the one
which was not involved in a merge with the child.
**Request options:**
- `files=bool` toggles the addition of a "files" array property which
contains objects describing the files changed by the commit,
including their hash, previous hash, and state change type
(modified, added, or removed). ([“uuid” here means hash][uvh])\
CLI mode: `--show-files|-f`
- `tag|branch=string` selects only entries with the given tag or "close
to" the given branch. Only one of these may be specified and if both
are specified, which one takes precedence is unspecified. If the
given tag/branch does not exist, an error response is generated. The
difference between the two is subtle - tag filters only on the given
tag (analog to the HTML interface's "r" option) whereas branch can
also return entries from other branches which were merged into the
requested branch (analog to the HTML interface's "b" option). If one
of these is specified, the response payload will contain a "tag"
*or* "branch" property with the tag/branch name given by the client.
<a id="technote"></a>
# Technote (formerly Event) Timeline
**Status:** implemented 20180803
**Required privileges:** "j"
**Request:**
- `/json/timeline/technote`
- DEPRECATED: `/json/timeline/event` (technotes were formerly called `events`)
**Response payload example:**
```json
{
"limit": number, /* if not set, all records are returned */
"timeline":[{
"name":"8d18bf27e9f9ff8b9017edd55afc35701407d418",
"uuid":"b23962c88c123924a77fd663e91af094780d920a",
"timestamp":1478376113,
"comment":"Style update due to [8d880f0bb4]",
"user":"andygoth",
"eventType":"e"
},...]
}
```
The `uuid` of each entry can be passed to `/json/artifact` to fetch the raw
event content.
<a id="ticket"></a>
# Ticket Timeline
**Status:** implemented 201109xx
**Required privileges:** "r" or "o"
**Request:** `/json/timeline/ticket`
**Response payload example:**
```json
{
"limit": number, /* if not set, all records are returned */
"timeline":[{
"uuid":"5065a5da060e181da49a618f8ae5dc245215e95b",
"timestamp":1316511322,
"user":"stephan",
"eventType":"t",
"comment":"Ticket [b64435dba9] <i>How to...</i>",
"briefComment":"Ticket [b64435dba9]: 2 changes",
"ticketUuid":"b64435dba9cceb709bd54fbc5883884d73f93491"
},...]
}
```
**Notice that there are two [hashes][uvh] for tickets** - `uuid` is the change
hash and `ticketUuid` is the actual ticket’s hash. This is an unfortunate
discrepancy vis-a-vis the other timeline entries, which only have one
hash. We may want to swap `uuid` to mean the ticket hash and change `uuid`
to `commitHash`.
<a id="wiki"></a>
# Wiki Timeline
**Status:** implemented 201109xx
**Required privileges:** "j" or "o"
**Requests:**
- `/json/timeline/wiki`
- `/json/wiki/timeline` (alias)
**Response payload example:**
```json
{
"limit": number, /* if not set, all records are returned */
"timeline":[{
"uuid":"4b2026f06eb48eaf187209fcb05ba5438c3b0ef0",
"timestamp":1331351121,
"comment":"Changes to wiki page [Page3]",
"user":"stephan",
"eventType":"w"
},...]
}
```
The `uuid` of each entry can be passed to `/json/artifact` or
`/json/wiki/get?uuid=...` to fetch the raw page and the hash of the
parent version.
[uvh]: ../hashes.md#uvh
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: /user
([⬑JSON API Index](index.md))
Jump to:
* [Get User Info](#get)
* [List Users](#list)
* [Save User](#save)
---
<a id="get"></a>
# Get User Info
**Status:** implemented 20110927.
**Required privileges:** "a" or "s"
**Request:**
- POST `/json/user/get`\
with `POST.payload.name=USERNAME`
- `/json/user/get?name=USERNAME`
**Response payload example:**
```json
{
"uid":1,
"name":"stephan",
"capabilities":"abcdefhgijkmnopqrstuvwxz",
"info":"https://wanderinghorse.net/home/stephan/",
"timestamp":1316122562
}
```
(What does that timestamp field represent, anyway?)
<a id="list"></a>
# List Users
**Status:** implemented 20110927.
**Required privileges:** "a" or "s"
**Request:** `/json/user/list`
**Response payload example:**
```json
[
{
"uid":1,
"name":"stephan",
"capabilities":"abcdefhgijkmnoprstuvwxz",
"info":"",
"timestamp":1316122562
},
... more users...
]
```
<a id="save"></a>
# Save User
Only admin/setup users may modify accounts other than their own.
**Status:** implemented 20111021 *but* it is missing "login group"
support, so changes do not yet propagate to other repos within a group.
**Required privileges:** 'p' or 'a' or 's', depending on the context.
**Request:** `/json/user/save`
All request options must come from the `POST.payload` and/or GET/CLI
parameters (exception: "name" must come from POST.payload or CLI).
GET/CLI parameters take precedence over those in `POST.payload`, the
intention being to use an input file as a template and overriding the
template's defaults via the CLI. The options include:
- `name=string` Specifies the user name to change. When changing a
user's name, the current uid and the new name must be specified.\
**Achtung:** due to fossil-internal ambiguity in the handling of the
"name" parameter, this parameter must come from the `POST.payload`
data or it will not be recognized. In CLI mode it may be specified
with the `--name` flag.
- `uid=int` Specifies the uid to change. At least one of uid or name are
required. A uid of -1 means to create a new user, in which case the
name must be provided.
- `password=string` Optionally changes the user's password. When
renaming existing or creating new users, be sure to always provide a
new password because any old password hash is invalidated by the
name change.
- `info=string` Optionally changes the user's info field.
- `capabilities=string` Optionally changes the user's capabilities
field.
- `forceLogout=bool` (=false, or true when renaming) Optionally clears
any current login info for the current user, which will invalidate
any active session. Requires 'a' or 's' privileges. Intended to be
used when disabling a user account, to ensure that any open session
is invalidated. When a user is renamed this option is implied (and
cannot be disabled) because renaming invalidates any currently
stored auth token (because the old name is part of the hash
equation).
Fields which are not provided in the request will not be modified.
Non-admin/setup users cannot edit other users and may only change their
own data if they have the 'p' (password) privilege.
As of 20120217, users who do not have the setup privilege may neither
change the setup privilege for any user nor edit another user who has
that privilege. That is, only users with setup access may propagate or
remove setup status and accounts with the setup privilege may only be
edited by themselves and other setup users.
**Response payload:** Same as user/get, using the new/saved state of the
modified user.
Example usage from the command line:
```console
$ fossil json user save --name drh --password sqlite3 \
--capabilities "as" --info "DRH"
$ fossil json user save --uid 1 --name richard \
--password fossil \
--info "Previously known as drh"
```
**Warnings:**
- When creating a new user or renaming a user, if no (new) password is
specified in the save request then the user will not be able to log
in because the previous password (for existing users) is hashed
against the old name.
- Renaming a user invalidates any active login token because his old
name is a part of the hash. i.e. the user must log back in with the
new name after being renamed.
**TODOs:**
- Login group support.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: /version
([⬑JSON API Index](index.md))
# Version (a.k.a. HAI)
**Status:** implemented
**Required privileges:** none
**Requests:**
- `/json/version`
- `/json/HAI` (alias borrowed from LOLCATZ jargon)
**Response payload example:**
```json
{
"manifestUuid":"20ff808f9809541d2eca6c49a17d5cbd16e1b93f",
"manifestVersion":"[20ff808f98]",
"manifestDate":"2011-09-09 16:49:23",
"manifestYear":"2011",
"releaseVersion":"1.19",
"releaseVersionNumber":119,
"jsonApiVersion": "YYYYMMDD" // added 20120409
}
```
Those particular payload fields were chosen only because they're defined
in `VERSION.h`. We may want to add other information, but nothing comes to
mind at this time.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: /wiki
([⬑JSON API Index](index.md))
Jump to:
* [Page List](#list)
* [Fetch a Page](#get)
* [Create or Save Page](#create-save)
* [Wiki Diffs](#diffs)
* [Preview](#preview)
* [Notes and TODOs](#todo)
---
<a id="list"></a>
# Page List
Returns a list of all pages, not including their content (which can be
arbitrarily large).
**Status:** implemented 201109xx
**Required privileges:** "j" or "o"
**Request:** `/json/wiki/list`
**Options:**
- `bool verbose` (=false) Changes the results to return much more
information. Added 20120219.
- `glob=wildcard` string (default=`*`). If set, only page names
matching the given wildcard are returned. Added 20120325.\
CLI: `--glob|-g STRING`
- `like=SQL LIKE string` (default=`%`). If set, only page names matching
the given SQL LIKE string are returned. Note that this match is
case-INsensitive. If both glob and like are given then only one will
work and which one takes precedence is unspecified. Added 20120325.\
CLI: `--like|-l STRING`
- `invert=bool` (default=false). If set to a true value, the glob/like
filter has a reverse meaning (pages *not* matching the wildcard are
returned). Added 20120329.\
CLI: `-i/--invert`
**Response payload: format depends on "verbose" option**
Non-verbose mode:
```json
["PageName1",..."PageNameN"]
```
In verbose mode:
```json
[{
"name":"Apache On Windows XP",
"uuid":"a7e68df71b95d35321b9d9aeec3c8068f991926c",
"user":"jeffrimko",
"timestamp":1310227825,
"size":793 /* in bytes */
},...]
```
The verbose-mode output is the same as the [`/json/wiki/get`](#get) output, but
without the content. The size property of each reflects the *byte*
length of the raw (non-HTMLized) page content.
**Potential TODOs:**
- Allow specifying (in the request) a list/array of names, as opposed
to listing all pages. The page count is rarely very high, though, so
an "overload" is very unlikely. (i have one wiki with about 47 pages
in it.)
<a id="get"></a>
# Fetch a Page
Fetches a single wiki page, including content and significant metadata.
**Status:** implemented 20110922, but response format may change.
**Required privileges:** "j" or "o"
**Request:**
- GET: `/json/wiki/get?name=PageName`
- GET: `/json/wiki/get/PageName`
- POST: `/json/wiki/get` with page name as `POST.payload` or
`POST.payload.name`.
**Response payload example:**
```json
{
"name": "Fossil",
"uuid": "...hex string...",
"parent": "uuid of parent (not set for first version of page)",
"user": "anonymous",
"timestamp": 1286143975,
"size": 1906, /* In bytes, not UTF8 characters!
Affected by format option! */
"content": "..."
}
```
**FIXME:** it's missing the mimetype (that support was added to fossil
after this was implemented).
If given no page to load, or if asked to get a page which does not
exist, an error response is generated (a usage- or resource-not-found
error, respectively).
**Options (via CLI/GET/`POST.payload`):**
- `name=string`. The page to fetch. The latest version is fetched
unless the uuid paramter is supplied (in which case name is ignored). \
CLI: `--name|-n string`\
Optionally, the name may be the 4th positional argument/request path element.
- `uuid=string`. Fetches a specific version. The name parameter is
ignored when this is specified.\
CLI: `--uid|-u string`
- `format=string ("raw"|"html"|"none")` (default="raw"). Specifies the
format of the "content" payload value.\
CLI: `--format|-f string` \
The "none" format means to return no content. In that case the size
refers to the same size as the "raw" format.
**TODOs:**
- Support passing an array of names in the request (and change
response to return an array).
<a id="create-save"></a>
# Create or Save Page
**Status:** implemented 20110922.
**Required privileges:** "k" (save) or "f" (create)
**Request:**
- `/json/wiki/create`
- `/json/wiki/save`
These work only in HTTP mode, not CLI mode. (FIXME: now that we can
simulate POST from a file, these could be used in CLI mode.)
The semantic differences between save and create are:
- Save will fail if the page doesn't already exist whereas create will
fail if it does. The createIfNotExists option (described below) can
be used to create new pages using the save operation.
- The content property is optional for the create request, whereas it
is required to be a string for save requests (but it *may* be an
empty string). This requirement for save is *almost* arbitrary, and
is intended to prevent accidental erasing of existing page content
via API misuse.
**Response payload example:**
The same as for [`/json/wiki/get`](#get) but the page content is not
included in the response (only the metadata).
**Request options** (via GET or `POST.payload` object):
- `name=string` specifies the page name.
- `content=string` is the body text.\
Content is required for save (unless `createIfNotExists` is true *and*
the page does not exist), optional for create. It *may* be an empty
string.
- Save (not create) supports a `createIfNotExists` boolean option which
makes it a functional superset of the create/save features. i.e. it
will create if needed, else it will update. If createIfNotExists is
false (the default) then save will fail if given a page name which
does not refer to an existing page.
- **TODO:** add `commitMessage` string property. The fossil internals
don't have a way to do this at the moment (they can as of late 2019).
Since fossil wiki commits have always had the same default commit message, this is not a
high-priority addition. See:\
[](/doc/trunk/www/fileformat.wiki#wikichng)
- **Potential TODO:** we *could* optionally also support
multi-page saving using an array of pages in the request payload:\
`[… page objects … ]`
<a id="diffs"></a>
# Wiki Diffs
**Status:** implemented 20120304
**Required privileges:** "h"
**Request:**
- `/json/wiki/diff[/version1_UUID/version2_UUID]`
**Response payload example:**
```json
{
"v1":"e32ccdcda59e930c77c1e01cebace5d71253f621",
"v2":"e15992f475760cdf3a9564d8f88cacb659ab4b07",
"diff":"@@ -1,4 +1,9 @@...<SNIP>..."
}
```
**Options:**
- `v1=uuid` and `v2=uuid` specify the two versions to diff, and are
required parameters. They may optionally be specified as the two
URL/CLI parameters following the "diff" sub-command/path.
This command does not verify that both UUIDs actually refer to the same
page name, but do verify that they refer to wiki content.
Trivia: passing the same UUIDs to the `/json/diff` command will produce
very different results - that one diffs the manifests of the commits.
**TODOs:**
- Add options for changing the format of the diff, e.g. side-by-side
and enabling the HTML markup supported by the main fossil HTML GUI.
- Potentially do a name comparison to verify that the diff is against
the same page. That said, when "renaming" pages it might be useful
to diff two different pages.
<a id="preview"></a>
# Preview
**Status:** implemented 20120310
**Required privileges:** "k" (to limit its use to only those who can
edit wiki pages). This limitation is up for debate/reconsideration.
**Request:**
- `/json/wiki/preview`
This command wiki-processes arbitrary text sent from the client. To help
curb potential abuse, its use is restricted to those with "k" access
rights.
The `POST.payload` property must be a string containing Fossil wiki
markup. The response payload is also a string, but contains the
HTML-processed form of the string. Whether or not "all HTML" is allowed
depends on site-level configuration options, and that changes how the
input is processed.
Note that the links in the generated page are for the HTML interface,
and will not work as-is for arbitrary JSON clients. In order to
integrate the parsed content with JSON-based clients the HTML will
probably need to be post-processed, e.g. using jQuery to fish out the
links and re-map wiki page links to a JSON-capable page handler.
**TODO**: Update this to accept the other two wiki formats (which
didn't exist when this API was implemented): markdown and plain text
(which gets HTML-ized for preview purposes). That requires changing
the payload to an object, perhaps simply submitting the same thing as
`/json/save`. There's no reason we can't support both call forms.
<a id="todo"></a>
# Notes and TODOs
- When server-parsing the wiki content, the generated
intra-wiki/intra-site links will only be useful in the context of
the original fossil UI (or a work-alike), not arbitrary JSON
client apps.
Potential TODOs:
- `/wiki/history` analog to the [](/whistory) page.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: General Conventions
([⬑JSON API Index](index.md))
Jump to:
* [JSON Property Naming](#property-names)
* [HTTP GET Requests](#http-get)
* [HTTP POST Requests](#http-post)
* [POST Request Envelope](#request-envelope)
* [Request Parameter Data Types](#request-param-types)
* [Response Envelope](#response-envelope)
* [HTTP Response Headers](#http-response-header)
* [CLI vs. HTTP Mode](#cli-vs-http)
* [Simulating POSTed data](#simulating-post-data)
* [Indentation/Formatting of JSON Output](#json-indentation)
* [JSONP](#jsonp)
* [API Result Codes](#result-codes)
---
<a id="property-names"></a>
# JSON Property Naming
Since most JSON usage conventionally happens in JavaScript
environments, this API has (without an explicit decision ever having
been made) adopted the ubiquitous JavaScript convention of
`camelCaseWithStartingLower` for naming properties in JSON objects.
<a id="http-get"></a>
# HTTP GET Requests
Many (if not most) requests can be made via simple GET requests, e.g. we
*could* use any of the following patterns for a hypothetical JSON-format
timeline:
- `https://..../timeline/json`
- `/timeline?format=json`
- `/timeline?json=1`
- `/timeline.json`
- `/json/timeline?...options...`
The API settled on the `/json/...` convention, primarily because it
simplifies dispatching and argument-handling logic compared to the
`/[.../]foo.json` approach. Using `/json/...` allows us to unify that
logic for all JSON sub-commands, for both CLI and HTTP modes.
<a id="http-post"></a>
# HTTP Post Requests
Certain requests, mainly things like editing checkin messages and
committing new files entirely, require POST data. This is fundamentally
very simple to do - clients post plain/unencoded JSON using a common
wrapper envelope which contains the request-specific data to submit as
well as some request-independent information (like authentication data).
<a id="request-envelope"></a>
## POST Request Envelope
POST requests are sent to the same URL as their GET counterpart (if any,
else their own path), and are sent as plain-text/unencoded JSON wrapped
in a common request envelope with the following properties:
- `requestId`: Optional arbitrary JSON value, not used by fossil, but
returned as-is in responses.
- `command`: Provides a secondary mechanism for specifying which JSON
command should be run. A request path of /json/foo/bar is equivalent
to a request with path=/json and command=foo/bar. Note that subpaths
do not work this way. e.g. path=/json/foo, command=bar will not
work, but path=/json, command=foo/bar will. This option is
particularly useful when generating JSON for piping in to CLI mode,
but it also has some response-dispatching uses on the client side.
- `authToken`: Authentication token. Created by a login
request. Determines what access rights the user has, and any given
request may require specific rights. In principle this is required
by any request which needs non-guest privileges, but cookie-aware
clients do not manually need to track this (it is managed as a
cookie by the agent/browser).
- Note that when accessing fossil over a local server instance
which was started with the `--localauth` flag, the `authToken`
will be ignored and need not be sent with any requests. The user
will automatically be given full privileges, as if they were
using CLI mode.
- `payload`: Command-specific parameters. Most can optionally come in
via GET parameters, but those taking complex structures expect them
to be placed here.
- `indent`: Optionally specifies indentation for the output. 0=no
indention. 1=a single TAB character for each level of
indentation. >1 means that many spaces per level. e.g. indent=7
means to indent 7 spaces per object/array depth level. cson also
supports other flags for fine-tuning the output spacing, and adding
them to this interface might be interesting at some
point. e.g. whether or not to add a newline to the output. CLI mode
adds extra indentation by default, whereas CGI/server modes produce
unindented output by default.
- `jsonp`: Optional String (client function name). Requests which
include this will be returned with `Content-Type
application/javascript` and will be wrapped up in a function call
using the given name. e.g. if `jsonp=foo` then the result would look like:\
`foo( {...the response envelope...} )`
The API allows most of those (normally all but the payload) to come in
as either GET parameters or properties of the top-level POSTed request
JSON envelope, with GET taking priority over POST. (Reminder to self: we
could potentially also use values from cookies. Fossil currently only
uses 1 cookie (the login token), and i'd prefer to keep it that way.)
POST requests without such an envelope will be rejected, generating a
Fossil/JSON error response (as opposed to an HTTP error response). GET
requests, by definition, never have an envelope.
POSTed client requests *must* send a Content-Type header of either
`application/json` , `application/javascript`, or `text/plain`, or the
JSON-handling code will never see the POST data. The POST handler
optimistically assumes that type `text/plain` "might be JSON", since
`application/json` is a newer convention which many existing clients
do not use (as of the time these docs were written, back in 2011).
## POST Envelope vs. `POST.payload`
When this document refers to POST data, it is referring to top-level
object in JSON-format POSTed input data. When we say `POST.payload` we
refer to the "payload" property of the POST data. While fossil's core
handles *form-urlencoded* POST data, if such data is sent in then
parsing it as JSON cannot succeed, which means that (at worst) the
JSON-mode bits will "not see" any POST data. Data POSTed to the JSON API
must be sent non-form-urlencoded (i.e. as plain text).
Framework-level configuration options are always set via the top-level
POST envelope object or GET parameters. Request-specific options are set
either in `POST.payload` or GET parameters (though the former is required
in some cases). Here is an example which demonstrates the possibly
not-so-obvious difference between the two types of options (framework
vs. request-specific):
```json
{
"requestId":"my request", // standard envelope property (optional)
"command": "timeline/wiki", // also standard
"indent":2, // output indention is a framework-level option
"payload":{ // *anything* in the payload is request-specific
"limit":1
}
}
```
When a given parameter is set in two places, e.g. GET and POST, or
POST-from-a-file and CLI parameters, which one takes precedence
depends on the concrete command handler (and may be unspecified). Most
will give precedence to CLI and GET parameters, but POSTed values are
technically preferred for non-string data because no additional "type
guessing" or string-to-whatever conversion has to be made (GET/CLI
parameters are *always* strings, even if they look like a number or
boolean).
<a id="request-param-types"></a>
# Request Parameter Data Types
When parameters are sent in the form of GET or CLI arguments, they are
inherently strings. When they come in from JSON they keep their full
type (boolean, number, etc.). All parameters in this API specify what
*type* (or types) they must (or may) be. For strings, there is no
internal conversion/interpretation needed for GET- or CLI-provided
parameters, but for other types we sometimes have to convert strings to
other atomic types. This section describes how those string-to-whatever
conversions behave.
No higher-level constructs, e.g. JSON **arrays** or **objects**, are
accepted in string form. Such parameters must be set in the POST
envelope or payload, as specified by the specific API.
This API does not currently use any **floating-point** parameters, but
does return floating-point results in a couple places.
For **integer** parameters we use a conventional string-to-int algorithm
and assume base 10 (analog to atoi(3)). The API may err on the side of
usability when given technically invalid values. e.g. "123abc" will
likely be interpreted as the integer 123. No APIs currently rely on
integer parameters with more than 32 bits (signedness is call-dependent
but few, if any, use negative values).
**Boolean** parameters are a bit schizophrenic...
In **CLI mode**, boolean flags do not have a value, per se, and thus
require no string-to-bool conversion. e.g. `fossil foo -aBoolOpt
-non-bool-opt value`.
Those which arrive as strings via **GET parameters** treat any of the
following as true: a string starting with a character in the set
`[1-9tT]`. All other string values are considered to be false for this
purpose.
Those which are part of the **POST data** are normally (but not always -
it depends on the exact context) evaluated as the equivalent of
JavaScript booleans. e.g. if we have `POST.envelope.foo="f"`, and evaluate
it as a JSON boolean (as opposed to a string-to-bool conversion), the
result will be true because the underlying JSON API follows JavaScript
semantics for any-type-to-bool conversions. As long as clients always
send "proper" booleans in their POST data, the difference between
GET/CLI-provided booleans should never concern them.
TODO: consider changing the GET-value-to-bool semantics to match the JS
semantics, for consistency (within the JSON API at least, but that might
cause inconsistencies vis-a-vis the HTML interface).
<a id="response-envelope"></a>
# Response Envelope
Every response comes in the form of a HTTP response or (in CLI mode)
JSON sent to stdout. The body of the response is a JSON object following
a common envelope format. The envelope has the following properties:
- `fossil`: Fossil server version string. This property is basically
"the official response envelope marker" - if it is set, clients can
"probably safely assume" that the object indeed came from one of the
Fossil/JSON APIs. This API never creates responses which do not
contain this property.
- `requestId`: Only set if the request contained it, and then it is
echoed back to the caller as-is. This can be use to determine
(client-side) which request a given response is coming in for
(assuming multiple asynchronous requests are pending). In practice
this generally isn’t needed because response handling tends to be
done by closures associated with the original request object (at
least in JavaScript code). In languages without closures it might
have some use. It may be any legal JSON value - it need not be
confined to a string or number.
- `resultCode`: Standardized result code string in the form
`FOSSIL-####`. Only error responses contain a `resultCode`.
- `resultText`: Possibly a descriptive string, possibly
empty. Supplements the resultCode, but can also be set on success
responses (but normally isn't). Clients must not rely on any
specific values being set here.
- `payload`: Request-specific response payload (data type/structure is
request-specific). The payload is never set for error responses,
only for success responses (and only those which actually have a
payload - not all do).
- `timestamp`: Response timestamp (GMT Unix Epoch). We use seconds
precision because i did not know at the time that Fossil actually
records millisecond precision.
- `payloadVersion`: Not initially needed, but reserved for future use
in maintaining version compatibility when the format of a given
response type's payload changes. If needed, the "first version"
value is assumed to be 0, for semantic [near-]compatibility with the
undefined value clients see when this property is not set.
- `command`: Normalized form of the command being run. It consists of
the "command" (non-argument) parts of the request path (or CLI
positional arguments), excluding the initial "/json/" part. e.g. the
"command" part of "/json/timeline/checkin?a=b" (CLI: json timeline
checkin...) is "timeline/checkin" (both in CLI and HTTP modes).
- `apiVersion`: Not yet used, but reserved for a numeric value which
represents the JSON API's version (which can be used to determine if
it has a given feature or not). This will not be implemented until
it's needed.
- `warnings`: Reserved for future use as a standard place to put
non-fatal warnings in responses. Will be an array but the warning
structure/type is not yet specified. Intended primarily as a
debugging tool, and will "probably not" become part of the public
client interface.
- `g`: Fossil administrators (those with the "a" or "s" permissions)
may set the `debugFossilG` boolean request parameter (CLI:
`--json-debug-g`) to enable this property for any given response. It
contains a good deal of the server-side internal state at the time
the response was generated, which is often useful in debuggering
problems. Trivia: it is called "g" because that's the name of
fossil's internal global state object.
- `procTimeMs`: For debugging only - generic clients must not rely on
this property. Contains the number of milliseconds the JSON command
processor needed to dispatch and process the command. TODO: move the
timer into the fossil core so that we can generically time its
responses and include the startup overhead in the time calculation.
<a id="http-response-header"></a>
# HTTP Response Headers
The Content-Type HTTP header of a response will be either
application/json, application/javascript, or text/plain, depending on
whether or not we are in JSONP mode or (failing that) the contents of
the "Accept" header sent in the request. The response type will be
text/plain if it cannot figure out what to do. The response's
Content-Type header *may* contain additional metadata, e.g. it might
look like: application/json; charset=utf-8
Apropos UTF-8: note that JSON is, by definition, Unicode and recommends
UTF-8 encoding (which is what we use). That means if your console cannot
handle UTF-8 then using this API in CLI mode might (depending on the
content) render garbage on your screen.
<a id="cli-vs-http"></a>
# CLI vs. HTTP Mode
CLI (command-line interface) and HTTP modes (CGI and standalone server)
are consolidated in the same implementations and behave essentially
identically, with only minor exceptions.
An HTTP path of `/json/foo` translates to the CLI command `fossil json
foo`. CLI mode takes options in the fossil-convention forms (e.g. `--foo 3`
or `-f 3`) whereas HTTP mode takes them via GET/POST data (e.g. `?foo=1`).
(Note that per long-standing fossil convention CLI parameters taking a
value do not use an equal sign before the value!)
For example:
- HTTP: `/json/timeline/wiki?after=2011-09-01&limit=3`
- CLI: `fossil json timeline wiki --after 2011-09-01 --limit 3`
Some commands may only work in one mode or the other (for various
reasons). In CLI mode the user automatically has full setup/admin
access.
In HTTP mode, request-specific options can also be specified in the
`POST.payload` data, and doing so actually has an advantage over
specifying them as URL parameters: posting JSON data retains the full
type information of the values, whereas GET-style parameters are always
strings and must be explicitly type-checked/converted (which may produce
unpredictable results when given invalid input). That said, oftentimes
it is more convenient to pass the options via URL parameters, rather
than generate the request envelope and payload required by POST
requests, and the JSON API makes some extra effort to treat GET-style
parameters type-equivalent to their POST counterparts. If a property
appears in both GET and `POST.payload`, GET-style parameters *typically*
take precedence over `POST.payload` by long-standing convention (=="PHP
does it this way by default").
(That is, however, subject to eventual reversal because of the
stronger type safety provided by POSTed JSON. Philosophically
speaking, though, GET *should* take precedence, in the same way that
CLI-provided options conventionally override app-configuration-level
options.)
One notable functional difference between CLI and HTTP modes is that in
CLI mode error responses *might* be accompanied by a non-0 exit status
(they "should" always be, but there might be cases where that does not
yet happen) whereas in HTTP mode we always try to exit with code 0 to
avoid generating an HTTP 500 ("internal server error"), which could keep
the JSON response from being delivered. The JSON code only intentionally
allows an HTTP 500 when there is a serious internal error like
allocation or assertion failure. HTTP clients are expected to catch
errors by evaluating the response object, not the HTTP result code.
<a id="simulating-post-data"></a>
# Simulating POSTed data
We have a mechanism to feed request data to CLI mode via
files (simulating POSTed data), as demonstrated in this example:
```console
$ cat in.json
{ "command": "timeline/wiki", "indent":2, "payload":{"limit":1}}
$ fossil json --json-input in.json # use filename - for stdin
```
The above is equivalent to:
```console
$ echo '{"indent":2, "payload":{"limit":1}}' \
| fossil json timeline wiki --json-input -
```
Note that the "command" JSON parameter is only checked when no json
subcommand is provided on the CLI or via the HTTP request path. Thus we
cannot pass the CLI args "json timeline" in conjunction with a "command"
string of "wiki" this way.
***HOWEVER...***
Much of the existing JSON code was written before the `--json-input`
option was possible. Because of this, there might be some
"misinteractions" when providing request-specific options via *both*
CLI options and simulated POST data. Those cases will eventually be
ironed out (with CLI options taking precedence). Until then, when
"POSTing" data in CLI mode, for consistent/predictible results always
provide any options via the JSON request data, not CLI arguments. That
said, there "should not" be any blatant incompatibilities, but some
routines will prefer `POST.payload` over CLI/GET arguments, so there
are some minor inconsistencies across various commands with regards to
which source (POST/GET/CLI) takes precedence for a given option. The
precedence "should always be the same," but currently cannot be due to
core fossil implementation details (the internal consolidation of
GET/CLI/POST vars into a single set).
<a id="json-indentation"></a>
# Indentation/Formatting of JSON Output
CLI mode accepts the `--indent|-I #` option to set the indention level
and HTTP mode accepts `indent=#` as a GET/POST parameter. The semantics
of the indention level are derived from the underlying JSON library and
have the following meanings: 0 (zero) or less disables all superfluous
indentation (this is the default in HTTP mode). A value of 1 uses 1 hard
TAB character (ASCII 0x09) per level of indention (the default in CLI
mode). Values greater than 1 use that many whitespaces (ASCII 32d) per
level of indention. e.g. a value of 7 uses 7 spaces per level of
indention. There is no way to specify one whitespace per level, but if
you *really* want one whitespace instead of one tab (same data size) you
can filter the output to globally replace ASCII 9dec (TAB) with ASCII
32dec (space). Because JSON string values *never* contain hard tabs
(they are represented by `\t`) there is no chance that such a global
replacement will corrupt JSON string contents - only the formatting will
be affected.
Potential TODO: because extraneous indention "could potentially" be used
as a form DoS, the option *might* be subject to later removed from HTTP
mode (in CLI it's fine).
In HTTP mode no trailing newline is added to the output, whereas in CLI
mode one is normally appended (exception: in JSONP mode no newline is
appended, to (rather pedantically and arbitraily) allow the client to
add a semicolon at the end if he likes). There is currently no option to
control the newline behaviour, but the underlying JSON code supports
this option, so adding it to this API is just a matter of adding the
CLI/HTTP args for it.
Pedantic note: internally the indention level is stored as a single
byte, so giving large indention values will cause harmless numeric
overflow (with only cosmetic effects), meaning, e.g., 257 will overflow
to the value 1.
Potential TODO: consider changing cson's indention mechanism to use a
*signed* number, using negative values for tabs and positive for
whitespace count (or the other way around). This would require more
doc changes than code changes :/.
<a id="jsonp"></a>
# JSONP
The API supports JSONP-style output. The caller specifies the callback
name and the JSON response will be wrapped in a function call to that
name. For HTTP mode pass the `jsonp=string` option (via GET or POST
envelope) and for CLI use `--jsonp string`.
For example, if we pass the JSONP name `myCallback` then a response will
look like:
```js
myCallback({...response...})
```
Note that fossil does not evaluate the callback name itself, other than
to verify that it is-a string, so "garbage in, garbage out," and all
that. (Remember that CLI and GET parameters are *always* strings, even
if they *look* like numbers.)
<a id="result-codes"></a>
# API Result Codes
Result codes are strings which tell the client whether or not a given
API call succeeded or failed, and if it failed *perhaps* some hint as to
why it failed.
The result code is available via the resultCode property of every
*error* response envelope. Since having a result code value for success
responses is somewhat redundant, success responses contain no resultCode
property. In practice this simplifies error checking on the client side.
The codes are strings in the form `FOSSIL-####`, where `####` is a
4-digit integral number, left-padded with zeros. The numbers follow
these conventions:
- The number 0000 is reserved for the "not an error" (OK) case. Since
success responses do not contain a result code, clients won't see
this value (except in documentation).
- All numbers with a leading 0 are reserved for *potential* future use
in reporting non-fatal warnings.
- Despite *possibly* having leading zeros, the numbers are decimal,
not octal. Script code which uses eval() or similar to produce
integers from them may need to take that into account.
- The 1000ths and 100ths places of the number describe the general
category of the error, e.g. authentication- vs. database- vs. usage
errors. The 100ths place is more specific than the 1000ths place,
allowing two levels of sub-categorization (which "should be enough"
for this purpose). This separation allows the server administrator
to configure the level of granularity of error reporting. e.g. some
admins consider error messages to be security-relevant and like to
"dumb them down" on their way to the client, whereas developers
normally want to see very specific error codes when tracking down a
problem. We can offer a configuration option to "dumb down" error
codes to their generic category by simply doing a modulo 100
(or 1000) against the native error code number. e.g. FOSSIL-1271
could (via a simple modulo) be reduced to FOSSIL-1200 or
FOSSIL-1000, depending on the paranoia level of the sysadmin. i have
tried to order the result code numbers so that a dumb-down level of
2 provides reasonably usable results without giving away too much
detail to malicious clients.\
(**TODO:** `g.json.errorDetailParanoia` is used to set the
default dumb-down level, but it is currently set at compile-time.
It needs to be moved to a config option. We have a chicken/egg scenario
with error reporting and db access there (where the config is
stored).)
- Once a number is assigned to a given error condition (and actually
used somewhere), it may not be changed/redefined. JSON clients need
to be able to rely on stable result codes in order to provide
adequate error reporting to their clients, and possibly for some
error recovery logic as well (i.e. to decide whether to abort or
retry).
The *tentative* list of result codes is shown in the following table.
These numbers/ranges are "nearly arbitrarily" chosen except for the
"special" value 0000.
**Maintenance reminder:** these codes are defined in
[`src/json_detail.h`](/finfo/src/json_detail.h) (enum
`FossilJsonCodes`) and assigned default `resultText` values in
[`src/json.c:json_err_cstr()`](/finfo/src/json.c). Changes there need
to be reflected here (and vice versa). Also, we have assertions in
place to ensure that C-side codes are in the range 1000-9999, so do
not just go blindly change the numeric ranges used by the enum.
**`FOSSIL-0###`: Non-error Category**
- `FOSSIL-0000`: Success/not an error. Succesful responses do not
contain a resultCode, so clients should never see this.
- `FOSSIL-0###`: Reserved for potential future use in reporting
non-fatal warnings.
**`FOSSIL-1000`: Generic Errors Category**
- `FOSSIL-1101`: Invalid request. Request envelope is invalid or
missing.
- `FOSSIL-1102`: Unknown JSON command.
- `FOSSIL-1103`: Unknown/unspecified error
- `FOSSIL-1104`: RE-USE
- `FOSSIL-1105`: A server-side timeout was reached. (i’m not sure we
can actually implement this one, though.)
- `FOSSIL-1106`: Assertion failed (or would have had we
continued). Note: if an `assert()` fails in CGI/server modes, the HTTP
response will be code 500 (Internal Server Error). We want to avoid
that and return a JSON response instead. All of that said - there seems
to be little reason to implementi this, since assertions are "truly
serious" errors.
- `FOSSIL-1107`: Allocation/out of memory error. This cannot be reasonably
reported because fossil aborts if an allocation fails.
- `FOSSIL-1108`: Requested API is not yet implemented.
- `FOSSIL-1109`: Panic! Fossil's `fossil_panic()` or `cgi_panic()` was
called. In non-JSON HTML mode this produces an HTTP 500
error. Clients "should" report this as a potential bug, as it
"possibly" indicates that the C code has incorrect argument- or
error handling somewhere.
- `FOSSIL-1110`: Reading of artifact manifest failed. Time to contact
your local fossil guru.
- `FOSSIL-1111`: Opening of file failed (e.g. POST data provided to
CLI mode).
**`FOSSIL-2000`: Authentication/Access Error Category**
- `FOSSIL-2001`: Privileged request was missing authentication
token/cookie.
- `FOSSIL-2002`: Access to requested resource was denied. Oftentimes
the `resultText` property will contain a human-language description of
the access rights needed for the given command.
- `FOSSIL-2003`: Requested command is not available in the current
operating mode. Returned in CLI mode by commands which require HTTP
mode (e.g. login), and vice versa. FIXME: now that we can simulate
POST in CLI mode, we can get rid of this distinction for some of the
commands.
- `FOSSIL-2100`: Login Failed.
- `FOSSIL-2101`: Anonymous login attempt is missing the
"anonymousSeed" property (fetched via [the `/json/anonymousPassword`
request](api-auth.md#login-anonymous)). Note that this is more
specific form of `FOSSIL-3002`.
ONLY FOR TESTING purposes should the remaning 210X sub-codes be
enabled (they are potentially security-relevant, in that the client
knows which part of the request was valid/invalid):
- `FOSSIL-2102`: Name not supplied in login request
- `FOSSIL-2103`: Password not supplied in login request
- `FOSSIL-2104`: No name/password match found
**`FOSSIL-3000`: Usage Error Category**
- `FOSSIL-3001`: Invalid argument/parameter type(s) or value(s) in
request
- `FOSSIL-3002`: Required argument(s)/parameter(s) missing from
request
- `FOSSIL-3003`: Requested resource identifier is ambiguous (e.g. a
shortened hash that matches multiple artifacts, an abbreviated
date that matches multiple commits, etc.)
- `FOSSIL-3004`: Unresolved resource identifier. A branch/tag/uuid
provided by client code could not be resolved. This is a special
case of #3006.
- `FOSSIL-3005`: Resource already exists and overwriting/replacing is
not allowed. e.g. trying to create a wiki page or user which already
exists. FIXME? Consolidate this and resource-not-found into a
separate category for dumb-down purposes?
- `FOSSIL-3006`: Requested resource not found. e.g artifact ID, branch
name, etc.
**`FOSSIL-4000`: Database-related Error Category**
- `FOSSIL-4001`: Statement preparation failed.
- `FOSSIL-4002`: Parameter binding failed.
- `FOSSIL-4003`: Statement execution failed.
- `FOSSIL-4004`: Database locked (this is not used anywhere, but
reserved for future use).
Special-case DB-related errors...
- `FOSSIL-4101`: Fossil Schema out of date (repo rebuild required).
- `FOSSIL-4102`: Fossil repo db could not be found.
- `FOSSIL-4103`: Repository db is not valid (possibly corrupt).
- `FOSSIL-4104`: Check-out not found. This is similar to FOSSIL-4102
but indicates that a local checkout is required (but was not
found). Note that the 4102 gets triggered earlier than this one, and
so can appear in cases when a user might otherwise expect a 4104
error.
Some of those error codes are of course "too detailed" for the client to
do anything with (e.g.. 4001-4004), but their intention is to make it
easier for Fossil developers to (A) track down problems and (B) support
clients who report problems. If a client reports, "I get a FOSSIL-4000,
how can I fix it?" then the developers/support personnel can't say much
unless they know if it's a 4001, 4002, 4003, 4004, or 4101 (in which
case they can probably zero in on the problem fairly quickly, since they
know which API call triggered it and they know (from the error code) the
general source of the problem).
## Why Standard/Immutable Result Codes are Important
- They are easily internationalized (i.e. associated with non-English
error text)
- Clients may be able to add automatic retry strategies for certain
problem types by examining the result code. e.g. if fossil returns a
locking or timeout error \[it currently does no special
timeout/locking handling, by the way\] the client could re-try,
whereas a usage error cannot be sensibly retried with the same
inputs.
- The "category" structure described above allows us some degree of
flexibility in how detailed the reported errors are reported.
- While the string prefix "FOSSIL-" on the error codes may seem
superfluous, it has one minor *potential* advantage on the client
side: when managing several unrelated data sources, these error
codes can be immediately identified (by higher-level code which may
be ignorant of the data source) as having come from the fossil API.
Think "ORA-111" vs. "111".
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: Hacker's Guide
([⬑JSON API Index](index.md))
Jump to:
* [Before Committing Changes](#before-committing)
* [JSON C API](#json-c-api)
* [Reporting Errors](#reporting-errors)
* [Getting Command Arguments](#command-args)
* [Creating JSON Data](#creating-json)
* [Creating JSON Values](#creating-json-values)
* [Converting SQL Query Results to JSON](#query-to-json)
This section will only be of interest to those wanting to work on the
Fossil/JSON code. That said...
If you happen to hack on the code and find something worth noting here
for others, please feel free to expand this section. It will only
improve via feedback from those working on the code.
---
<a id="before-committing"></a>
# Before Committing Changes...
Because this code lives in the trunk, there are certain
guidelines which must be followed before committing any changes:
1. Read the [checkin preparation list](/doc/trunk/www/checkin.wiki).
2. Changes to the files `src/json_*.*`, and its related support code
(e.g. `ajax/*.*`), may be made freely without affecting mainline
users. Changes to other files, unless they are trivial or made for
purposes outside the JSON API (e.g. an unrelated bug fix), must be
reviewed carefully before committing. When in doubt, create a branch
and post a request for a review.
3. The Golden Rule is: *do not break the trunk build*.
<a id="json-c-api"></a>
# JSON C API
libcson, the underlying JSON API, is a separate project, included in
fossil in "amalgamation" form: see `src/cson_amalgamation.[ch]`. It has
thorough API docs and a good deal of information is in its wiki:
[](https://fossil.wanderinghorse.net/wikis/cson/)
In particular:
[](https://fossil.wanderinghorse.net/wikis/cson/?page=CsonArchitecture)
gives an overview of its architecture. Occasionally new versions of it
are pulled into the Fossil tree, but other developers generally need not
concern themselves with that.
(Trivia: the cson wiki's back-end is fossil, living on top of a
JavaScript+HTML5 application.)
Only a small handful of low-level fossil routines actually input or
output JSON text (only for reading in POST data and sending the
response). In the C code we work with the higher-level JSON value
abstractions provided by cson (conceptually similar to an XML DOM). All
of the JSON-defined data types are supported, and we can construct JSON
output of near arbitrary complexity with the caveat that *cyclic data
structures are strictly forbidden*, and *will* cause memory corruption,
crashes, double free()'s, or other undefined behaviour. Because JSON
cannot, without client-specific semantic extensions to JSON, represent
cyclic structures, it is not anticipated that this will be a
problem/limitation when generating output for fossil.
<a id="json-commands"></a>
# Architecture of JSON Commands
In order to consolidate CLI/HTTP modes for JSON handling, this code
foregoes fossil's conventional command/path dispatching mechanism. Only
the top-most "json" command/path is dispatched directly by fossil's
core. The disadvantages of this are that we lose fossil's conventional
help text mechanism (which is based on code comments in the
command/path's dispatcher impl) and the ability to write abbreviated
command names in CLI mode ("json" itself may be abbreviated, but not the
subcommands). The advantages are that we can handle CLI/HTTP modes
almost identically (there are a couple minor differences) by unifying
them under the same callback functions much more easily.
The top-level "json" command/path uses its own dispatching mechanism
which uses either the path (in HTTP mode) or CLI positional arguments to
dispatch commands (stopping at the first "flag option" (e.g. -foo) in
CLI mode). The command handlers are simply callback functions which
return a cson\_value pointer (the C representation of an arbitrary JSON
value), representing the "payload" of the response (or NULL - not all
responses need a payload). On error these callbacks set the internal
JSON error state (detailed in a subsection below) and return NULL. The
top-level dispatcher then creates a response envelope and returns the
"payload" from the command (if any) to the caller. If a callback sets
the error state, the top-level dispatcher takes care to set the error
information in the response envelope. In summary:
- The top-level dispatchers (`json_page_top()` and `json_cmd_top()`)
are called by fossil's core when the "json" command/path is called.
They initialize the JSON-mode global state, dispatch the requested
command, and handle the creation of the response envelope. They
prepare all the basic things which the individual subcommands need
in order to function.
- The command handlers (most are named `json_page_something()`)
implement the `fossil_json_f()` callback interface (see
[`src/json_detail.h`](/finfo/src/json_detail.h)). They are
responsible for permissions checking, setting any error state, and
passing back a payload (if needed - not all commands return a
payload). It is strictly forbidden for these callbacks to produce
any output on stdout/stderr, and doing so effectively corrupts the
out-bound JSON and HTTP headers.
There is a wrench in all of that, however: the vast majority of fossil's
commands "fail fast" - they will `exit()` if they encounter an error. To
handle that, the fossil core error reporting routines have been
refactored a small bit to operate differently when we are running in
JSON mode. Instead of the conventional output, they generate a JSON
error response. In HTTP mode they exit with code 0 to avoid causing an
HTTP 500 error, whereas in CLI mode they will exit with a non-0 code.
Those routines still `exit()`, as in the conventional CLI/HTTP modes, but
they will exit differently. Because of this, it is perfectly fine for a
command handler to exit via one of fossil's conventional mechanisms
(e.g. `db_prepare()` can be fatal, and callbacks may call `fossil_panic()`
if they really want to). One exception is `fossil_exit()`, which does
_not_ generate any extra output and will `exit()` the app. In the JSON
API, as a rule of thumb, `fossil_exit()` is only used when we *want* a
failed request to cause an HTTP 500 error, and it is reserved for
allocation errors and similar truly catostrophic failures. That said...
libcson has been hacked to use `fossil_alloc()` and friends for memory
management, and those routines exit on error, so alloc error handling in
the JSON command handler code can afford to be a little lax (the
majority of *potential* errors clients get from the cson API have
allocation failure as their root cause).
As a side-note: the vast majority (if not all) of the cson API calls are
"NULL-safe", meaning that will return an error code (or be a no-op) if
passed NULL arguments. e.g. the following chain of calls will not crash
if the value we're looking for does not exist, is-not-a String (see
`cson_value_get_string()` for important details), or if `myObj` is NULL:
```c
const char * str =
cson_string_cstr( // get the C-string form of a cson_string
cson_value_get_string( // get its cson_string form
cson_object_get(myObj,"foo") // search for key in an Object
)
);
```
If `"foo"` is not found in `myObj` (or if `myObj` is NULL) then v will be
NULL, as opposed to stepping on a NULL pointer somewhere in that call
chain.
Note that all cson JSON values except Arrays and Objects are *immutable*
- you cannot change a string's or number's value, for example. They also
use reference counting to manage ownership, as documented and
demonstrated on this page:
[](https://fossil.wanderinghorse.net/wikis/cson/?page=TipsAndTricks)
In short, after creating a new value you must eventually *either* add it
to a container (Object or Array) to transfer ownership *or* call
`cson_value_free()` to clean it up (exception: the Fossil/JSON command
callbacks *return* a value to transfer ownership to the dispatcher).
Sometimes it's more complex than that, but not normally. Any given value
may legally be stored in any number of containers (or multiple times
within one container), as long as *no cycles* are introduced (cycles
*will* cause undefined behaviour). Ownership is shared using reference
counting and the value will eventually be freed up when its last
remaining reference is freed (e.g. when the last container holding it is
cleaned up). For many examples of using cson in the context of fossil,
see the existing `json_page_xxx()` functions in `json_*.c`.
<a id="reporting-errors"></a>
# Reporting Errors
To report an error from a command callback, one abstractly needs to:
- Set g.json.resultCode to one of the `FSL_JSON_E_xxx` values
(defined in [`src/json_detail.h`](/finfo/src/json_detail.h)).
- *Optionally* set `g.zErrMsg` to contain the (dynamically-allocated!)
error string to be sent to the client. If no error string is set
then a standard/generic string is used for the given error code.
- Clean up any resources created so far by the handler.
- Return NULL. If it returns non-NULL, the dispatcher will destroy the
value and not include it in the error response.
That normally looks something like this:
```
if(!g.perm.Read){
json_set_err(FSL_JSON_E_DENIED, "Requires 'o' permissions.");
return NULL;
}
```
`json_set_err()` is a variadic printf-like function, and can use the
printf extensions supported by mprintf() and friends (e.g. `%Q` and `%q`)
(but they are normally not needed in the context of JSON). If the error
string is NULL or empty then `json_err_cstr(errorCode)` is used to fetch
the standard/generic error string for the given code.
When control returns to the top-level dispatching function it will check
`g.json.resultCode` and, if it is not 0, create an error response using
the `g.json.resultCode` and `g.zErrMsg` to construct the response's
`resultCode` and `resultText` properties.
If a function wants to output an error and exit by itself, as opposed
to returning to the dispatcher, then it must behave slightly
differently. See the docs for `json_err()` (in
[`src/json.c`](/finfo/src/json.c)) for details, and search that file
for various examples of its usage. It is also used by fossil's core
error-reporting APIs, e.g. `fossil_panic()` (defined in [`src/main.c`](/finfo/src/main.c)).
That said, it would be "highly unusual" for a callback to need to do
this - it is *far* simpler (and more consistent/reliable) to set the
error state and return to the dispatcher.
<a id="command-args"></a>
# Getting Command Arguments
Positional parameters can be fetched usinig `json_command_arg(N)`, where
N is the argument position, with position 0 being the "json"
command/path. In CLI mode positional arguments have their obvious
meaning. In HTTP mode the request path (or the "command" request
property) is used to build up the "command path" instead. For example:
CLI: `fossil json a b c`
HTTP: `/json/a/b/c`
HTTP POST or CLI with `--json-input`: /json with POSTed envelope
`{"command": "a/b/c" …}`
Those will have identical "command paths," and `json_command_path(2)`
would return the "b" part.
Caveat: a limitation of this support is that all CLI flags must come
*after* all *non-flag* positional arguments (e.g. file names or
subcommand names). Any argument starting with a dash ("-") is considered
by this code to be a potential "flag" argument, and all arguments after
it are ignored (because the generic handling cannot know if a flag
requires an argument, which changes how the rest of the arguments need
to be interpreted).
To get named parameters, there are several approaches (plus some special
cases). Named parameters can normally come from any of the following
sources:
- CLI arguments, e.g. `--foo bar`
- GET parameters: `/json/...?foo=bar`
- Properties of the POST envelope
- Properties of the `POST.payload` object (if any).
To try to simplify the guessing process the API has a number of
functions which behave ever so slightly differently. A summary:
- `json_getenv()` and `json_getenv_TYPE()` search the so-called "JSON
environment," which is a superset of the GET/POST/`POST.payload` (if
`POST.payload` is-a Object).
- `json_find_option_TYPE()`: searches the CLI args (only when in CLI
mode) and the JSON environment.
- The use of fossil's `P()` and `PD()` macros is discourages in JSON
callbacks because they can only handle String data from the CLI or
GET parameters (not POST/`POST.payload`). (Note that `P()` and `PD()`
*normally* also handle POSTed keys, but they only "see" values
posted as form-urlencoded fields, and not JSON format.)
- `find_option()` (from `src/main.c`) "should" also be avoided in
JSON API handlers because it removes flag from the g.argv
arguments list. That said, the JSON API does use `find_option()` in
several of its option-finding convenience wrappers.
For example code: the existing command callbacks demonstrate all kinds
of uses and the various styles of parameter/option inspection. Check out
any of the functions named `json_page_SOMETHING()`.
<a href="creating-json"></a>
# Creating JSON Data
<a href="creating-json-values"></a>
## Creating JSON Values
cson has a fairly rich API for creating and manipulating the various
JSON-defined value types. For a detailed overview and demonstration i
recommend reading:
[](https://fossil.wanderinghorse.net/wikis/cson/?page=HowTo)
That said, the Fossil/JSON API has several convenience wrappers to save
a few bytes of typing:
- `json_new_string("foo")` is easier to use than
`cson_value_new_string("foo", 3)`, and
`json_new_string_f("%s","foo")` is more flexible.
- `json_new_int()` is easier to type than `cson_value_new_integer()`.
- `cson_output_Blob()` and `cson_parse_Blob()` can write/read JSON
to/from fossil `Blob`-type objects.
It also provides several lower-level JSON features which aren't of
general utility but provide necessary functionality for some of the
framework-level code (e.g. `cson_data_dest_cgi()`), which is only used
by the deepest of the JSON internals).
<a href="query-to-json"></a>
## Converting SQL Query Results to JSON
The `cson_sqlite3_xxx()` family of functions convert `sqlite3_stmt` rows
to Arrays or Objects, or convert single columns to a JSON-compatible
form. See `json_stmt_to_array_of_obj()`,
`json_stmt_to_array_of_array()` (both in `src/json.c`), and
`cson_sqlite3_column_to_value()` and friends (in
`src/cson_amalgamation.h`). They work in an intuitive way for numeric
types, but they optimistically/natively *assume* that any fields of type
TEXT or BLOB are actually UTF8 data, and treat them as such. cson's
string class only handles UTF8 data and it is semantically illegal to
feed them anything but UTF8. Violating this will likely result in
down-stream errors (e.g. when emiting the JSON string output). **The
moral of this story is:** *do not use these APIs to fetch binary data*.
JSON doesn't do binary and the `cson_string` class does not
protect itself against clients feeding it non-UTF8 data.
Here's a basic example of using these features:
```c
Stmt q = empty_Stmt;
cson_value * rows = NULL;
db_prepare(&q, "SELECT a AS a, b AS b, c AS c FROM foo");
rows = json_stmt_to_array_of_obj( &sql, NULL );
db_finalize(&q);
// side note: if db_prepare()/finalize() fail (==they exit())
// then a JSON-format error reponse will be generated.
```
On success (and if there were results), `rows` is now an Array value,
each entry of which contains an Object containing the fields (key/value
pairs) of each row. `json_stmt_to_array_of_array()` returns each row
as an Array containing the column values (with no column name
information).
**Note the seemingly superfluous use of the "AS" clause in the above
SQL.** Having them is actually significant! If a query does *not* use AS
clauses, the row names returned by the db driver *might* be different
than they appear in the query (this is documented behaviour of sqlite3).
Because the JSON API needs to return stable field names, we need to use
AS clauses to be guaranteed that the db driver will return the column
names we want. Note that the AS clause is often used to translate column
names into something more JSON-conventional or user-friendly, e.g.
"SELECT cap AS capabilities...". Alternately, we can convert the
individual `sqlite3_stmt` column values to JSON using
`cson_sqlite3_column_to_value()`, without refering directly to the
db-reported column name.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | # JSON API Index This is the client-side documentation of Fossil's JSON API. The JSON API aims to provide access to many of the primary fossil features via AJAX-style interfaces. * [Introduction](intro.md) * [General API Conventions](conventions.md) * [Tips & Tricks](tips.md) * [Hacking Guide](hacking.md) General warnings regarding the APIs linked to in the following list: - **NOTE** that request/response examples shown in the individual API pages do not show [the standard request/response envelope](conventions.md) (for brevity and sanity). - **Achtung:** just because a given feature is described as being implemented does not mean that the implementation is "final" - it may be changed at any time until we find/implement useful APIs. The APIs, alphabetically by category: * [Artifact Info](api-artifact.md) * [Authentication](api-auth.md) * [Branches](api-branch.md) * [Checkout Status](api-checkout.md) * [Config](api-config.md) * [Diffs](api-diff.md) * [Directory Listing](api-dir.md) * [File Info](api-finfo.md) * [The Obligatory Misc. Category](api-misc.md) * [Repository Stats](api-stat.md) * [SQL Query](api-query.md) * [Tags](api-tag.md) * [Tickets](api-ticket.md) * [Timeline](api-timeline.md) * [User Management](api-user.md) * [Version](api-version.md) * [Wiki](api-wiki.md) |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API Introduction
([⬑JSON API Index](index.md))
Jump to:
* [Why?](#why)
* [Building JSON Support](#builing)
* [Goals & Non-goals](#goals)
* [Potential Client-side Uses](#potential-uses)
* [Technical Problems and Considerations](#considerations)
---
<a id="why"></a>
# Why?
In September, 2011, Fossil contributor Stephan Beal had the great
pleasure of meeting D. Richard Hipp, Fossil's author, for lunch in
Munich, Germany. During the conversation Richard asked, "what does
Fossil need next?" Stephan's first answer was, "refactoring into a
library/client, as opposed to a monolithic app." We very quickly
agreed that the effort required would be "herculean," and second
choice was voiced, "a JSON API." They briefly discussed the idea and
Richard gave his blessing. That night work began.
Why a JSON API? Because it is the next best thing to the
"librification" of Fossil, in that it makes Fossil's features
available to near-arbitrary applications using a simple, globally
available data format.
<a id="building"></a>
# Building JSON Support
In environments supported by fossil's `configure` script,
simply pass `--enable-json` to it:
```
$ ./configure --prefix=$HOME --enable-json ...
```
When built without that option, JSON support is disabled. **When
reconfiguring the source tree**, ***always be sure to do a "make
clean"*** (or equivalent for your platform) between builds (preferably
*before* reconfiguring), to ensure that everything is rebuilt properly.
If you fail to do that after enabling JSON on a tree which has already
been built, most of the sources will not be rebuilt properly. The reason
is that the JSON files are actually unconditionally compiled, but when
built without `--enable-json` they compile to empty object files. Thus
after a reconfigure the (empty) object files are still up-to-date
vis-a-vis the sources, and won't be rebuilt.
To build Fossil with JSON support on Windows using the Microsoft C
compiler:
```
cd win
nmake -f Makefile.msc FOSSIL_ENABLE_JSON=1
```
It has been seen to compile in VC versions 6 and higher.
<a id="goals"></a>
# Goals & Non-goals
The API described here is most certainly not
[*REST*](http://en.wikipedia.org/wiki/Representational_state_transfer)-conformant,
but is instead JSON over HTTP. The error reporting techniques of the
REST conventions (using HTTP error codes) "does not mesh" with my ideas
of separation of transport- vs. app-side errors. Additionally, REST
requires HTTP methods which are not specified by CGI (namely PUT and
DELETE), which means we can't possibly implement a REST-compatible
interface on top of fossil (which uses CGI mode even for its built-in
server).
The **overall goals** of this effort include:
- A JSON-based API off of which clients can build customized Fossil
UIs and special-purpose applications. e.g. a desktop notification
applet which polls for new timeline data.
- Arbitrary JSON-using clients should be able to use it. Though JSON
originates from JavaScript, it is truly a cross-platform data format
with a very high adoption rate. (There’s even a JSON implementation
for Oracle PL/SQL.)
- Fossil’s CGI and Server modes are the main targets and should be
supported equally. CLI JSON mode is of secondary concern (but is in
practice easier to test, so it’s generally implemented first).
The ***non-goals*** include:
- We won’t be able to implement *every* feature of Fossil via a JSON
interface, and we won’t try to.
- Binary data (e.g. commits of binary files or downloading ZIP files)
is not an initial goal, but "might be interesting" once the overall
infrastructure is in place and working well. See below for more
details about binary data in JSON.
- A "pure REST" interface is seemingly not possible due to REST
relying on HTTP methods not specified in the CGI standard (PUT and
DELETE). Additionally, REST-style error reporting cannot be used by
non-HTTP clients (which this code supports).
Adding JSON support also gives us a framework off of which to
build/enhance other features. Some examples include:
- **Internationalization**. Errors are reported via standard codes and
the raw artifact data is language-independent.
- The ability to author **special-case clients**, e.g. a ticket
poller.
- Use **arbitrary HTTP-capable languages** to implement such tools.
Programming languages which can execute programs and intercept their
stdout output can use the JSON API via a local fossil binary.
- **Automatable tests.** Many of fossil's test results currently have
to be "visually reviewed" for correctness after changes (e.g.
changes in the HTML interface). JSON structures can be
programmatically checked for correctness. Artifacts are immutable,
which allows us to be very specific in what data to expect as output
(for artifact-specific requests the payload data will often (but not
always) be the same across all requests and all time).
<a id="potential-uses"></a>
# Potential Client-side Uses
Some of the potential client-side uses of this API include...
- Custom apps/applets to fetch timeline/ticket/etc. information from
arbitrary repositories. There are many possibilities here, including
"dashboard" sites which monitor several repositories.
- Custom post-commit triggers, by polling for changes and reacting to
them (e.g. sending mails).
- A custom wiki front-end which uses fossil as the back-end storage,
inheriting its versioning and user access support while providing a
completely custom wiki-centric UI. Such a wiki need not have, on the
surface, anything to do with fossil or source control, as fossil
would just become a glorified wiki back-end. This approach also
allows clients to serve wiki pages in a format of their choice -
since all rendering would be done client-side, they could use
whatever format they like.
<a id="considerations"></a>
# Technical Problems and Considerations
A random list of considerations which need to be made and potential
problem areas...
- **Binary data:** HTML4 and JavaScript have no portable way of
handling binary data, so commands which could potentially deal with
binary data (e.g. committing a file) are ruled out for the time
being. HTML5 and accompanying JavaScript additions will binary
data usable client-side. That said, a JSON interface cannot natively
work with binary unless it is encoded (base64 or hex or whatever),
and such encoding would have to be understood on both the server and
client sides, which may rule out usage in some environments.\
**Status:** deferred until needed. My current thinking is to send
URLs instead of binary data, and the URLs would point to some path
which produces the raw artifact content. We could read POSTed binary
input, but this might require some re-tooling of fossil's innards
and it precludes the use of a JSON request envelope, so it would be
limited to requests which can be configured solely via GET arguments
(as opposed to POST envelope/payload options). i.e. configure the
JSON bits via GET and POST the binary data.
- **64-bit integers:** JSON does not specify integer precision,
probably because it targets many different platforms and not all
of them can support more than 32 bits. JavaScript (from which JSON
derives) supports 53 bits of integer precision. That said, it's
"highly unlikely" that we'll have any range problems with "only"
53 bits of precision. The underlying JSON API supports *signed*
32- or 64-bit integers on both 32- and 64-bit builds, but only if
"long long" or `int64_t` are available (from the C99 header
`stdint.h`). Only multi-gig repositories are ever expected to use
large numbers, and even then only rarely (e.g. via the "stat"
command).
- **Timestamps:** for portability this API uses GMT Unix Epoch
timestamps. They are the most portable time representation out
there, easily usable in most programming environments. (In hindsight,
this should have been Unix + Milliseconds, but the API already
pervasively uses seconds-precision.)
- **Artifact vs. Artefact:** both are correct vis-a-vis the
english language but Fossil consistently uses the former, so we’ll
use that.
- **Multiple logins per user:** fossil currently does not allow
multiple active logins for a given user except anonymous. For all
others, the most recent login wins. This is only a very minor
annoyance for the HTML interface but will be more problematic for
JSON clients. e.g. a user might have a ticket poller and a commit poller
running, and both would need to be logged in.\
**Status:** as of 20120315 (commit
[*73038baaa3*](http://www.fossil-scm.org/index.html/info/73038baaa3)),
fossil allows a user to be logged in multiple times (confirm: only
within the same network?). The only caveat is that if any one of
them logs out, it will invalidate the login session for the others.
This is good enough for the time being, however. It will likely only
become painful if we actually get enough apps in the wild that
someone might have some running on his mobile phone and some on his
PC and some on his server. The workarounds for now are (A) not to
log out and (B) program apps/applets/widgets to try to re-login
occasionally. Fossil will at some point expire the login, anyway.
FIXME: update the expiry time on each request? To do that right we'd
have to re-set the cookie on each request :/. We could optionally
add a new JSON request which simply updates the login cookie
lifetime (e.g. /json/keepalive or expand /json/whoami to do that).
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# JSON API: Tips and Tricks
([⬑JSON API Index](index.md))
Jump to:
* [Beware of Content-Type and Encoding...](#content-type)
* [Using `curl` and `wget`](#curl-wget)
* [Example JavaScript](#javascript)
* [Demo Apps](#demo-apps)
---
<a id="content-type"></a>
# Beware of Content-Type and Encoding...
When posting data to fossil, make sure that the request sends:
- **Content-Type** of `application/json`. Fossil also (currently)
accepts `application/javascript` and `text/plain` as JSON input,
but `application/json` is preferred. The client may optionally
send `;charset=utf-8` with the Content-Type, but any other
encoding produces undefined results. Behaviour without the charset
or with `;charset=utf-8` suffix is identical.
- **POST data must be an non-form-encoded JSON string**
(ASCII or UTF-8). jQuery, by default, form-urlencodes it, which the
fossil json bits cannot read. e.g. post the result of
`JSON.stringify(requestObject)`, without any additional encoding on
top of it.
- **When POSTing via jQuery**, set these AJAX options:
- `contentType:'application/json'`
- `dataType:'text'`
- `data:JSON.stringify(requestObject)`
- **When POSTing via XMLHttpRequest** (XHR), be sure to:
- `xhr.open( … )`
- `xhr.setRequestHeader("Content-Type", "application/json")`
- `xhr.send( JSON.stringify( requestObject ) )`
The response will be (except in the case of an HTTP 500 error or
similar) a JSON or JSONP string, ready to be parsed by your favourite
`JSON.parse()` implementation or `eval()`'d directly.
<a id="curl-wget"></a>
## Using `curl` and `wget`
Both [curl](https://curl.haxx.se/) and
[wget](https://www.gnu.org/software/wget/) can be used to post data to
this API from the command line or scripts, but both require an extra
parameter to set the request encoding.
Example:
```console
$ cat x.json
{
"payload": {
"sql": "SELECT * FROM reportfmt limit 1",
"format": "o"
}
}
# Fossil has been started locally with:
# fossil server --localauth
# which allows the following requests to work without extra
# authenticaion:
$ wget -q -O- \
--post-file=x.json \
--header="Content-Type: application/json" \
'http://localhost:8080/json/query'
$ curl \
--data-binary @x.json \
--header 'Content-Type: application/json' \
'http://localhost:8080/json/query'
```
The relevant parts for encoding are the `--header` flag for `wget` and
`curl`, noting that they have different syntaxes for each
(`--header=X` vs `--header X`).
<a id="javascript"></a>
# Example JavaScript (Browser and Shell)
In the fossil source tree, [in the ajax directory](/dir/ajax), is test/demo code
implemented in HTML+JavaScript. While it is still quite experimental, it
demonstrates one approach to creating client-side wrapper APIs for
remote Fossil/JSON repositories.
There is some additional JS test code, which uses the Rhino JS engine
(i.e. from the console, not the browser), under
[`ajax/i-test`](/dir/ajax/-itest). That adds a Rhino-based connection
back-end to the AJAJ API and uses it for running integration-style
tests against an arbitrary JSON-capable repository.
<a id="demo-apps"></a>
# Demo Apps
Known in-the-wild apps using this API:
- The wiki browsers/editors at [](https://fossil.wanderinghorse.net/wikis/)
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Managing Server Load
A Fossil server is very efficient and normally presents a very light
load on the server. The Fossil [self-hosting server][sh] is a 1/24th
slice VM at [Linode.com][lin] hosting 65 other repositories in addition
to Fossil, including some very high-traffic sites such as
<http://www.sqlite.org> and <http://system.data.sqlite.org>. This small
VM has a typical load of 0.05 to 0.1. A single HTTP request to Fossil
normally takes less than 10 milliseconds of CPU time to complete, so
requests can be arriving at a continuous rate of 20 or more per second,
and the CPU can still be mostly idle.
However, there are some Fossil web pages that can consume large amounts
of CPU time, especially on repositories with a large number of files or
with long revision histories. High CPU usage pages include
[`/zip`](/help/zip), [`/tarball`](/help/tarball),
[`/annotate`](/help/annotate), and others. On very large repositories,
these commands can take 15 seconds or more of CPU time. If these kinds
of requests arrive too quickly, the load average on the server can grow
dramatically, making the server unresponsive.
Fossil provides two capabilities to help avoid server overload problems
due to excessive requests to expensive pages:
1. An optional cache is available that remembers the 10 most recently
requested `/zip` or `/tarball` pages and returns the precomputed
answer if the same page is requested again.
2. Page requests can be configured to fail with a
“[503 Server Overload][503]” HTTP error if an expensive request is
received while the host load average is too high.
Both of these load-control mechanisms are turned off by default, but
they are recommended for high-traffic sites.
The webpage cache is activated using the [`fossil cache init`](/help/cache)
command-line on the server. Add a `-R` option to
specify the specific repository for which to enable caching. If running
this command as root, be sure to “`chown`” the cache database to give
the Fossil server write permission for the user ID of the web server;
this is a separate file in the same directory and with the same name as
the repository but with the “`.fossil`” suffix changed to “`.cache`”.
To activate the server load control feature visit the Admin → Access
setup page in the administrative web interface; in the “**Server Load
Average Limit**” box enter the load average threshold above which “503
Server Overload” replies will be issued for expensive requests. On the
self-hosting Fossil server, that value is set to 1.5, but you could
easily set it higher on a multi-core server.
The maximum load average can also be set on the command line using
commands like this:
fossil set max-loadavg 1.5
fossil all set max-loadavg 1.5
The second form is especially useful for changing the maximum load
average simultaneously on a large number of repositories.
Note that this load-average limiting feature is only available on
operating systems that support the [`getloadavg()`][gla] API. Most
modern Unix systems have this interface, but Windows does not, so the
feature will not work on Windows.
Because Linux implements `getloadavg()` by accessing the `/proc/loadavg`
virtual file, you will need to make sure `/proc` is available to the
Fossil server. The most common reason for it to not be available is that
you are running a Fossil instance [inside a `chroot(2)`
jail](./chroot.md) and you have not mounted the `/proc` virtual file
system inside that jail. On the [self-hosting Fossil repositories][sh],
this was accomplished by adding a line to the `/etc/fstab` file:
chroot_jail_proc /home/www/proc proc ro 0 0
The `/home/www/proc` pathname should be adjusted so that the `/proc`
component is at the root of the chroot jail, of course.
To see if the load-average limiter is functional, visit the
[`/test_env`][hte] page of the server to view the current load average.
If the value for the load average is greater than zero, that means that
it is possible to activate the load-average limiter on that repository.
If the load average shows exactly "0.0", then that means that Fossil is
unable to find the load average. This can either be because it is in a
`chroot(2)` jail without `/proc` access, or because it is running on a
system that does not support `getloadavg()` and so the load-average
limiter will not function.
[503]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.4
[hte]: /help?cmd=/test_env
[gla]: https://linux.die.net/man/3/getloadavg
[lin]: http://www.linode.com
[sh]: ./selfhost.wiki
|
| ︙ | ︙ | |||
295 296 297 298 299 300 301 | the appropriate SSL implementation. And, of course, Fossil needs to link against the standard C library. No other libraries or external dependences are used. Fossil includes a copy of [https://github.com/richgel999/miniz | miniz] which can be used as an alternative to zlib. | > > > > > > > > > > > > > > > > > > > > > > > > > > | | 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 | the appropriate SSL implementation. And, of course, Fossil needs to link against the standard C library. No other libraries or external dependences are used. Fossil includes a copy of [https://github.com/richgel999/miniz | miniz] which can be used as an alternative to zlib. <h1>7.0 Debugging</h1> Debug mode is controlled via FOSSIL_DEBUG preprocessor macro which could be set explicitly at the make command for the target platform. However, in practice it is instead recommended to add a respective configure option for the target platform and then perform a clean build. This way the Debug flags are consistently applied across the whole build process. For example, use these Debug flags in addition to other flags passed to the configure scripts: On Linux, *NIX and similar platforms: <blockquote><pre> ./configure --fossil-debug </pre></blockquote> On Windows: <blockquote><pre> win\buildmsvc.bat FOSSIL_DEBUG=1 </pre></blockquote> The resulting fossil binary could then be loaded into a platform-specific debugger. Source files displayed in the debugger correspond to the ones generated from the translation stage of the build process, that is what was actually compiled into the object files. <h1>8.0 See Also</h1> * [./tech_overview.wiki | A Technical Overview Of Fossil] * [./adding_code.wiki | How To Add Features To Fossil] |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | # Markdown Link-test This document exist solely as a test for some of the hyperlinking capabilities of Markdown as implemented by Fossil. ## Relative-Path Links * The index: [](../index.wiki) * Load management: [](../loadmgmt.md) * Site-map: [](../../../../sitemap) * Windows CGI: [](../server/windows/cgi.md) ## The Magic $ROOT Path Prefix In text of the form `href="$ROOT/..."` in the HTML that markdown generates, the $ROOT is replaced by the complete URI for the root of the document tree. Note that the $ROOT translation only occurs within the `<a href="...">` element, not within the text of the hyperlink. So you should see the $ROOT text on this page, but if you mouse-over the hyperlink the $ROOT value should have been expanded to the actual document root. * Timeline: []($ROOT/timeline) * Site-map: []($ROOT/sitemap) The $ROOT prefix on markdown links is superfluous. The same link works without the $ROOT prefix. (Though: the $ROOT prefix is required for HTML documents.) * Timeline: [](/timeline) * Help: [](/help?cmd=help) * Site-map: [](/sitemap) ## The Magic $CURRENT Document Version Translation In URI text of the form `.../doc/$CURRENT/...` the $CURRENT value is converted to the version number of the document currently being displayed. This conversion happens after translation into HTML and only occurs on href='...' attributes so it does not occur for plain text. * Document index: [](/doc/$CURRENT/www/index.wiki) Both the $ROOT and the $CURRENT conversions can occur on the same link. * Document index: []($ROOT/doc/$CURRENT/www/index.wiki) The translations must be contained within HTML markup in order to work. They do not work for ordinary text that appears to be an href= attribute. * `x href='$ROOT/timeline'` * `x action="$ROOT/whatever"` * `x href="https://some-other-site.com/doc/$CURRENT/tail"` |
1 2 3 4 | # Limitations On Git Mirrors The "<tt>[fossil git export](/help?cmd=git)</tt>" command can be used to mirror a Fossil repository to Git. | | | > > > > > > > > > > > > | | < | > | | > > | | | > | | > | > | | | > > > > > > > > | | | | | | | | 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 | # Limitations On Git Mirrors The "<tt>[fossil git export](/help?cmd=git)</tt>" command can be used to mirror a Fossil repository to Git. ([Setup instructions](./mirrortogithub.md) and an [example](https://github.com/drhsqlite/fossil-mirror).) But the export to Git is not perfect. Some information is lost during export due to limitations in Git. This page describes what content of Fossil is not included in an export to Git. ## (1) Wiki, Tickets, Technotes, Forum Git only supports version control. The additional features of Fossil such as Wiki, Tickets, Technotes, and the Forum are not supported in Git, so those features are not included in an export. Third-party Git based tooling may add some of these features (e.g. GitHub, GitLab) but because their data are not stored in the Git blockchain, there is no single destination for Fossil to convert its equivalent data *to*. For instance, Fossil tickets do not become GitHub issues, because that is a proprietary feature of GitHub separate from Git proper, stored outside the blockchain on the GitHub servers. You can also see the problem in its inverse case: you do not get a copy of your GitHub issues when cloning the Git repository. You *do* get the Fossil tickets, wiki, forum posts, etc. when cloning a remote Fossil repo. ## (2) Cherrypick Merges The Git client supports cherrypick merges but does not record the cherrypick parent(s). Fossil tracks cherrypick merges in its blockchain and displays cherrypicks in its timeline. (As an example, the dashed lines [here](/timeline?c=0a9f12ce6655b7a5) are cherrypicks.) Because Git does not have a way to represent this same information in its blockchain, the history of Fossil cherrypicks cannot be exported to Git, only their direct effects on the managed file data. ## (3) Named Branches Git has only limited support for named branches. Git identifies the head check-in of each branch. Depending on the check-in graph topology, this is sufficient to infer the branch for many historical check-ins as well. However, complex histories with lots of cross-merging can lead to ambiguities. Fossil keeps track of historical branch names unambiguously, but the extra details about branch names that Fossil keeps at hand cannot be exported to Git. ## (4) Non-unique Tags Git requires tags to be unique: each tag must refer to exactly one check-in. Fossil does not have this restriction, and so it is common in Fossil to tag multiple check-ins with the same name. For example, it is common in Fossil to tag each check-in creating a release both with a unique version tag *and* a common tag like "release" so that all historical releases can be found at once. ([Example](/timeline?t=release).) Git does not allow this. The "release" tag must refer to just one check-in. The work-around is that the non-unique tag in the Git export is made to refer to only the most recent check-in with that tag. This is why the ["release" tag view][ghrtv] in the GitHub mirror of this repository shows only the latest release version; contrast the prior example. Both URLs are asking the repository the same question, but because of Git's relatively impoverished data model, it cannot give the same answer that Fossil does. [ghrtv]: https://github.com/drhsqlite/fossil-mirror/tree/release ## (5) Amendments To Check-ins Check-ins are immutable in both Fossil and Git. However, Fossil has a mechanism by which tags can be added to its blockchain to provide after-the-fact corrections to prior check-ins. For example, tags can be added to check-ins that correct typos in the check-in comment. The original check-in is immutable and so the original comment is preserved in addition to the correction. But software that displays the check-ins knows to look for the comment-change tag and if present displays the corrected comment rather than the original. ([Example](/info/8ed91bbe44d0d383) changing the typo "os" into "so".) Git has no mechanism for providing corrections or clarifications to historical check-ins. When exporting from Fossil to Git, the latest corrections to a Fossil check-in are used to generate the corresponding Git check-in. But once the Git check-in has been created, any subsequent corrections are omitted as there is no way to transfer them to Git. |
| ︙ | ︙ | |||
114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
* There is a
[long list of restrictions](https://git-scm.com/docs/git-check-ref-format)
on tag and branch names in Git. If any of your Fossil tag or branch names
violate these rules, then the names are translated prior to being exported
to Git. The translation usually involves converting the offending characters
into underscores.
## Example GitHub Mirrors
As of this writing (2019-03-16) Fossil’s own repository is mirrored
on GitHub at:
>
<https://github.com/drhsqlite/fossil-mirror>
| > | 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
* There is a
[long list of restrictions](https://git-scm.com/docs/git-check-ref-format)
on tag and branch names in Git. If any of your Fossil tag or branch names
violate these rules, then the names are translated prior to being exported
to Git. The translation usually involves converting the offending characters
into underscores.
<a name='ex1'></a>
## Example GitHub Mirrors
As of this writing (2019-03-16) Fossil’s own repository is mirrored
on GitHub at:
>
<https://github.com/drhsqlite/fossil-mirror>
|
| ︙ | ︙ |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/usr/bin/env tclsh
#
# Run this TCL script to generate a WIKI page that contains a
# permuted index of the various documentation files.
#
# tclsh mkindex.tcl
#
set doclist {
aboutcgi.wiki {How CGI Works In Fossil}
aboutdownload.wiki {How The Download Page Works}
adding_code.wiki {Adding New Features To Fossil}
adding_code.wiki {Hacking Fossil}
| < > > > > > > > > > > > > > | | 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 |
#!/usr/bin/env tclsh
#
# Run this TCL script to generate a WIKI page that contains a
# permuted index of the various documentation files.
#
# tclsh mkindex.tcl
#
set doclist {
aboutcgi.wiki {How CGI Works In Fossil}
aboutdownload.wiki {How The Download Page Works}
adding_code.wiki {Adding New Features To Fossil}
adding_code.wiki {Hacking Fossil}
alerts.md {Email Alerts And Notifications}
antibot.wiki {Defense against Spiders and Bots}
backoffice.md {The "Backoffice" mechanism of Fossil}
blame.wiki {The Annotate/Blame Algorithm Of Fossil}
blockchain.md {Fossil As Blockchain}
branching.wiki {Branching, Forking, Merging, and Tagging}
bugtheory.wiki {Bug Tracking In Fossil}
build.wiki {Compiling and Installing Fossil}
caps/ {Administering User Capabilities}
caps/admin-v-setup.md {Differences Between Setup and Admin Users}
caps/ref.html {User Capability Reference}
cgi.wiki {CGI Script Configuration Options}
changes.wiki {Fossil Changelog}
checkin_names.wiki {Check-in And Version Names}
checkin.wiki {Check-in Checklist}
childprojects.wiki {Child Projects}
copyright-release.html {Contributor License Agreement}
concepts.wiki {Fossil Core Concepts}
contribute.wiki {Contributing Code or Documentation To The Fossil Project}
css-tricks.md {Fossil CSS Tips and Tricks}
customgraph.md {Theming: Customizing the Timeline Graph}
customskin.md {Theming: Customizing The Appearance of Web Pages}
customskin.md {Custom Skins}
custom_ticket.wiki {Customizing The Ticket System}
defcsp.md {The Default Content Security Policy}
delta_encoder_algorithm.wiki {Fossil Delta Encoding Algorithm}
delta_format.wiki {Fossil Delta Format}
embeddeddoc.wiki {Embedded Project Documentation}
encryptedrepos.wiki {How To Use Encrypted Repositories}
env-opts.md {Environment Variables and Global Options}
event.wiki {Events}
faq.wiki {Frequently Asked Questions}
fileedit-page.md {The fileedit Page}
fileformat.wiki {Fossil File Format}
fiveminutes.wiki {Up and Running in 5 Minutes as a Single User}
forum.wiki {Fossil Forums}
foss-cklist.wiki {Checklist For Successful Open-Source Projects}
fossil-from-msvc.wiki {Integrating Fossil in the Microsoft Express 2010 IDE}
fossil_prompt.wiki {Fossilized Bash Prompt}
fossil-v-git.wiki {Fossil Versus Git}
globs.md {File Name Glob Patterns}
gitusers.md {Hints For Users With Git Experience}
grep.md {Fossil grep vs POSIX grep}
hacker-howto.wiki {Hacker How-To}
hacker-howto.wiki {Fossil Developers Guide}
hashes.md {Hashes: Fossil Artifact Identification}
hashpolicy.wiki {Hash Policy: Choosing Between SHA1 and SHA3-256}
/help {Lists of Commands and Webpages}
hints.wiki {Fossil Tips And Usage Hints}
history.md {The Purpose And History Of Fossil}
index.wiki {Home Page}
inout.wiki {Import And Export To And From Git}
image-format-vs-repo-size.md {Image Format vs Fossil Repo Size}
javascript.md {Use of JavaScript in Fossil}
makefile.wiki {The Fossil Build Process}
mirrorlimitations.md {Limitations On Git Mirrors}
mirrortogithub.md {How To Mirror A Fossil Repository On GitHub}
/md_rules {Markdown Formatting Rules}
newrepo.wiki {How To Create A New Fossil Repository}
password.wiki {Password Management And Authentication}
pop.wiki {Principles Of Operation}
private.wiki {Creating, Syncing, and Deleting Private Branches}
qandc.wiki {Questions And Criticisms}
quickstart.wiki {Fossil Quick Start Guide}
quotes.wiki
{Quotes: What People Are Saying About Fossil, Git, and DVCSes in General}
../test/release-checklist.wiki {Pre-Release Testing Checklist}
rebaseharm.md {Rebase Considered Harmful}
reviews.wiki {Reviews}
selfcheck.wiki {Fossil Repository Integrity Self Checks}
selfhost.wiki {Fossil Self Hosting Repositories}
server/ {How To Configure A Fossil Server}
serverext.wiki {CGI Server Extensions}
serverext.wiki {Adding Extensions To A Fossil Server Using CGI Scripts}
settings.wiki {Fossil Settings}
/sitemap {Site Map}
shunning.wiki {Shunning: Deleting Content From Fossil}
stats.wiki {Performance Statistics}
style.wiki {Source Code Style Guidelines}
|
| ︙ | ︙ | |||
125 126 127 128 129 130 131 | <input type="text" name="s" size="40" autofocus> <input type="submit" value="Search Docs"> </form> </center> <h2>Primary Documents:</h2> <ul> <li> <a href='quickstart.wiki'>Quick-start Guide</a> | | > > > < < | 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 |
<input type="text" name="s" size="40" autofocus>
<input type="submit" value="Search Docs">
</form>
</center>
<h2>Primary Documents:</h2>
<ul>
<li> <a href='quickstart.wiki'>Quick-start Guide</a>
<li> <a href='history.md'>Purpose and History of Fossil</a>
<li> <a href='build.wiki'>Compiling and installing Fossil</a>
<li> <a href='../COPYRIGHT-BSD2.txt'>License</a>
<li> <a href='$ROOT/help'>List of commands, web-pages, and settings</a>
<li> <a href='userlinks.wiki'>Miscellaneous Docs for Fossil Users</a>
<li> <a href='hacker-howto.wiki'>Fossil Developer's Guide</a>
<li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's
book</a>
</ul>
<a name="pindex"></a>
<h2>Permuted Index:</h2>
<ul>}
foreach entry $permindex {
foreach {title file bold} $entry break
if {$bold} {set title <b>$title</b>}
if {[string match /* $file]} {set file ../../..$file}
puts $out "<li><a href=\"$file\">$title</a></li>"
}
puts $out "</ul></div>"
|
| ︙ | ︙ | |||
71 72 73 74 75 76 77 | hashes the password and compares it against the value stored in USER.PW. If they match, the server sets a cookie on the client to record the login. This cookie contains a large amount of high-quality randomness and is thus intractable to guess. The value of the cookie and the IP address of the client is stored in the USER.COOKIE and USER.IPADDR fields of the USER table on the server. The USER.CEXPIRE field holds an expiration date | | | 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | hashes the password and compares it against the value stored in USER.PW. If they match, the server sets a cookie on the client to record the login. This cookie contains a large amount of high-quality randomness and is thus intractable to guess. The value of the cookie and the IP address of the client is stored in the USER.COOKIE and USER.IPADDR fields of the USER table on the server. The USER.CEXPIRE field holds an expiration date for the cookie, encoded as a Julian day number. On all subsequent HTTP requests, the cookie value is matched against the USER table to enable access to the repository. A login cookie will only work if the IP address matches. This feature is designed to make it more difficult for an attacker to sniff the cookie and take over the connection. A cookie-sniffing attack will only work if the attacker is able to send and receive from the same IP address as |
| ︙ | ︙ | |||
95 96 97 98 99 100 101 | Note that in order to log into a Fossil server, it is necessary to write information into the repository database. Hence, login is not possible on a Fossil repository with a read-only database file. The user password is sent over the wire as cleartext on the initial login attempt. The plan moving forward is to compute the SHA1 hash of | | | 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | Note that in order to log into a Fossil server, it is necessary to write information into the repository database. Hence, login is not possible on a Fossil repository with a read-only database file. The user password is sent over the wire as cleartext on the initial login attempt. The plan moving forward is to compute the SHA1 hash of the password on the client using JavaScript and then send only the hash over the wire, but that plan has not yet been set in code. <h2>Sync Protocol Authentication</h2> A different authentication mechanism is used when one repository wants to sync (or push or pull or clone) another repository. When two repositories are syncing, the one that initiates the transaction is |
| ︙ | ︙ |
1 2 3 4 5 6 7 8 9 10 11 | <div class='fossil-doc' data-title='Index Of Fossil Documentation'> <center> <form action='$ROOT/docsrch' method='GET'> <input type="text" name="s" size="40" autofocus> <input type="submit" value="Search Docs"> </form> </center> <h2>Primary Documents:</h2> <ul> <li> <a href='quickstart.wiki'>Quick-start Guide</a> | | > > > < < | > > < | > > > | > > > > > | > > > > > > > > > | > | 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 | <div class='fossil-doc' data-title='Index Of Fossil Documentation'> <center> <form action='$ROOT/docsrch' method='GET'> <input type="text" name="s" size="40" autofocus> <input type="submit" value="Search Docs"> </form> </center> <h2>Primary Documents:</h2> <ul> <li> <a href='quickstart.wiki'>Quick-start Guide</a> <li> <a href='history.md'>Purpose and History of Fossil</a> <li> <a href='build.wiki'>Compiling and installing Fossil</a> <li> <a href='../COPYRIGHT-BSD2.txt'>License</a> <li> <a href='$ROOT/help'>List of commands, web-pages, and settings</a> <li> <a href='userlinks.wiki'>Miscellaneous Docs for Fossil Users</a> <li> <a href='hacker-howto.wiki'>Fossil Developer's Guide</a> <li> <a href='http://www.fossil-scm.org/schimpf-book/home'>Jim Schimpf's book</a> </ul> <a name="pindex"></a> <h2>Permuted Index:</h2> <ul> <li><a href="fiveminutes.wiki">5 Minutes as a Single User — Up and Running in</a></li> <li><a href="fossil-from-msvc.wiki">2010 IDE — Integrating Fossil in the Microsoft Express</a></li> <li><a href="tech_overview.wiki"><b>A Technical Overview Of The Design And Implementation Of Fossil</b></a></li> <li><a href="serverext.wiki"><b>Adding Extensions To A Fossil Server Using CGI Scripts</b></a></li> <li><a href="adding_code.wiki"><b>Adding New Features To Fossil</b></a></li> <li><a href="caps/admin-v-setup.md">Admin Users — Differences Between Setup and</a></li> <li><a href="caps/"><b>Administering User Capabilities</b></a></li> <li><a href="copyright-release.html">Agreement — Contributor License</a></li> <li><a href="alerts.md">Alerts And Notifications — Email</a></li> <li><a href="delta_encoder_algorithm.wiki">Algorithm — Fossil Delta Encoding</a></li> <li><a href="blame.wiki">Algorithm Of Fossil — The Annotate/Blame</a></li> <li><a href="blame.wiki">Annotate/Blame Algorithm Of Fossil — The</a></li> <li><a href="customskin.md">Appearance of Web Pages — Theming: Customizing The</a></li> <li><a href="faq.wiki">Asked Questions — Frequently</a></li> <li><a href="password.wiki">Authentication — Password Management And</a></li> <li><a href="backoffice.md">Backoffice mechanism of Fossil — The</a></li> <li><a href="fossil_prompt.wiki">Bash Prompt — Fossilized</a></li> <li><a href="whyusefossil.wiki"><b>Benefits Of Version Control</b></a></li> <li><a href="caps/admin-v-setup.md">Between Setup and Admin Users — Differences</a></li> <li><a href="hashpolicy.wiki">Between SHA1 and SHA3-256 — Hash Policy: Choosing</a></li> <li><a href="blockchain.md">Blockchain — Fossil As</a></li> <li><a href="antibot.wiki">Bots — Defense against Spiders and</a></li> <li><a href="private.wiki">Branches — Creating, Syncing, and Deleting Private</a></li> <li><a href="branching.wiki"><b>Branching, Forking, Merging, and Tagging</b></a></li> <li><a href="bugtheory.wiki"><b>Bug Tracking In Fossil</b></a></li> <li><a href="makefile.wiki">Build Process — The Fossil</a></li> <li><a href="caps/">Capabilities — Administering User</a></li> <li><a href="caps/ref.html">Capability Reference — User</a></li> <li><a href="cgi.wiki"><b>CGI Script Configuration Options</b></a></li> <li><a href="serverext.wiki">CGI Scripts — Adding Extensions To A Fossil Server Using</a></li> <li><a href="serverext.wiki"><b>CGI Server Extensions</b></a></li> <li><a href="aboutcgi.wiki">CGI Works In Fossil — How</a></li> <li><a href="changes.wiki">Changelog — Fossil</a></li> <li><a href="checkin_names.wiki"><b>Check-in And Version Names</b></a></li> <li><a href="checkin.wiki"><b>Check-in Checklist</b></a></li> <li><a href="checkin.wiki">Checklist — Check-in</a></li> <li><a href="../test/release-checklist.wiki">Checklist — Pre-Release Testing</a></li> <li><a href="foss-cklist.wiki"><b>Checklist For Successful Open-Source Projects</b></a></li> <li><a href="selfcheck.wiki">Checks — Fossil Repository Integrity Self</a></li> <li><a href="childprojects.wiki"><b>Child Projects</b></a></li> <li><a href="hashpolicy.wiki">Choosing Between SHA1 and SHA3-256 — Hash Policy:</a></li> <li><a href="contribute.wiki">Code or Documentation To The Fossil Project — Contributing</a></li> <li><a href="style.wiki">Code Style Guidelines — Source</a></li> <li><a href="../../../help">Commands and Webpages — Lists of</a></li> <li><a href="build.wiki"><b>Compiling and Installing Fossil</b></a></li> <li><a href="concepts.wiki">Concepts — Fossil Core</a></li> <li><a href="cgi.wiki">Configuration Options — CGI Script</a></li> <li><a href="server/">Configure A Fossil Server — How To</a></li> <li><a href="rebaseharm.md">Considered Harmful — Rebase</a></li> <li><a href="shunning.wiki">Content From Fossil — Shunning: Deleting</a></li> <li><a href="defcsp.md">Content Security Policy — The Default</a></li> <li><a href="contribute.wiki"><b>Contributing Code or Documentation To The Fossil Project</b></a></li> <li><a href="copyright-release.html"><b>Contributor License Agreement</b></a></li> <li><a href="whyusefossil.wiki">Control — Benefits Of Version</a></li> <li><a href="concepts.wiki">Core Concepts — Fossil</a></li> <li><a href="newrepo.wiki">Create A New Fossil Repository — How To</a></li> <li><a href="private.wiki"><b>Creating, Syncing, and Deleting Private Branches</b></a></li> <li><a href="qandc.wiki">Criticisms — Questions And</a></li> <li><a href="css-tricks.md">CSS Tips and Tricks — Fossil</a></li> <li><a href="customskin.md"><b>Custom Skins</b></a></li> <li><a href="customskin.md">Customizing The Appearance of Web Pages — Theming:</a></li> <li><a href="custom_ticket.wiki"><b>Customizing The Ticket System</b></a></li> <li><a href="customgraph.md">Customizing the Timeline Graph — Theming:</a></li> <li><a href="tech_overview.wiki">Databases Used By Fossil — SQLite</a></li> <li><a href="defcsp.md">Default Content Security Policy — The</a></li> <li><a href="antibot.wiki"><b>Defense against Spiders and Bots</b></a></li> <li><a href="shunning.wiki">Deleting Content From Fossil — Shunning:</a></li> <li><a href="private.wiki">Deleting Private Branches — Creating, Syncing, and</a></li> <li><a href="delta_encoder_algorithm.wiki">Delta Encoding Algorithm — Fossil</a></li> <li><a href="delta_format.wiki">Delta Format — Fossil</a></li> <li><a href="tech_overview.wiki">Design And Implementation Of Fossil — A Technical Overview Of The</a></li> <li><a href="theory1.wiki">Design Of The Fossil DVCS — Thoughts On The</a></li> <li><a href="hacker-howto.wiki">Developers Guide — Fossil</a></li> <li><a href="caps/admin-v-setup.md"><b>Differences Between Setup and Admin Users</b></a></li> <li><a href="embeddeddoc.wiki">Documentation — Embedded Project</a></li> <li><a href="contribute.wiki">Documentation To The Fossil Project — Contributing Code or</a></li> <li><a href="aboutdownload.wiki">Download Page Works — How The</a></li> <li><a href="theory1.wiki">DVCS — Thoughts On The Design Of The Fossil</a></li> <li><a href="quotes.wiki">DVCSes in General — Quotes: What People Are Saying About Fossil, Git, and</a></li> <li><a href="alerts.md"><b>Email Alerts And Notifications</b></a></li> <li><a href="embeddeddoc.wiki"><b>Embedded Project Documentation</b></a></li> <li><a href="delta_encoder_algorithm.wiki">Encoding Algorithm — Fossil Delta</a></li> <li><a href="encryptedrepos.wiki">Encrypted Repositories — How To Use</a></li> <li><a href="env-opts.md"><b>Environment Variables and Global Options</b></a></li> <li><a href="event.wiki"><b>Events</b></a></li> <li><a href="webpage-ex.md">Examples — Webpage</a></li> <li><a href="gitusers.md">Experience — Hints For Users With Git</a></li> <li><a href="inout.wiki">Export To And From Git — Import And</a></li> <li><a href="fossil-from-msvc.wiki">Express 2010 IDE — Integrating Fossil in the Microsoft</a></li> <li><a href="serverext.wiki">Extensions — CGI Server</a></li> <li><a href="serverext.wiki">Extensions To A Fossil Server Using CGI Scripts — Adding</a></li> <li><a href="adding_code.wiki">Features To Fossil — Adding New</a></li> <li><a href="fileformat.wiki">File Format — Fossil</a></li> <li><a href="globs.md"><b>File Name Glob Patterns</b></a></li> <li><a href="fileedit-page.md">fileedit Page — The</a></li> <li><a href="unvers.wiki">Files — Unversioned</a></li> <li><a href="branching.wiki">Forking, Merging, and Tagging — Branching,</a></li> <li><a href="delta_format.wiki">Format — Fossil Delta</a></li> <li><a href="fileformat.wiki">Format — Fossil File</a></li> <li><a href="image-format-vs-repo-size.md">Format vs Fossil Repo Size — Image</a></li> <li><a href="../../../md_rules">Formatting Rules — Markdown</a></li> <li><a href="../../../wiki_rules">Formatting Rules — Wiki</a></li> <li><a href="forum.wiki">Forums — Fossil</a></li> <li><a href="blockchain.md"><b>Fossil As Blockchain</b></a></li> <li><a href="changes.wiki"><b>Fossil Changelog</b></a></li> <li><a href="concepts.wiki"><b>Fossil Core Concepts</b></a></li> <li><a href="css-tricks.md"><b>Fossil CSS Tips and Tricks</b></a></li> <li><a href="delta_encoder_algorithm.wiki"><b>Fossil Delta Encoding Algorithm</b></a></li> <li><a href="delta_format.wiki"><b>Fossil Delta Format</b></a></li> <li><a href="hacker-howto.wiki"><b>Fossil Developers Guide</b></a></li> <li><a href="fileformat.wiki"><b>Fossil File Format</b></a></li> <li><a href="forum.wiki"><b>Fossil Forums</b></a></li> <li><a href="grep.md"><b>Fossil grep vs POSIX grep</b></a></li> <li><a href="quickstart.wiki"><b>Fossil Quick Start Guide</b></a></li> <li><a href="selfcheck.wiki"><b>Fossil Repository Integrity Self Checks</b></a></li> <li><a href="selfhost.wiki"><b>Fossil Self Hosting Repositories</b></a></li> <li><a href="settings.wiki"><b>Fossil Settings</b></a></li> <li><a href="hints.wiki"><b>Fossil Tips And Usage Hints</b></a></li> <li><a href="fossil-v-git.wiki"><b>Fossil Versus Git</b></a></li> <li><a href="quotes.wiki">Fossil, Git, and DVCSes in General — Quotes: What People Are Saying About</a></li> <li><a href="fossil_prompt.wiki"><b>Fossilized Bash Prompt</b></a></li> <li><a href="faq.wiki"><b>Frequently Asked Questions</b></a></li> <li><a href="quotes.wiki">General — Quotes: What People Are Saying About Fossil, Git, and DVCSes in</a></li> <li><a href="fossil-v-git.wiki">Git — Fossil Versus</a></li> <li><a href="inout.wiki">Git — Import And Export To And From</a></li> <li><a href="gitusers.md">Git Experience — Hints For Users With</a></li> <li><a href="mirrorlimitations.md">Git Mirrors — Limitations On</a></li> <li><a href="quotes.wiki">Git, and DVCSes in General — Quotes: What People Are Saying About Fossil,</a></li> <li><a href="mirrortogithub.md">GitHub — How To Mirror A Fossil Repository On</a></li> <li><a href="globs.md">Glob Patterns — File Name</a></li> <li><a href="env-opts.md">Global Options — Environment Variables and</a></li> <li><a href="customgraph.md">Graph — Theming: Customizing the Timeline</a></li> <li><a href="grep.md">grep — Fossil grep vs POSIX</a></li> <li><a href="grep.md">grep vs POSIX grep — Fossil</a></li> <li><a href="hacker-howto.wiki">Guide — Fossil Developers</a></li> <li><a href="quickstart.wiki">Guide — Fossil Quick Start</a></li> <li><a href="style.wiki">Guidelines — Source Code Style</a></li> <li><a href="hacker-howto.wiki"><b>Hacker How-To</b></a></li> <li><a href="adding_code.wiki"><b>Hacking Fossil</b></a></li> <li><a href="rebaseharm.md">Harmful — Rebase Considered</a></li> <li><a href="hashpolicy.wiki"><b>Hash Policy: Choosing Between SHA1 and SHA3-256</b></a></li> <li><a href="hints.wiki">Hints — Fossil Tips And Usage</a></li> <li><a href="gitusers.md"><b>Hints For Users With Git Experience</b></a></li> <li><a href="history.md">History Of Fossil — The Purpose And</a></li> <li><a href="index.wiki"><b>Home Page</b></a></li> <li><a href="selfhost.wiki">Hosting Repositories — Fossil Self</a></li> <li><a href="aboutcgi.wiki"><b>How CGI Works In Fossil</b></a></li> <li><a href="aboutdownload.wiki"><b>How The Download Page Works</b></a></li> <li><a href="server/"><b>How To Configure A Fossil Server</b></a></li> <li><a href="newrepo.wiki"><b>How To Create A New Fossil Repository</b></a></li> <li><a href="mirrortogithub.md"><b>How To Mirror A Fossil Repository On GitHub</b></a></li> <li><a href="encryptedrepos.wiki"><b>How To Use Encrypted Repositories</b></a></li> <li><a href="hacker-howto.wiki">How-To — Hacker</a></li> <li><a href="tls-nginx.md">HTTPS with nginx — Proxying Fossil via</a></li> <li><a href="fossil-from-msvc.wiki">IDE — Integrating Fossil in the Microsoft Express 2010</a></li> <li><a href="image-format-vs-repo-size.md"><b>Image Format vs Fossil Repo Size</b></a></li> <li><a href="tech_overview.wiki">Implementation Of Fossil — A Technical Overview Of The Design And</a></li> <li><a href="inout.wiki"><b>Import And Export To And From Git</b></a></li> <li><a href="build.wiki">Installing Fossil — Compiling and</a></li> <li><a href="fossil-from-msvc.wiki"><b>Integrating Fossil in the Microsoft Express 2010 IDE</b></a></li> <li><a href="selfcheck.wiki">Integrity Self Checks — Fossil Repository</a></li> <li><a href="webui.wiki">Interface — The Fossil Web</a></li> <li><a href="javascript.md">JavaScript in Fossil — Use of</a></li> <li><a href="th1.md">Language — The TH1 Scripting</a></li> <li><a href="copyright-release.html">License Agreement — Contributor</a></li> <li><a href="mirrorlimitations.md"><b>Limitations On Git Mirrors</b></a></li> <li><a href="../../../help"><b>Lists of Commands and Webpages</b></a></li> <li><a href="password.wiki">Management And Authentication — Password</a></li> <li><a href="../../../sitemap">Map — Site</a></li> <li><a href="../../../md_rules"><b>Markdown Formatting Rules</b></a></li> |
| ︙ | ︙ | |||
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 | <li><a href="checkin_names.wiki">Names — Check-in And Version</a></li> <li><a href="adding_code.wiki">New Features To Fossil — Adding</a></li> <li><a href="newrepo.wiki">New Fossil Repository — How To Create A</a></li> <li><a href="tls-nginx.md">nginx — Proxying Fossil via HTTPS with</a></li> <li><a href="alerts.md">Notifications — Email Alerts And</a></li> <li><a href="foss-cklist.wiki">Open-Source Projects — Checklist For Successful</a></li> <li><a href="pop.wiki">Operation — Principles Of</a></li> <li><a href="env-opts.md">Options — Environment Variables and Global</a></li> <li><a href="tech_overview.wiki">Overview Of The Design And Implementation Of Fossil — A Technical</a></li> <li><a href="index.wiki">Page — Home</a></li> <li><a href="aboutdownload.wiki">Page Works — How The Download</a></li> <li><a href="customskin.md">Pages — Theming: Customizing The Appearance of Web</a></li> <li><a href="password.wiki"><b>Password Management And Authentication</b></a></li> <li><a href="globs.md">Patterns — File Name Glob</a></li> <li><a href="quotes.wiki">People Are Saying About Fossil, Git, and DVCSes in General — Quotes: What</a></li> <li><a href="stats.wiki"><b>Performance Statistics</b></a></li> <li><a href="hashpolicy.wiki">Policy: Choosing Between SHA1 and SHA3-256 — Hash</a></li> <li><a href="grep.md">POSIX grep — Fossil grep vs</a></li> <li><a href="../test/release-checklist.wiki"><b>Pre-Release Testing Checklist</b></a></li> <li><a href="pop.wiki"><b>Principles Of Operation</b></a></li> <li><a href="private.wiki">Private Branches — Creating, Syncing, and Deleting</a></li> <li><a href="makefile.wiki">Process — The Fossil Build</a></li> <li><a href="contribute.wiki">Project — Contributing Code or Documentation To The Fossil</a></li> <li><a href="embeddeddoc.wiki">Project Documentation — Embedded</a></li> <li><a href="foss-cklist.wiki">Projects — Checklist For Successful Open-Source</a></li> <li><a href="childprojects.wiki">Projects — Child</a></li> <li><a href="fossil_prompt.wiki">Prompt — Fossilized Bash</a></li> <li><a href="sync.wiki">Protocol — The Fossil Sync</a></li> <li><a href="tls-nginx.md"><b>Proxying Fossil via HTTPS with nginx</b></a></li> <li><a href="faq.wiki">Questions — Frequently Asked</a></li> <li><a href="qandc.wiki"><b>Questions And Criticisms</b></a></li> <li><a href="quickstart.wiki">Quick Start Guide — Fossil</a></li> <li><a href="quotes.wiki"><b>Quotes: What People Are Saying About Fossil, Git, and DVCSes in General</b></a></li> <li><a href="image-format-vs-repo-size.md">Repo Size — Image Format vs Fossil</a></li> <li><a href="selfhost.wiki">Repositories — Fossil Self Hosting</a></li> <li><a href="encryptedrepos.wiki">Repositories — How To Use Encrypted</a></li> <li><a href="newrepo.wiki">Repository — How To Create A New Fossil</a></li> <li><a href="selfcheck.wiki">Repository Integrity Self Checks — Fossil</a></li> <li><a href="mirrortogithub.md">Repository On GitHub — How To Mirror A Fossil</a></li> <li><a href="reviews.wiki"><b>Reviews</b></a></li> <li><a href="../../../md_rules">Rules — Markdown Formatting</a></li> <li><a href="../../../wiki_rules">Rules — Wiki Formatting</a></li> <li><a href="fiveminutes.wiki">Running in 5 Minutes as a Single User — Up and</a></li> <li><a href="quotes.wiki">Saying About Fossil, Git, and DVCSes in General — Quotes: What People Are</a></li> <li><a href="th1.md">Scripting Language — The TH1</a></li> <li><a href="serverext.wiki">Scripts — Adding Extensions To A Fossil Server Using CGI</a></li> <li><a href="selfcheck.wiki">Self Checks — Fossil Repository Integrity</a></li> <li><a href="selfhost.wiki">Self Hosting Repositories — Fossil</a></li> | > > > > > > > > | | | 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 | <li><a href="checkin_names.wiki">Names — Check-in And Version</a></li> <li><a href="adding_code.wiki">New Features To Fossil — Adding</a></li> <li><a href="newrepo.wiki">New Fossil Repository — How To Create A</a></li> <li><a href="tls-nginx.md">nginx — Proxying Fossil via HTTPS with</a></li> <li><a href="alerts.md">Notifications — Email Alerts And</a></li> <li><a href="foss-cklist.wiki">Open-Source Projects — Checklist For Successful</a></li> <li><a href="pop.wiki">Operation — Principles Of</a></li> <li><a href="cgi.wiki">Options — CGI Script Configuration</a></li> <li><a href="env-opts.md">Options — Environment Variables and Global</a></li> <li><a href="tech_overview.wiki">Overview Of The Design And Implementation Of Fossil — A Technical</a></li> <li><a href="index.wiki">Page — Home</a></li> <li><a href="fileedit-page.md">Page — The fileedit</a></li> <li><a href="aboutdownload.wiki">Page Works — How The Download</a></li> <li><a href="customskin.md">Pages — Theming: Customizing The Appearance of Web</a></li> <li><a href="password.wiki"><b>Password Management And Authentication</b></a></li> <li><a href="globs.md">Patterns — File Name Glob</a></li> <li><a href="quotes.wiki">People Are Saying About Fossil, Git, and DVCSes in General — Quotes: What</a></li> <li><a href="stats.wiki"><b>Performance Statistics</b></a></li> <li><a href="defcsp.md">Policy — The Default Content Security</a></li> <li><a href="hashpolicy.wiki">Policy: Choosing Between SHA1 and SHA3-256 — Hash</a></li> <li><a href="grep.md">POSIX grep — Fossil grep vs</a></li> <li><a href="../test/release-checklist.wiki"><b>Pre-Release Testing Checklist</b></a></li> <li><a href="pop.wiki"><b>Principles Of Operation</b></a></li> <li><a href="private.wiki">Private Branches — Creating, Syncing, and Deleting</a></li> <li><a href="makefile.wiki">Process — The Fossil Build</a></li> <li><a href="contribute.wiki">Project — Contributing Code or Documentation To The Fossil</a></li> <li><a href="embeddeddoc.wiki">Project Documentation — Embedded</a></li> <li><a href="foss-cklist.wiki">Projects — Checklist For Successful Open-Source</a></li> <li><a href="childprojects.wiki">Projects — Child</a></li> <li><a href="fossil_prompt.wiki">Prompt — Fossilized Bash</a></li> <li><a href="sync.wiki">Protocol — The Fossil Sync</a></li> <li><a href="tls-nginx.md"><b>Proxying Fossil via HTTPS with nginx</b></a></li> <li><a href="history.md">Purpose And History Of Fossil — The</a></li> <li><a href="faq.wiki">Questions — Frequently Asked</a></li> <li><a href="qandc.wiki"><b>Questions And Criticisms</b></a></li> <li><a href="quickstart.wiki">Quick Start Guide — Fossil</a></li> <li><a href="quotes.wiki"><b>Quotes: What People Are Saying About Fossil, Git, and DVCSes in General</b></a></li> <li><a href="rebaseharm.md"><b>Rebase Considered Harmful</b></a></li> <li><a href="caps/ref.html">Reference — User Capability</a></li> <li><a href="image-format-vs-repo-size.md">Repo Size — Image Format vs Fossil</a></li> <li><a href="selfhost.wiki">Repositories — Fossil Self Hosting</a></li> <li><a href="encryptedrepos.wiki">Repositories — How To Use Encrypted</a></li> <li><a href="newrepo.wiki">Repository — How To Create A New Fossil</a></li> <li><a href="selfcheck.wiki">Repository Integrity Self Checks — Fossil</a></li> <li><a href="mirrortogithub.md">Repository On GitHub — How To Mirror A Fossil</a></li> <li><a href="reviews.wiki"><b>Reviews</b></a></li> <li><a href="../../../md_rules">Rules — Markdown Formatting</a></li> <li><a href="../../../wiki_rules">Rules — Wiki Formatting</a></li> <li><a href="fiveminutes.wiki">Running in 5 Minutes as a Single User — Up and</a></li> <li><a href="quotes.wiki">Saying About Fossil, Git, and DVCSes in General — Quotes: What People Are</a></li> <li><a href="cgi.wiki">Script Configuration Options — CGI</a></li> <li><a href="th1.md">Scripting Language — The TH1</a></li> <li><a href="serverext.wiki">Scripts — Adding Extensions To A Fossil Server Using CGI</a></li> <li><a href="defcsp.md">Security Policy — The Default Content</a></li> <li><a href="selfcheck.wiki">Self Checks — Fossil Repository Integrity</a></li> <li><a href="selfhost.wiki">Self Hosting Repositories — Fossil</a></li> <li><a href="server/">Server — How To Configure A Fossil</a></li> <li><a href="serverext.wiki">Server Extensions — CGI</a></li> <li><a href="serverext.wiki">Server Using CGI Scripts — Adding Extensions To A Fossil</a></li> <li><a href="settings.wiki">Settings — Fossil</a></li> <li><a href="caps/admin-v-setup.md">Setup and Admin Users — Differences Between</a></li> <li><a href="hashpolicy.wiki">SHA1 and SHA3-256 — Hash Policy: Choosing Between</a></li> <li><a href="hashpolicy.wiki">SHA3-256 — Hash Policy: Choosing Between SHA1 and</a></li> <li><a href="shunning.wiki"><b>Shunning: Deleting Content From Fossil</b></a></li> <li><a href="fiveminutes.wiki">Single User — Up and Running in 5 Minutes as a</a></li> <li><a href="../../../sitemap"><b>Site Map</b></a></li> <li><a href="image-format-vs-repo-size.md">Size — Image Format vs Fossil Repo</a></li> <li><a href="customskin.md">Skins — Custom</a></li> |
| ︙ | ︙ | |||
252 253 254 255 256 257 258 | <li><a href="tickets.wiki">System — The Fossil Ticket</a></li> <li><a href="branching.wiki">Tagging — Branching, Forking, Merging, and</a></li> <li><a href="tech_overview.wiki">Technical Overview Of The Design And Implementation Of Fossil — A</a></li> <li><a href="../test/release-checklist.wiki">Testing Checklist — Pre-Release</a></li> <li><a href="th1.md">TH1 Scripting Language — The</a></li> <li><a href="backoffice.md"><b>The "Backoffice" mechanism of Fossil</b></a></li> <li><a href="blame.wiki"><b>The Annotate/Blame Algorithm Of Fossil</b></a></li> | | > > > > > | > > > | 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 | <li><a href="tickets.wiki">System — The Fossil Ticket</a></li> <li><a href="branching.wiki">Tagging — Branching, Forking, Merging, and</a></li> <li><a href="tech_overview.wiki">Technical Overview Of The Design And Implementation Of Fossil — A</a></li> <li><a href="../test/release-checklist.wiki">Testing Checklist — Pre-Release</a></li> <li><a href="th1.md">TH1 Scripting Language — The</a></li> <li><a href="backoffice.md"><b>The "Backoffice" mechanism of Fossil</b></a></li> <li><a href="blame.wiki"><b>The Annotate/Blame Algorithm Of Fossil</b></a></li> <li><a href="defcsp.md"><b>The Default Content Security Policy</b></a></li> <li><a href="fileedit-page.md"><b>The fileedit Page</b></a></li> <li><a href="makefile.wiki"><b>The Fossil Build Process</b></a></li> <li><a href="sync.wiki"><b>The Fossil Sync Protocol</b></a></li> <li><a href="tickets.wiki"><b>The Fossil Ticket System</b></a></li> <li><a href="webui.wiki"><b>The Fossil Web Interface</b></a></li> <li><a href="history.md"><b>The Purpose And History Of Fossil</b></a></li> <li><a href="th1.md"><b>The TH1 Scripting Language</b></a></li> <li><a href="customskin.md"><b>Theming: Customizing The Appearance of Web Pages</b></a></li> <li><a href="customgraph.md"><b>Theming: Customizing the Timeline Graph</b></a></li> <li><a href="theory1.wiki"><b>Thoughts On The Design Of The Fossil DVCS</b></a></li> <li><a href="custom_ticket.wiki">Ticket System — Customizing The</a></li> <li><a href="tickets.wiki">Ticket System — The Fossil</a></li> <li><a href="customgraph.md">Timeline Graph — Theming: Customizing the</a></li> <li><a href="css-tricks.md">Tips and Tricks — Fossil CSS</a></li> <li><a href="hints.wiki">Tips And Usage Hints — Fossil</a></li> <li><a href="bugtheory.wiki">Tracking In Fossil — Bug</a></li> <li><a href="css-tricks.md">Tricks — Fossil CSS Tips and</a></li> <li><a href="unvers.wiki"><b>Unversioned Files</b></a></li> <li><a href="fiveminutes.wiki"><b>Up and Running in 5 Minutes as a Single User</b></a></li> <li><a href="hints.wiki">Usage Hints — Fossil Tips And</a></li> <li><a href="javascript.md"><b>Use of JavaScript in Fossil</b></a></li> <li><a href="fiveminutes.wiki">User — Up and Running in 5 Minutes as a Single</a></li> <li><a href="caps/">User Capabilities — Administering</a></li> <li><a href="caps/ref.html"><b>User Capability Reference</b></a></li> <li><a href="caps/admin-v-setup.md">Users — Differences Between Setup and Admin</a></li> <li><a href="gitusers.md">Users With Git Experience — Hints For</a></li> <li><a href="serverext.wiki">Using CGI Scripts — Adding Extensions To A Fossil Server</a></li> <li><a href="ssl.wiki"><b>Using SSL with Fossil</b></a></li> <li><a href="env-opts.md">Variables and Global Options — Environment</a></li> <li><a href="whyusefossil.wiki">Version Control — Benefits Of</a></li> <li><a href="checkin_names.wiki">Version Names — Check-in And</a></li> <li><a href="fossil-v-git.wiki">Versus Git — Fossil</a></li> <li><a href="tls-nginx.md">via HTTPS with nginx — Proxying Fossil</a></li> |
| ︙ | ︙ |
| ︙ | ︙ | |||
29 30 31 32 33 34 35 | <blockquote><pre> fossil update trunk fossil merge private fossil commit </pre></blockquote> | < | > > > > > > > > > > > > > > | | | | | | | | 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 |
<blockquote><pre>
fossil update trunk
fossil merge private
fossil commit
</pre></blockquote>
The private branch remains private, but all of the changes associated with
the private branch are now folded into the public branch and are hence
visible to other users of the project.
A private branch created with Fossil version 1.30 or newer can also be
converted into a public branch using the <code>fossil publish</code>
command. However, there is no way to convert a private branch created with
older versions of Fossil into a public branch.
The <code>--integrate</code> option of <code>fossil merge</code> (to close
the merged branch when committing) is ignored for a private branch -- or the
check-in manifest of the resulting merge child would include a
<code>+close</code> tag referring to the leaf check-in on the private branch,
and generate a missing artifact reference on repository clones without that
private branch. It's still possible to close the leaf of the private branch
(after committing the merge child) with the <code>fossil amend --close</code>
command.
<h2>Syncing Private Branches</h2>
A private branch normally stays on the one repository where it was
originally created. But sometimes you want to share private branches
with another repository. For example, you might be building a cross-platform
application and have separate repositories on your Windows laptop,
your Linux desktop, and your iMac. You can transfer private branches
between these machines by using the --private option on the "sync",
"push", "pull", and "clone" commands. For example, if you are running
"fossil server" on your Linux box and you want to clone that repository
to your Mac, including all private branches, use:
<blockquote><pre>
fossil clone --private http://user@linux.localnetwork:8080/ mac-clone.fossil
</pre></blockquote>
You'll have to supply a username and password in order for this to work.
Fossil will not clone (or sync) private branches anonymously.
By default, there are no users that can do private branch syncing. You
will have to give a user
the "Private" capability ("x") if you want them to be able to do this.
We deny such capability for normal users by default to add a barrier to
accidental syncing of a private branch to a public server. It is highly recommended that
you leave the "x" capability turned off on all repositories used for
collaboration (repositories to which many people push and pull) and
only enable "x" for local repositories when you need to share private
branches.
Private branch sync only works if you use the --private command-line option.
Private branches are never synced via the auto-sync mechanism. Once
|
| ︙ | ︙ |
1 2 3 4 | <title>Questions And Criticisms</title> <nowiki> <h1 align="center">Questions And Criticisms</h1> | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <title>Questions And Criticisms</title> <nowiki> <h1 align="center">Questions And Criticisms</h1> <p>This page is a collection of real questions and criticisms that were raised against Fossil early in its history (circa 2008). This page is old and has not been kept up-to-date. See the </nowiki>[/finfo?name=www/qandc.wiki|change history of this page]<nowiki>.</p> <b>Fossil sounds like a lot of reinvention of the wheel. Why create your own DVCS when you could have reused mercurial?</b> <blockquote> <p>I wrote fossil because none of the other available DVCSes met my needs. If the other DVCSes do |
| ︙ | ︙ |
1 2 3 | <title>Fossil Quick Start Guide</title> <h1 align="center">Fossil Quick Start</h1> | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<title>Fossil Quick Start Guide</title>
<h1 align="center">Fossil Quick Start</h1>
<p>This is a guide to help you get started using Fossil quickly
and painlessly.</p>
<h2 id="install">Installing</h2>
<p>Fossil is a single self-contained C program. You need to
either download a
<a href="https://www.fossil-scm.org/fossil/uv/download.html">precompiled
binary</a>
or <a href="build.wiki">compile it yourself</a> from sources.
Install Fossil by putting the fossil binary
someplace on your $PATH.</p>
<a name="fslclone"></a>
<h2>General Work Flow</h2>
<p>Fossil works with repository files (a database with the project's
complete history) and with checked-out local trees (the working directory
|
| ︙ | ︙ | |||
144 145 146 147 148 149 150 |
changes if told to do so.</p>
<h2>Configuring Your Local Repository</h2>
<p>When you create a new repository, either by cloning an existing
project or create a new project of your own, you usually want to do some
local configuration. This is easily accomplished using the web-server
| | | 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
changes if told to do so.</p>
<h2>Configuring Your Local Repository</h2>
<p>When you create a new repository, either by cloning an existing
project or create a new project of your own, you usually want to do some
local configuration. This is easily accomplished using the web-server
that is built into fossil. Start the fossil web server like this:
([/help/ui | more info])</p>
<blockquote>
<b>fossil ui </b><i> repository-filename</i>
</blockquote>
<p>You can omit the <i>repository-filename</i> from the command above
|
| ︙ | ︙ | |||
280 281 282 283 284 285 286 |
<i># make sure the merge didn't break anything...</i><br>
<b>fossil [/help/commit|commit]
</blockquote>
<p>The argument to the [/help/merge|merge] command can be any of the
version identifier forms that work for [/help/update|update].
([./checkin_names.wiki|more info].)
| | | 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 |
<i># make sure the merge didn't break anything...</i><br>
<b>fossil [/help/commit|commit]
</blockquote>
<p>The argument to the [/help/merge|merge] command can be any of the
version identifier forms that work for [/help/update|update].
([./checkin_names.wiki|more info].)
The merge command has options to cherry-pick individual
changes, or to back out individual changes, if you don't want to
do a full merge.</p>
The merge command puts all changes in your working check-out.
No changes are made to the repository.
You must run [/help/commit|commit] separately
to add the merge changes into your repository to make them persistent
|
| ︙ | ︙ | |||
336 337 338 339 340 341 342 |
address only (and thus makes the web interface visible only on the
local machine) and it automatically start your web browser pointing at the
server. For cross-machine collaboration, use the <b>server</b> command,
which binds on all IP addresses and does not try to start a web browser.</p>
<p>Servers are also easily configured as:
<ul>
| | > | | | 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
address only (and thus makes the web interface visible only on the
local machine) and it automatically start your web browser pointing at the
server. For cross-machine collaboration, use the <b>server</b> command,
which binds on all IP addresses and does not try to start a web browser.</p>
<p>Servers are also easily configured as:
<ul>
<li>[./server/any/inetd.md|inetd]
<li>[./server/debian/service.md|systemd]
<li>[./server/any/cgi.md|CGI]
<li>[./server/any/scgi.md|SCGI]
</ul>
<p>The [./selfhost.wiki | self-hosting fossil repositories] use
CGI.
<a name="proxy"></a>
<h2>HTTP Proxies</h2>
|
| ︙ | ︙ | |||
388 389 390 391 392 393 394 |
is easily done on the command-line. For example, to sync with
a co-workers repository on your LAN, you might type:</p>
<blockquote>
<b>fossil sync http://192.168.1.36:8080/ --proxy off</b>
</blockquote>
| | | > > > > | | < | < | 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 |
is easily done on the command-line. For example, to sync with
a co-workers repository on your LAN, you might type:</p>
<blockquote>
<b>fossil sync http://192.168.1.36:8080/ --proxy off</b>
</blockquote>
<h2 id="links">Other Resources</h2>
<ul>
<li> <a href="./gitusers.md">Hints for users with prior Git experience</a>
<li> <a href="./whyusefossil.wiki">Benefits Of Version Control</a>
<li> <a href="./history.md">The Purpose And History of Fossil</a>
<li> <a href="./branching.wiki">Branching, Forking, Merge, and Taggings</a>
<li> <a href="./hints.wiki">Tips and Usage Hints</a>
<li> <a href="./permutedindex.html">Comprehensive documentation index</a>
</ul>
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
<!--Created by yEd 3.19-->
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
<key for="port" id="d1" yfiles.type="portgraphics"/>
<key for="port" id="d2" yfiles.type="portgeometry"/>
<key for="port" id="d3" yfiles.type="portuserdata"/>
<key attr.name="url" attr.type="string" for="node" id="d4"/>
<key attr.name="description" attr.type="string" for="node" id="d5"/>
<key for="node" id="d6" yfiles.type="nodegraphics"/>
<key for="graphml" id="d7" yfiles.type="resources"/>
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
<graph edgedefault="directed" id="G">
<data key="d0" xml:space="preserve"/>
<node id="n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="203.5" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C0<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="263.5" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C1<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n2">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="323.5" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C2<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n3">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="382.5" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C3<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n4">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="441.5" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C5<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n5">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="382.5" y="-511.8418334831767"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C4<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<edge id="e0" source="n0" target="n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e1" source="n1" target="n2">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e2" source="n2" target="n5">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e3" source="n2" target="n3">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e4" source="n3" target="n4">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e5" source="n5" target="n4">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
</graph>
<data key="d7">
<y:Resources/>
</data>
</graphml>
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill-opacity="1" color-rendering="auto" color-interpolation="auto" text-rendering="auto" stroke="black" stroke-linecap="square" width="289" stroke-miterlimit="10" shape-rendering="auto" stroke-opacity="1" fill="black" stroke-dasharray="none" font-weight="normal" stroke-width="1" height="97" font-family="'Dialog'" font-style="normal" stroke-linejoin="miter" font-size="12px" stroke-dashoffset="0" image-rendering="auto">
<!--Generated by ySVG 2.5-->
<defs id="genericDefs"/>
<g>
<defs id="defs1">
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath1">
<path d="M0 0 L289 0 L289 97 L0 97 L0 0 Z"/>
</clipPath>
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath2">
<path d="M193 -522 L482 -522 L482 -425 L193 -425 L193 -522 Z"/>
</clipPath>
</defs>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="translate(-193,522)" stroke="white">
<rect x="193" width="289" height="97" y="-522" clip-path="url(#clipPath2)" stroke="none"/>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="218.5" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="218.5" cy="-450.668"/>
<text x="210.5547" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C0</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="278.5" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="278.5" cy="-450.668"/>
<text x="270.5547" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C1</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="338.5" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="338.5" cy="-450.668"/>
<text x="330.5547" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C2</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="397.5" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="397.5" cy="-450.668"/>
<text x="389.5547" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C3</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="456.5" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="456.5" cy="-450.668"/>
<text x="448.5547" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C5</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="397.5" cy="-496.8418" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="397.5" cy="-496.8418"/>
<text x="389.5547" y="-492.3067" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C4</text>
<path fill="none" d="M233.5 -450.668 L255.5 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M263.5 -450.668 L251.5 -455.668 L254.5 -450.668 L251.5 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M293.5 -450.668 L315.5 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M323.5 -450.668 L311.5 -455.668 L314.5 -450.668 L311.5 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M353.5 -450.668 L374.5 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M382.5 -450.668 L370.5 -455.668 L373.5 -450.668 L370.5 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M412.5 -450.668 L433.5 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M441.5 -450.668 L429.5 -455.668 L432.5 -450.668 L429.5 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M350.3126 -459.9126 L379.3874 -482.6667" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M385.6874 -487.5972 L373.1558 -484.139 L378.5999 -482.0504 L379.3189 -476.264 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M409.3126 -487.5972 L438.3874 -464.843" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M444.6874 -459.9126 L438.3189 -471.2458 L437.5999 -465.4594 L432.1559 -463.3708 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
</g>
</g>
</svg>
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
<!--Created by yEd 3.19-->
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
<key for="port" id="d1" yfiles.type="portgraphics"/>
<key for="port" id="d2" yfiles.type="portgeometry"/>
<key for="port" id="d3" yfiles.type="portuserdata"/>
<key attr.name="url" attr.type="string" for="node" id="d4"/>
<key attr.name="description" attr.type="string" for="node" id="d5"/>
<key for="node" id="d6" yfiles.type="nodegraphics"/>
<key for="graphml" id="d7" yfiles.type="resources"/>
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
<graph edgedefault="directed" id="G">
<data key="d0" xml:space="preserve"/>
<node id="n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="203.5" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C0<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="263.5" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C1<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n2">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="323.5" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C2<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n3">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="382.5" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C3<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n4">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="441.5" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="22.638671875" x="3.6806640625" xml:space="preserve" y="5.93359375">C4'<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n5">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="382.5" y="-511.8418334831767"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C4<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<edge id="e0" source="n0" target="n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e1" source="n1" target="n2">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e2" source="n2" target="n5">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e3" source="n2" target="n3">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e4" source="n3" target="n4">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
</graph>
<data key="d7">
<y:Resources/>
</data>
</graphml>
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill-opacity="1" color-rendering="auto" color-interpolation="auto" text-rendering="auto" stroke="black" stroke-linecap="square" width="289" stroke-miterlimit="10" shape-rendering="auto" stroke-opacity="1" fill="black" stroke-dasharray="none" font-weight="normal" stroke-width="1" height="97" font-family="'Dialog'" font-style="normal" stroke-linejoin="miter" font-size="12px" stroke-dashoffset="0" image-rendering="auto">
<!--Generated by ySVG 2.5-->
<defs id="genericDefs"/>
<g>
<defs id="defs1">
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath1">
<path d="M0 0 L289 0 L289 97 L0 97 L0 0 Z"/>
</clipPath>
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath2">
<path d="M193 -522 L482 -522 L482 -425 L193 -425 L193 -522 Z"/>
</clipPath>
</defs>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="translate(-193,522)" stroke="white">
<rect x="193" width="289" height="97" y="-522" clip-path="url(#clipPath2)" stroke="none"/>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="218.5" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="218.5" cy="-450.668"/>
<text x="210.5547" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C0</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="278.5" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="278.5" cy="-450.668"/>
<text x="270.5547" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C1</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="338.5" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="338.5" cy="-450.668"/>
<text x="330.5547" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C2</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="397.5" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="397.5" cy="-450.668"/>
<text x="389.5547" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C3</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="456.5" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="456.5" cy="-450.668"/>
<text x="447.1807" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C4'</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="397.5" cy="-496.8418" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-193,522)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="397.5" cy="-496.8418"/>
<text x="389.5547" y="-492.3067" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C4</text>
<path fill="none" d="M233.5 -450.668 L255.5 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M263.5 -450.668 L251.5 -455.668 L254.5 -450.668 L251.5 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M293.5 -450.668 L315.5 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M323.5 -450.668 L311.5 -455.668 L314.5 -450.668 L311.5 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M353.5 -450.668 L374.5 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M382.5 -450.668 L370.5 -455.668 L373.5 -450.668 L370.5 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M412.5 -450.668 L433.5 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M441.5 -450.668 L429.5 -455.668 L432.5 -450.668 L429.5 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M350.3126 -459.9126 L379.3874 -482.6667" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M385.6874 -487.5972 L373.1558 -484.139 L378.5999 -482.0504 L379.3189 -476.264 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
</g>
</g>
</svg>
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
<!--Created by yEd 3.19-->
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
<key for="port" id="d1" yfiles.type="portgraphics"/>
<key for="port" id="d2" yfiles.type="portgeometry"/>
<key for="port" id="d3" yfiles.type="portuserdata"/>
<key attr.name="url" attr.type="string" for="node" id="d4"/>
<key attr.name="description" attr.type="string" for="node" id="d5"/>
<key for="node" id="d6" yfiles.type="nodegraphics"/>
<key for="graphml" id="d7" yfiles.type="resources"/>
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
<graph edgedefault="directed" id="G">
<data key="d0" xml:space="preserve"/>
<node id="n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="46.0" width="304.8657196180818" x="190.26470451454668" y="-473.84183348317674"/>
<y:Fill color="#C6E2FF" transparent="false"/>
<y:BorderStyle color="#7CA5CC" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="sides" modelPosition="s" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.74609375" x="136.05981293404088" xml:space="preserve" y="50.0">main</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="46.0" width="157.99516150784632" x="337.1352626247822" y="-519.8418334831767"/>
<y:Fill color="#9ACCFC" transparent="false"/>
<y:BorderStyle color="#7CA5CC" raised="false" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="sides" modelPosition="n" textColor="#000000" verticalTextPosition="bottom" visible="true" width="45.255859375" x="56.36965106642316" xml:space="preserve" y="-22.1328125">feature</y:NodeLabel>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="203.5" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C0<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n3">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="265.07206789081687" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C1<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n4">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="326.64413578163374" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C2<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n5">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="449.7882715632675" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C6<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n6">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="388.21620367245066" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C4<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n7">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="357.4301697270422" y="-511.84183348317674"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C3<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n8">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="419.00223761785907" y="-511.84183348317674"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C5<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<edge id="e0" source="n2" target="n3">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e1" source="n3" target="n4">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e2" source="n4" target="n7">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e3" source="n4" target="n6">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e4" source="n6" target="n5">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e5" source="n7" target="n8">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e6" source="n7" target="n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e7" source="n8" target="n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
</graph>
<data key="d7">
<y:Resources/>
</data>
</graphml>
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill-opacity="1" color-rendering="auto" color-interpolation="auto" text-rendering="auto" stroke="black" stroke-linecap="square" width="326" stroke-miterlimit="10" shape-rendering="auto" stroke-opacity="1" fill="black" stroke-dasharray="none" font-weight="normal" stroke-width="1" height="157" font-family="'Dialog'" font-style="normal" stroke-linejoin="miter" font-size="12px" stroke-dashoffset="0" image-rendering="auto">
<!--Generated by ySVG 2.5-->
<defs id="genericDefs"/>
<g>
<defs id="defs1">
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath1">
<path d="M0 0 L326 0 L326 157 L0 157 L0 0 Z"/>
</clipPath>
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath2">
<path d="M180 -552 L506 -552 L506 -395 L180 -395 L180 -552 Z"/>
</clipPath>
</defs>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="translate(-180,552)" stroke="white">
<rect x="180" width="326" height="157" y="-552" clip-path="url(#clipPath2)" stroke="none"/>
</g>
<g fill="rgb(198,226,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke="rgb(198,226,255)">
<rect x="190.2647" width="304.8657" height="46" y="-473.8418" clip-path="url(#clipPath2)" stroke="none"/>
</g>
<g stroke-linecap="butt" transform="matrix(1,0,0,1,-180,552)" fill="rgb(124,165,204)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" stroke="rgb(124,165,204)" stroke-miterlimit="1.45">
<rect fill="none" x="190.2647" width="304.8657" height="46" y="-473.8418" clip-path="url(#clipPath2)"/>
<text x="328.3245" y="-410.2403" clip-path="url(#clipPath2)" fill="black" font-family="sans-serif" stroke="none" xml:space="preserve">main</text>
</g>
<g fill="rgb(154,204,252)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke="rgb(154,204,252)">
<rect x="337.1353" width="157.9952" height="46" y="-519.8418" clip-path="url(#clipPath2)" stroke="none"/>
</g>
<g stroke-linecap="butt" transform="matrix(1,0,0,1,-180,552)" fill="rgb(124,165,204)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" stroke="rgb(124,165,204)" stroke-miterlimit="1.45">
<rect fill="none" x="337.1353" width="157.9952" height="46" y="-519.8418" clip-path="url(#clipPath2)"/>
<text x="395.5049" y="-528.3731" clip-path="url(#clipPath2)" fill="black" font-family="sans-serif" stroke="none" xml:space="preserve">feature</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="218.5" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="218.5" cy="-450.668"/>
<text x="210.5547" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C0</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="280.0721" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="280.0721" cy="-450.668"/>
<text x="272.1268" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C1</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="341.6441" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="341.6441" cy="-450.668"/>
<text x="333.6988" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C2</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="464.7883" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="464.7883" cy="-450.668"/>
<text x="456.843" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C6</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="403.2162" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="403.2162" cy="-450.668"/>
<text x="395.2709" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C4</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="372.4302" cy="-496.8418" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="372.4302" cy="-496.8418"/>
<text x="364.4849" y="-492.3067" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C3</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="434.0022" cy="-496.8418" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,552)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="434.0022" cy="-496.8418"/>
<text x="426.0569" y="-492.3067" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C5</text>
<path fill="none" d="M233.5 -450.668 L257.0721 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M265.0721 -450.668 L253.0721 -455.668 L256.0721 -450.668 L253.0721 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M295.0721 -450.668 L318.6441 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M326.6441 -450.668 L314.6441 -455.668 L317.6441 -450.668 L314.6441 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M356.6441 -450.668 L380.2162 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M388.2162 -450.668 L376.2162 -455.668 L379.2162 -450.668 L376.2162 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M418.2162 -450.668 L441.7883 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M449.7883 -450.668 L437.7883 -455.668 L440.7883 -450.668 L437.7883 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M349.9653 -463.1483 L359.6711 -477.7054" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M364.109 -484.3615 L353.292 -477.151 L359.1163 -476.8734 L361.6122 -471.6036 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M387.4302 -496.8418 L411.0022 -496.8418" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M419.0022 -496.8418 L407.0022 -501.8418 L410.0022 -496.8418 L407.0022 -491.8418 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
</g>
</g>
</svg>
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
<!--Created by yEd 3.19-->
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
<key for="port" id="d1" yfiles.type="portgraphics"/>
<key for="port" id="d2" yfiles.type="portgeometry"/>
<key for="port" id="d3" yfiles.type="portuserdata"/>
<key attr.name="url" attr.type="string" for="node" id="d4"/>
<key attr.name="description" attr.type="string" for="node" id="d5"/>
<key for="node" id="d6" yfiles.type="nodegraphics"/>
<key for="graphml" id="d7" yfiles.type="resources"/>
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
<graph edgedefault="directed" id="G">
<data key="d0" xml:space="preserve"/>
<node id="n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="46.0" width="402.9304840497709" x="190.26470451454665" y="-473.84183348317674"/>
<y:Fill color="#C6E2FF" transparent="false"/>
<y:BorderStyle color="#7CA5CC" raised="false" type="line" width="1.0"/>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="46.0" width="256.0599259395354" x="337.1352626247822" y="-519.8418334831767"/>
<y:Fill color="#9ACCFC" transparent="false"/>
<y:BorderStyle color="#7CA5CC" raised="false" type="line" width="1.0"/>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="203.5" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C0<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n3">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="265.07206789081687" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C1<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n4">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="326.64413578163374" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C2<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n5">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="449.7882715632675" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C6<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n6">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="388.21620367245066" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C4<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n7">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="357.4301697270422" y="-511.84183348317674"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C3<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n8">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="419.00223761785907" y="-511.84183348317674"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C5<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n9">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="480.57430550867593" y="-511.84183348317674"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="22.638671875" x="3.6806640625" xml:space="preserve" y="5.93359375">C3'<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n10">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="542.1463733994929" y="-511.84183348317674"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="22.638671875" x="3.6806640625" xml:space="preserve" y="5.93359375">C5'<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<edge id="e0" source="n2" target="n3">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e1" source="n3" target="n4">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e2" source="n4" target="n7">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e3" source="n4" target="n6">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e4" source="n6" target="n5">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e5" source="n7" target="n8">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e6" source="n7" target="n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e7" source="n8" target="n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e8" source="n5" target="n9">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e9" source="n9" target="n10">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
</graph>
<data key="d7">
<y:Resources/>
</data>
</graphml>
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill-opacity="1" color-rendering="auto" color-interpolation="auto" text-rendering="auto" stroke="black" stroke-linecap="square" width="424" stroke-miterlimit="10" shape-rendering="auto" stroke-opacity="1" fill="black" stroke-dasharray="none" font-weight="normal" stroke-width="1" height="113" font-family="'Dialog'" font-style="normal" stroke-linejoin="miter" font-size="12px" stroke-dashoffset="0" image-rendering="auto">
<!--Generated by ySVG 2.5-->
<defs id="genericDefs"/>
<g>
<defs id="defs1">
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath1">
<path d="M0 0 L424 0 L424 113 L0 113 L0 0 Z"/>
</clipPath>
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath2">
<path d="M180 -530 L604 -530 L604 -417 L180 -417 L180 -530 Z"/>
</clipPath>
</defs>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="translate(-180,530)" stroke="white">
<rect x="180" width="424" height="113" y="-530" clip-path="url(#clipPath2)" stroke="none"/>
</g>
<g fill="rgb(198,226,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="rgb(198,226,255)">
<rect x="190.2647" width="402.9305" height="46" y="-473.8418" clip-path="url(#clipPath2)" stroke="none"/>
</g>
<g stroke-linecap="butt" transform="matrix(1,0,0,1,-180,530)" fill="rgb(124,165,204)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" stroke="rgb(124,165,204)" stroke-miterlimit="1.45">
<rect fill="none" x="190.2647" width="402.9305" height="46" y="-473.8418" clip-path="url(#clipPath2)"/>
</g>
<g fill="rgb(154,204,252)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="rgb(154,204,252)">
<rect x="337.1353" width="256.0599" height="46" y="-519.8418" clip-path="url(#clipPath2)" stroke="none"/>
</g>
<g stroke-linecap="butt" transform="matrix(1,0,0,1,-180,530)" fill="rgb(124,165,204)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" stroke="rgb(124,165,204)" stroke-miterlimit="1.45">
<rect fill="none" x="337.1353" width="256.0599" height="46" y="-519.8418" clip-path="url(#clipPath2)"/>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="218.5" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="218.5" cy="-450.668"/>
<text x="210.5547" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C0</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="280.0721" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="280.0721" cy="-450.668"/>
<text x="272.1268" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C1</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="341.6441" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="341.6441" cy="-450.668"/>
<text x="333.6988" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C2</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="464.7883" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="464.7883" cy="-450.668"/>
<text x="456.843" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C6</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="403.2162" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="403.2162" cy="-450.668"/>
<text x="395.2709" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C4</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="372.4302" cy="-496.8418" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="372.4302" cy="-496.8418"/>
<text x="364.4849" y="-492.3067" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C3</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="434.0022" cy="-496.8418" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="434.0022" cy="-496.8418"/>
<text x="426.0569" y="-492.3067" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C5</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="495.5743" cy="-496.8418" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="495.5743" cy="-496.8418"/>
<text x="486.255" y="-492.3067" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C3'</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="557.1464" cy="-496.8418" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="557.1464" cy="-496.8418"/>
<text x="547.827" y="-492.3067" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C5'</text>
<path fill="none" d="M233.5 -450.668 L257.0721 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M265.0721 -450.668 L253.0721 -455.668 L256.0721 -450.668 L253.0721 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M295.0721 -450.668 L318.6441 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M326.6441 -450.668 L314.6441 -455.668 L317.6441 -450.668 L314.6441 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M356.6441 -450.668 L380.2162 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M388.2162 -450.668 L376.2162 -455.668 L379.2162 -450.668 L376.2162 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M418.2162 -450.668 L441.7883 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M449.7883 -450.668 L437.7883 -455.668 L440.7883 -450.668 L437.7883 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M349.9653 -463.1483 L359.6711 -477.7054" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M364.109 -484.3615 L353.292 -477.151 L359.1163 -476.8734 L361.6122 -471.6036 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M387.4302 -496.8418 L411.0022 -496.8418" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M419.0022 -496.8418 L407.0022 -501.8418 L410.0022 -496.8418 L407.0022 -491.8418 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M473.1094 -463.1483 L482.8152 -477.7054" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M487.2531 -484.3615 L476.4361 -477.151 L482.2605 -476.8734 L484.7563 -471.6036 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M510.5743 -496.8418 L534.1464 -496.8418" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M542.1464 -496.8418 L530.1464 -501.8418 L533.1464 -496.8418 L530.1464 -491.8418 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
</g>
</g>
</svg>
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
<!--Created by yEd 3.19-->
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
<key for="port" id="d1" yfiles.type="portgraphics"/>
<key for="port" id="d2" yfiles.type="portgeometry"/>
<key for="port" id="d3" yfiles.type="portuserdata"/>
<key attr.name="url" attr.type="string" for="node" id="d4"/>
<key attr.name="description" attr.type="string" for="node" id="d5"/>
<key for="node" id="d6" yfiles.type="nodegraphics"/>
<key for="graphml" id="d7" yfiles.type="resources"/>
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
<graph edgedefault="directed" id="G">
<data key="d0" xml:space="preserve"/>
<node id="n0">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="46.0" width="340.4059698148014" x="190.26470451454665" y="-473.84183348317674"/>
<y:Fill color="#C6E2FF" transparent="false"/>
<y:BorderStyle color="#7CA5CC" raised="false" type="line" width="1.0"/>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n1">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="46.0" width="193.53541170456577" x="337.1352626247822" y="-519.8418334831767"/>
<y:Fill color="#9ACCFC" transparent="false"/>
<y:BorderStyle color="#7CA5CC" raised="false" type="line" width="1.0"/>
<y:Shape type="rectangle"/>
</y:ShapeNode>
</data>
</node>
<node id="n2">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="203.5" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C0<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n3">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="265.07206789081687" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C1<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n4">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="326.64413578163374" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C2<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n5">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="449.7882715632675" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C6<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n6">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="388.21620367245066" y="-465.66796875"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C4<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n7">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="357.4301697270422" y="-511.84183348317674"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C3<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n8">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="419.00223761785907" y="-511.84183348317674"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C5<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<node id="n9">
<data key="d6">
<y:ShapeNode>
<y:Geometry height="30.0" width="30.0" x="480.57430550867593" y="-511.84183348317674"/>
<y:Fill color="#FFFFFF" transparent="false"/>
<y:BorderStyle color="#000000" raised="false" type="line" width="2.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.890625" x="5.0546875" xml:space="preserve" y="5.93359375">C7<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
<y:Shape type="ellipse"/>
</y:ShapeNode>
</data>
</node>
<edge id="e0" source="n2" target="n3">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e1" source="n3" target="n4">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e2" source="n4" target="n7">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e3" source="n4" target="n6">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e4" source="n6" target="n5">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e5" source="n7" target="n8">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e6" source="n7" target="n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e7" source="n8" target="n1">
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e8" source="n5" target="n9">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e9" source="n8" target="n9">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
</graph>
<data key="d7">
<y:Resources/>
</data>
</graphml>
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill-opacity="1" color-rendering="auto" color-interpolation="auto" text-rendering="auto" stroke="black" stroke-linecap="square" width="361" stroke-miterlimit="10" shape-rendering="auto" stroke-opacity="1" fill="black" stroke-dasharray="none" font-weight="normal" stroke-width="1" height="113" font-family="'Dialog'" font-style="normal" stroke-linejoin="miter" font-size="12px" stroke-dashoffset="0" image-rendering="auto">
<!--Generated by ySVG 2.5-->
<defs id="genericDefs"/>
<g>
<defs id="defs1">
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath1">
<path d="M0 0 L361 0 L361 113 L0 113 L0 0 Z"/>
</clipPath>
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath2">
<path d="M180 -530 L541 -530 L541 -417 L180 -417 L180 -530 Z"/>
</clipPath>
</defs>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="translate(-180,530)" stroke="white">
<rect x="180" width="361" height="113" y="-530" clip-path="url(#clipPath2)" stroke="none"/>
</g>
<g fill="rgb(198,226,255)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="rgb(198,226,255)">
<rect x="190.2647" width="340.406" height="46" y="-473.8418" clip-path="url(#clipPath2)" stroke="none"/>
</g>
<g stroke-linecap="butt" transform="matrix(1,0,0,1,-180,530)" fill="rgb(124,165,204)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" stroke="rgb(124,165,204)" stroke-miterlimit="1.45">
<rect fill="none" x="190.2647" width="340.406" height="46" y="-473.8418" clip-path="url(#clipPath2)"/>
</g>
<g fill="rgb(154,204,252)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="rgb(154,204,252)">
<rect x="337.1353" width="193.5354" height="46" y="-519.8418" clip-path="url(#clipPath2)" stroke="none"/>
</g>
<g stroke-linecap="butt" transform="matrix(1,0,0,1,-180,530)" fill="rgb(124,165,204)" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" stroke="rgb(124,165,204)" stroke-miterlimit="1.45">
<rect fill="none" x="337.1353" width="193.5354" height="46" y="-519.8418" clip-path="url(#clipPath2)"/>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="218.5" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="218.5" cy="-450.668"/>
<text x="210.5547" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C0</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="280.0721" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="280.0721" cy="-450.668"/>
<text x="272.1268" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C1</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="341.6441" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="341.6441" cy="-450.668"/>
<text x="333.6988" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C2</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="464.7883" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="464.7883" cy="-450.668"/>
<text x="456.843" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C6</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="403.2162" cy="-450.668" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="403.2162" cy="-450.668"/>
<text x="395.2709" y="-446.1328" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C4</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="372.4302" cy="-496.8418" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="372.4302" cy="-496.8418"/>
<text x="364.4849" y="-492.3067" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C3</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="434.0022" cy="-496.8418" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="434.0022" cy="-496.8418"/>
<text x="426.0569" y="-492.3067" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C5</text>
</g>
<g fill="white" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke="white">
<circle r="15" clip-path="url(#clipPath2)" cx="495.5743" cy="-496.8418" stroke="none"/>
</g>
<g text-rendering="geometricPrecision" stroke-miterlimit="1.45" stroke-width="2" shape-rendering="geometricPrecision" transform="matrix(1,0,0,1,-180,530)" stroke-linecap="butt">
<circle fill="none" r="15" clip-path="url(#clipPath2)" cx="495.5743" cy="-496.8418"/>
<text x="487.629" y="-492.3067" clip-path="url(#clipPath2)" font-family="sans-serif" stroke="none" stroke-width="1" xml:space="preserve">C7</text>
<path fill="none" d="M233.5 -450.668 L257.0721 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M265.0721 -450.668 L253.0721 -455.668 L256.0721 -450.668 L253.0721 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M295.0721 -450.668 L318.6441 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M326.6441 -450.668 L314.6441 -455.668 L317.6441 -450.668 L314.6441 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M356.6441 -450.668 L380.2162 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M388.2162 -450.668 L376.2162 -455.668 L379.2162 -450.668 L376.2162 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M418.2162 -450.668 L441.7883 -450.668" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M449.7883 -450.668 L437.7883 -455.668 L440.7883 -450.668 L437.7883 -445.668 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M349.9653 -463.1483 L359.6711 -477.7054" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M364.109 -484.3615 L353.292 -477.151 L359.1163 -476.8734 L361.6122 -471.6036 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M387.4302 -496.8418 L411.0022 -496.8418" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M419.0022 -496.8418 L407.0022 -501.8418 L410.0022 -496.8418 L407.0022 -491.8418 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M473.1094 -463.1483 L482.8152 -477.7054" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M487.2531 -484.3615 L476.4361 -477.151 L482.2605 -476.8734 L484.7563 -471.6036 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
<path fill="none" d="M449.0022 -496.8418 L472.5743 -496.8418" stroke-width="1" clip-path="url(#clipPath2)"/>
<path d="M480.5743 -496.8418 L468.5743 -501.8418 L471.5743 -496.8418 L468.5743 -491.8418 Z" stroke-width="1" clip-path="url(#clipPath2)" stroke="none"/>
</g>
</g>
</svg>
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Rebase Considered Harmful
Fossil deliberately omits a "rebase" command because the original
designer of Fossil (and [original author][vhist] of this article) considers rebase to be
an anti-pattern to be avoided. This article attempts to
explain that point of view.
[vhist]: /finfo?name=www/rebaseharm.md&ubg
## 1.0 Rebasing is dangerous
Most people, even strident advocates of rebase, agree that rebase can
cause problems when misused. The Git rebase documentation talks about the
[golden rule of rebasing][golden]: never rebase on a public
branch. Horror stories of misused rebase abound, and the rebase
documentation devotes considerable space toward explaining how to
recover from rebase errors and/or misuse.
## <a name="cap-loss"></a>2.0 Rebase provides no new capabilities
Sometimes sharp and dangerous tools are justified,
because they accomplish things that cannot be
done otherwise, or at least cannot be done easily.
Rebase does not fall into that category,
because it provides no new capabilities.
### <a name="orphaning"></a>2.1 A rebase is just a merge with historical references omitted
A rebase is really nothing more than a merge (or a series of merges)
that deliberately forgets one of the parents of each merge step.
To help illustrate this fact,
consider the first rebase example from the
[Git documentation][gitrebase]. The merge looks like this:

And the rebase looks like this:

As the [Git documentation][gitrebase] points out, check-ins C4\' and C5
are identical. The only difference between C4\' and C5 is that C5
records the fact that C4 is its merge parent but C4\' does not.
Thus, a rebase is just a merge that forgets where it came from.
The Git documentation acknowledges this fact (in so many words) and
justifies it by saying "rebasing makes for a cleaner history." I read
that sentence as a tacit admission that the Git history display
capabilities are weak and need active assistance from the user to
keep things manageable.
Surely a better approach is to record
the complete ancestry of every check-in but then fix the tool to show
a "clean" history in those instances where a simplified display is
desirable and edifying, but retain the option to show the real,
complete, messy history for cases where detail and accuracy are more
important.
So, another way of thinking about rebase is that it is a kind of
merge that intentionally forgets some details in order to
not overwhelm the weak history display mechanisms available in Git.
Wouldn't it be better, less error-prone, and easier on users
to enhance the history display mechanisms in Git so that rebasing
for a clean, linear history became unnecessary?
### <a name="clean-diffs"></a>2.2 Rebase does not actually provide better feature-branch diffs
Another argument, often cited, is that rebasing a feature branch
allows one to see just the changes in the feature branch without
the concurrent changes in the main line of development.
Consider a hypothetical case:

In the above, a feature branch consisting of check-ins C3 and C5 is
run concurrently with the main line in check-ins C4 and C6. Advocates
for rebase say that you should rebase the feature branch to the tip
of main in order to remove main-line development differences from
the feature branch's history:

You could choose to collapse C3\' and C5\' into a single check-in
as part of this rebase, but that's a side issue we'll deal with
[separately](#collapsing).
Because Fossil purposefully lacks rebase, the closest you can get to this same check-in
history is the following merge:

Check-ins C5\' and C7 check-ins hold identical code. The only
difference is in their history.
The argument from rebase advocates
is that with merge it is difficult to see only the changes associated
with the feature branch without the commingled mainline changes.
In other words, diff(C2,C7) shows changes from both the feature
branch and from the mainline, whereas in the rebase case
diff(C6,C5\') shows only the feature branch changes.
But that argument is comparing apples to oranges, since the two diffs
do not have the same baseline. The correct way to see only the feature
branch changes in the merge case is not diff(C2,C7) but rather diff(C6,C7).
<center><table border="1" cellpadding="5" cellspacing="0">
<tr><th>Rebase<th>Merge<th>What You See
<tr><td>diff(C2,C5\')<td>diff(C2,C7)<td>Commingled branch and mainline changes
<tr><td>diff(C6,C5\')<td>diff(C6,C7)<td>Branch changes only
</table></center>
Remember: C7 and C5\' are bit-for-bit identical, so the output of the
diff is not determined by whether you select C7 or C5\' as the target
of the diff, but rather by your choice of the diff source, C2 or C6.
So, to help with the problem of viewing changes associated with a feature
branch, perhaps what is needed is not rebase but rather better tools to
help users identify an appropriate baseline for their diffs.
## <a name="siloing"></a>3.0 Rebase encourages siloed development
The [golden rule of rebasing][golden] is that you should never do it
on public branches, so if you are using rebase as intended, that means
you are keeping private branches. Or, to put it another way, you are
doing siloed development. You are not sharing your intermediate work
with collaborators. This is not good for product quality.
[Nagappan, et. al][nagappan] studied bugs in Windows Vista and found
that best predictor of bugs is the distance on the org-chart between
the stake-holders. The bug rate is inversely related to the
amount of communication among the engineers.
Similar findings arise in other disciplines. Keeping
private branches does not prove that developers are communicating
insufficiently, but it is a key symptom that problem.
[Weinberg][weinberg] argues programming should be "egoless." That
is to say, programmers should avoid linking their code with their sense of
self, as that makes it more difficult for them to find and respond
to bugs, and hence makes them less productive. Many developers are
drawn to private branches out of sense of ego. "I want to get the
code right before I publish it." I sympathize with this sentiment,
and am frequently guilty of it myself. It is humbling to display
your stupid mistake to the whole world on an Internet that
never forgets. And yet, humble programmers generate better code.
What is the fastest path to solid code? Is it to continue staring at
your private branch to seek out every last bug, or is it to publish it
as-is, whereupon the many eyeballs will immediately see that last stupid
error in the code? Testing and development are often done by separate
groups within a larger software development organization, because
developers get too close to their own code to see every problem in it.
Given that, is it better for those many eyeballs to find your problems
while they're still isolated on a feature branch, or should that vetting
wait until you finally push a collapsed version of a private working
branch to the parent repo? Will the many eyeballs even see those errors
when they’re intermingled with code implementing some compelling new feature?
## <a name="testing"></a>4.0 Rebase commits untested check-ins to the blockchain
Rebase adds new check-ins to the blockchain without giving the operator
an opportunity to test and verify those check-ins. Just because the
underlying three-way merge had no conflict does not mean that the resulting
code actually works. Thus, rebase runs the very real risk of adding
non-functional check-ins to the permanent record.
Of course, a user can also commit untested or broken check-ins without
the help of rebase. But at least with an ordinary commit or merge
(in Fossil at least), the operator
has the *opportunity* to test and verify the merge before it is committed,
and a chance to back out or fix the change if it is broken without leaving
busted check-ins on the blockchain to complicate future bisects.
With rebase, pre-commit testing is not an option.
## <a name="timestamps"></a>5.0 Rebase causes timestamp confusion
Consider the earlier example of rebasing a feature branch:

What timestamps go on the C3\' and C5\' check-ins? If you choose
the same timestamps as the original C3 and C5, then you have the
odd situation C3' is older than its parent C6. We call that a
"timewarp" in Fossil. Timewarps can also happen due to misconfigured
system clocks, so they are not unique to rebase, but they are very
confusing and so best avoided. The other option is to provide new
unique timestamps for C3' and C5' but then you lose the information
about when those check-ins were originally created, which can make
historical analysis of changes more difficult. It might also
complicate the legal defense of prior art claims.
## <a name="lying"></a>6.0 Rebasing is lying about the project history
By discarding parentage information, rebase attempts to deceive the
reader about how the code actually came together.
The [Git rebase documentation][gitrebase] admits as much. They acknowledge
that when you view a repository as record of what actually happened,
doing a rebase is "blasphemous" and "you're _lying_ about what
actually happened", but then goes on to justify rebase as follows:
>
_"The opposing point of view is that the commit history is the **story of
how your project was made.** You wouldn't publish the first draft of a
book, and the manual for how to maintain your software deserves careful
editing. This is the camp that uses tools like rebase and filter-branch
to tell the story in the way that's best for future readers."_
This counter-argument assumes you must
change history in order to enhance readability, which is not true.
In fairness to the Git documentation authors, changing the
project history appears to be the only way to make editorial
changes in Git.
But it does not have to be that way.
Fossil demonstrates how "the story of your project"
can be enhanced without changing the actual history
by allowing users to:
1. Edit check-in comments to fix typos or enhance clarity
2. Attach supplemental notes to check-ins or whole branches
3. Cross-reference check-ins with each other, or with
wiki, tickets, forum posts, and/or embedded documentation
4. Cause mistaken or unused branches to be hidden from
routine display
5. Fix faulty check-in date/times resulting from misconfigured
system clocks
6. And so forth....
These changes are accomplished not by removing or modifying existing
repository entries, but rather by adding new supplemental records.
The original incorrect or unclear inputs are preserved and are
readily accessible. The original history is preserved.
But for routine display purposes, the more
readable edited presentation is provided.
A repository can be a true and accurate
representation of history even without getting everything perfect
on the first draft. Those are not contradictory goals, at least
not in theory.
Unfortunately, Git does not currently provide the ability to add
corrections or clarifications or supplimental notes to historical check-ins.
Hence, once again,
rebase can be seen as an attempt to work around limitations
of Git. Git could be enhanced to support editorial changes
to check-ins.
Wouldn't it be better to fix the version control tool
rather than requiring users to fabricate a fictitious project history?
## <a name="collapsing"></a>7.0 Collapsing check-ins throws away valuable information
One of the oft-cited advantages of rebasing in Git is that it lets you
collapse multiple check-ins down to a single check-in to make the
development history “clean.” The intent is that development appear as
though every feature were created in a single step: no multi-step
evolution, no back-tracking, no false starts, no mistakes. This ignores
actual developer psychology: ideas rarely spring forth from fingers to
files in faultless finished form. A wish for collapsed, finalized
check-ins is a wish for a counterfactual situation.
The common counterargument is that collapsed check-ins represent a
better world, the ideal we're striving for. What that argument overlooks
is that we must throw away valuable information to get there.
### <a name="empathy"></a>7.1 Individual check-ins support developer empathy
Ideally, future developers of our software can understand every feature
in it using only context available in the version of the code they start
work with. Prior to widespread version control, developers had no choice
but to work that way. Pre-existing codebases could only be understood
as-is or not at all. Developers in that world had an incentive to
develop software that was easy to understand retrospectively, even if
they were selfish people, because they knew they might end up being
those future developers!
Yet, sometimes we come upon a piece of code that we simply cannot
understand. If you have never asked yourself, "What was this code's
developer thinking?" you haven't been developing software for very long.
When a developer can go back to the individual check-ins leading up to
the current code, they can work out the answers to such questions using
only the level of empathy necessary to be a good developer. To
understand such code using only the finished form, you are asking future
developers to make intuitive leaps that the original developer was
unable to make. In other words, you are asking your future maintenance
developers to be smarter than the original developers! That's a
beautiful wish, but there's a sharp limit to how far you can carry it.
Eventually you hit the limits of human brilliance.
When the operation of some bit of code is not obvious, both Fossil and
Git let you run a [`blame`](/help?cmd=blame) on the code file to get
information about each line of code, and from that which check-in last
touched a given line of code. If you squash the check-ins on a branch
down to a single check-in, you throw away the information leading up to
that finished form. Fossil not only preserves the check-ins surrounding
the one that included the line of code you're trying to understand, its
[superior data model][sdm] lets you see the surrounding check-ins in
both directions; not only what lead up to it, but what came next. Git
can't do that short of crawling the block-chain backwards from the tip
of the branch to the check-in you’re looking at, an expensive operation.
We believe it is easier to understand a line of code from the 10-line
check-in it was a part of — and then to understand the surrounding
check-ins as necessary — than it is to understand a 500-line check-in
that collapses a whole branch's worth of changes down to a single
finished feature.
[sdm]: ./fossil-v-git.wiki#durable
### <a name="bisecting"></a>7.2 Bisecting works better on small check-ins
Git lets a developer write a feature in ten check-ins but collapse it
down to an eleventh check-in and then deliberately push only that final
collapsed check-in to the parent repo. Someone else may then do a bisect
that blames the merged check-in as the source of the problem they’re
chasing down; they then have to manually work out which of the 10 steps
the original developer took to create it to find the source of the
actual problem.
An equivalent push in Fossil will send all 11 check-ins to the parent
repository so that a later investigator doing the same sort of bisect
sees the complete check-in history. That bisect will point the
investigator at the single original check-in that caused the problem.
### <a name="comments"></a>7.3 Multiple check-ins require multiple check-in comments
The more comments you have from a given developer on a given body of
code, the more concise documentation you have of that developer's
thought process. To resume the bisecting example, a developer trying to
work out what the original developer was thinking with a given change
will have more success given a check-in comment that explains what the
one check-in out of ten blamed by the "bisect" command was trying to
accomplish than if they must work that out from the eleventh check-in's
comment, which only explains the "clean" version of the collapsed
feature.
### <a name="cherrypicking"></a>7.4 Cherry-picks work better with small check-ins
While working on a new feature in one branch, you may come across a bug
in the pre-existing code that you need to fix in order for work on that
feature to proceed. You could choose to switch briefly back to the
parent branch, develop the fix there, check it in, then merge the parent
back up to the feature branch in order to continue work, but that's
distracting. If the fix isn't for a critical bug, fixing it on the
parent branch can wait, so it's better to maintain your mental working
state by fixing the problem in place on the feature branch, then check
the fix in on the feature branch, resume work on the feature, and later
merge that fix down into the parent branch along with the feature.
But now what happens if another branch *also* needs that fix? Let us say
our code repository has a branch for the current stable release, a
development branch for the next major version, and feature branches off
of the development branch. If we rebase each feature branch down into
the development branch as a single check-in, pushing only the rebase
check-in up to the parent repo, only that fix's developer has the
information locally to perform the cherry-pick of the fix onto the
stable branch.
Developers working on new features often do not care about old stable
versions, yet that stable version may have an end user community that
depends on that version, who either cannot wait for the next stable
version or who wish to put off upgrading to it for some time. Such users
want backported bug fixes, yet the developers creating those fixes have
poor incentives to provide those backports. Thus the existence of
maintenance and support organizations, who end up doing such work.
(There is [a famous company][rh] that built a multi-billion dollar
enterprise on such work.)
This work is far easier when each cherry-pick transfers completely and
cleanly from one branch to another, and we increase the likelihood of
achieving that state by working from the smallest check-ins that remain
complete. If a support organization must manually disentangle a fix from
a feature check-in, they are likely to introduce new bugs on the stable
branch. Even if they manage to do their work without error, it takes
them more time to do the cherry-pick that way.
[rh]: https://en.wikipedia.org/wiki/Red_Hat
### <a name="backouts"></a>7.5 Back-outs also work better with small check-ins
The inverse of the cherry-pick merge is the back-out merge. If you push
only a collapsed version of a private working branch up to the parent
repo, those working from that parent repo cannot automatically back out
any of the individual check-ins that went into that private branch.
Others must either manually disentangle the problematic part of your
merge check-in or back out the entire feature.
## <a name="better-plan"></a>8.0 Cherry-pick merges work better than rebase
Perhaps there are some cases where a rebase-like transformation
is actually helpful, but those cases are rare, and when they do
come up, running a series of cherry-pick merges achieves the same
topology with several advantages:
1. Cherry-pick merges preserve an honest record of history.
(They do in Fossil at least. Git's file format does not have
a slot to record cherry-pick merge history, unfortunately.)
2. Cherry-picks provide an opportunity to [test each new check-in
before it is committed][tbc] to the blockchain
3. Cherry-pick merges are "safe" in the sense that they do not
cause problems for collaborators if you do them on public branches.
4. Cherry-picks keep both the original and the revised check-ins,
so both timestamps are preserved.
[tbc]: ./fossil-v-git.wiki#testing
## <a name="conclusion"></a>9.0 Summary and conclusion
Rebasing is an anti-pattern. It is dishonest. It deliberately
omits historical information. It causes problems for collaboration.
And it has no offsetting benefits.
For these reasons, rebase is intentionally and deliberately omitted
from the design of Fossil.
[golden]: https://www.atlassian.com/git/tutorials/merging-vs-rebasing#the-golden-rule-of-rebasing
[gitrebase]: https://git-scm.com/book/en/v2/Git-Branching-Rebasing
[nagappan]: https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/tr-2008-11.pdf
[weinberg]: https://books.google.com/books?id=76dIAAAAMAAJ
|
| ︙ | ︙ | |||
11 12 13 14 15 16 17 | Fossil has been hosting itself and many other projects for years 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> | | | | | | | | | | | | | | | | | 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 | Fossil has been hosting itself and many other projects for years 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> The Fossil repository is stored in an <a href="http://www.sqlite.org/">SQLite</a> database file. ([./tech_overview.wiki | Addition information] about the repository file format.) SQLite is very mature and stable and has been in wide-spread use for many years, so we are confident it will not cause repository corruption. SQLite databases do not corrupt even if a program or system crash or power failure occurs in the middle of the update. If some kind of crash does occur in the middle of a change, then all the changes are rolled back the next time that the database is accessed. A check-in operation in Fossil makes many changes to the repository database. But all these changes happen within a single transaction. If something goes wrong in the middle of the commit, even if that something is a power failure or OS crash, then the transaction is rolled back and the database is unchanged. <h2>Verification Of Delta Encodings Prior To Transaction Commit</h2> The content files that comprise the global state of a Fossil repository 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 descendants. 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_encoder_algorithm.wiki | 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. To increase our confidence that everything in the repository is recoverable, Fossil makes sure it can extract an exact replica 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 and the SHA1 or SHA3-256 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, recomputes the hash, and verifies that the recomputed hash still matches. If anything does not match up, an error message is printed and the transaction rolls back. 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. <h2>Checksum Over All Files In A Check-in</h2> Manifest artifacts that define a check-in have two fields (the R-card and Z-card) that record MD5 hashes 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 check-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 algorithm (MD5) in order to avoid common-mode failures in the hash algorithm implementation. <h2>Checksums On Structural Artifacts And Deltas</h2> Every [./fileformat.wiki | structural artifact] in a Fossil repository contains a "Z-card" bearing an MD5 checksum over the rest of the artifact. Any mismatch causes the structural artifact to be ignored. The [./delta_format.wiki | file delta format] includes a 32-bit checksum of the target file. Whenever a file is reconstructed from a delta, that checksum is verified to make sure the reconstruction was done correctly. <h2>Reliability Versus Performance</h2> Some version control systems make a big deal out of being "high performance" or the "fastest version control system". Fossil makes no such claims and has no such ambition. Indeed, profiling indicates that Fossil bears a substantial performance cost for doing all of the checksumming and verification outlined above. Fossil takes the philosophy of the <a href="http://en.wikipedia.org/wiki/The_Tortoise_and_the_Hare">tortoise</a>: reliability is more important than raw speed. The developers of Fossil see no merit in getting the wrong answer quickly. Fossil may not be the fastest versioning system, but it is <i>fast enough</i>. Fossil runs quickly enough to stay out of the developer's way. Most operations complete in milliseconds, faster than you can press the "Enter" key. |
| ︙ | ︙ | |||
54 55 56 57 58 59 60 | #!/home/hwaci/bin/fossil repository: /home/hwaci/fossil/fossil.fossil </pre></blockquote> In recent years, virtual private servers have become a more flexible and less expensive hosting option compared to shared hosting accounts. So on 2017-07-25, server (3) was moved | > | | | 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | #!/home/hwaci/bin/fossil repository: /home/hwaci/fossil/fossil.fossil </pre></blockquote> In recent years, virtual private servers have become a more flexible and less expensive hosting option compared to shared hosting accounts. So on 2017-07-25, server (3) was moved onto a $5/month "droplet" [https://en.wikipedia.org/wiki/Virtual_private_server|VPS] from [https://www.digitalocean.com|Digital Ocean] located in San Francisco. Server (3) is synchronized with the canonical server (1) by running the following command via cron: <blockquote><pre> /home/hwaci/bin/fossil sync -R /home/hwaci/fossil/fossil.fossil </pre></blockquote> Server (2) is a <a href="http://www.linode.com/">Linode 4096</a> located in Newark, NJ and set up just like the canonical server (1) with the addition of a cron job for synchronization. The same cron job also runs the [/help?cmd=git|fossil git export] command after each sync in order to [./mirrortogithub.md#ex1|mirror all changes to GitHub]. |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Serving via althttpd
[Althttpd][althttpd]
is a light-weight web server that has been used to implement the SQLite and
Fossil websites for well over a decade. Althttpd strives for simplicity,
security, ease of configuration, and low resource usage.
To set up a Fossil server as CGI on a host running the althttpd web
server, follow these steps.
<ol>
<li<p>Get the althttpd webserver running on the host. This is easily
done by following the [althttpd documentation][althttpd].
<li><p>Create a CGI script for your Fossil respository. The script will
be typically be two lines of code that look something like this:
~~~
#!/usr/bin/fossil
repository: /home/yourlogin/fossils/project.fossil
~~~
Modify the filenames to conform to your system, of course. The
CGI script accepts [other options][cgi] besides the
repository:" line. You can add in other options as you desire,
but the single "repository:" line is normally all that is needed
to get started.
<li><p>Make the CGI script executable.
<li><p>Verify that the fossil repository file and the directory that contains
the repository are both writable by whatever user the web server is
running and.
</ol>
And you are done. Visit the URL that corresponds to the CGI script
you created to start using your Fossil server.
*[Return to the top-level Fossil server article.](../)*
[althttpd]: https://sqlite.org/docsrc/doc/trunk/misc/althttpd.md
[cgi]: ../../cgi.wiki
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# Serving via CGI
A Fossil server can be run from most ordinary web servers as a CGI
program. This feature allows Fossil to seamlessly integrate into a
larger website. We use CGI for the [self-hosting Fossil repository web
site](../../selfhost.wiki).
To run Fossil as CGI, create a CGI script (here called "repo") in the
CGI directory of your web server with content like this:
#!/usr/bin/fossil
repository: /home/fossil/repo.fossil
Adjust the paths appropriately. It may be necessary to set certain
permissions on this file or to modify an `.htaccess` file or make other
server-specific changes. Consult the documentation for your particular
web server. The following permissions are *normally* required, but,
again, may be different for a particular configuration:
* The Fossil binary (`/usr/bin/fossil` in the example above)
must be readable/executable.
* *All* directories leading up to the Fossil binary must be readable
by the process which executes the CGI.
* The CGI script must be executable for the user under which it will
run, which often differs from the one running the web server.
Consult your site's documentation or the web server’s system
administrator.
* *All* directories leading to the CGI script must be readable by the
web server.
* The repository file *and* the directory containing it must be
writable by the same account which executes the Fossil binary.
(This might differ from the user the web server normally runs
under.) The directory holding the repository file(s) needs to be
writable so that SQLite can write its journal files.
* Fossil must be able to create temporary files in a
[directory that varies by host OS](../../env-opts.md#temp). When the
CGI process is operating [within a chroot](../../chroot.md),
ensure that this directory exists and is readable/writeable by the
user who executes the Fossil binary.
Once the CGI script is set up correctly, and assuming your server is
also set correctly, you should be able to access your repository with a
URL like: <b>http://mydomain.org/cgi-bin/repo</b> This is assuming you
are running a web server like Apache that uses a “`cgi-bin`” directory
for scripts like our “`repo`” example.
To serve multiple repositories from a directory using CGI, use the
"directory:" tag in the CGI script rather than "repository:". You
might also want to add a "notfound:" tag to tell where to redirect if
the particular repository requested by the URL is not found:
#!/usr/bin/fossil
directory: /home/fossil/repos
notfound: http://url-to-go-to-if-repo-not-found/
Once deployed, a URL like: <b>http://mydomain.org/cgi-bin/repo/XYZ</b>
will serve up the repository `/home/fossil/repos/XYZ.fossil` if it
exists.
Additional options available to the CGI script are [documented
separately](../../cgi.wiki).
*[Return to the top-level Fossil server article.](../)*
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Serving via inetd
A Fossil server can be launched on-demand by `inetd` by using the
[`fossil http`](/help/http) command. To do so, add a line like the
following to its configuration file, typically `/etc/inetd.conf`:
80 stream tcp nowait.1000 root /usr/bin/fossil /usr/bin/fossil http /home/fossil/repo.fossil
In this example, you are telling `inetd` that when an incoming
connection appears on TCP port 80 that it should launch the program
`/usr/bin/fossil` with the arguments shown. Obviously you will need to
modify the pathnames for your particular setup. The final argument is
either the name of the fossil repository to be served or a directory
containing multiple repositories.
If you use a non-standard TCP port on systems where the port
specification must be a symbolic name and cannot be numeric, add the
desired name and port to `/etc/services`. For example, if you want your
Fossil server running on TCP port 12345 instead of 80, you will need to
add:
fossil 12345/tcp # fossil server
and use the symbolic name “`fossil`” instead of the numeric TCP port
number (“12345” in the above example) in `inetd.conf`.
Notice that we configured `inetd` to launch Fossil as root. See the
top-level section on “[The Fossil Chroot
Jail](../../chroot.md)” for the consequences of this and
alternatives to it.
You can instead configure `inetd` to bind to a higher-numbered TCP port,
allowing Fossil to be run as a normal user. In that case, Fossil will
not put itself into a chroot jail, because it assumes you have set up
file permissions and such on the server appropriate for that user.
The `inetd` daemon must be enabled for this to work, and it must be
restarted whenever its configuration file changes.
This is a more complicated method than the [standalone HTTP server
method](./none.md), but it has the advantage of only using system
resources when an actual connection is attempted. If no one ever
connects to that port, a Fossil server will not (automatically) run. It
has the disadvantage of requiring "root" access, which may not be
available to you, either due to local IT policy or because of
restrictions at your shared Internet hosting service.
For further details, see the relevant section in your system's
documentation. The FreeBSD Handbook covers `inetd` in [this
chapter](https://www.freebsd.org/doc/en/books/handbook/network-inetd.html).
*[Return to the top-level Fossil server article.](../)*
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Standalone HTTP Server
The easiest way to set up a Fossil server is to use either the
[`server`](/help/server) or [`ui`](/help/ui) command:
* **fossil server** _REPOSITORY_
* **fossil ui** _REPOSITORY_
The _REPOSITORY_ argument is either the name of the repository file or a
directory containing many repositories named “`*.fossil`”. Both of these
commands start a Fossil server, usually on TCP port 8080, though a
higher numbered port will be used instead if 8080 is already occupied.
You can access these using URLs of the form **http://localhost:8080/**,
or if _REPOSITORY_ is a directory, URLs of the form
**http://localhost:8080/**_repo_**/** where _repo_ is the base name of
the repository file without the “`.fossil`” suffix.
There are several key differences between “`ui`” and “`server`”:
* “`ui`” always binds the server to the loopback IP address (127.0.0.1)
so that it cannot serve to other machines.
* Anyone who visits this URL is treated as the all-powerful Setup
user, which is why the first difference exists.
* “`ui`” launches a local web browser pointed at this URL.
You can omit the _REPOSITORY_ argument if you run one of the above
commands from within a Fossil checkout directory to serve that
repository:
$ fossil ui # or...
$ fossil server
You can abbreviate Fossil sub-commands as long as they are unambiguous.
“`server`” can currently be as short as “`ser`”.
You can serve a directory containing multiple `*.fossil` files like so:
$ fossil server --port 9000 --repolist /path/to/repo/dir
There is an [example script](/file/tools/fslsrv) in the Fossil
distribution that wraps `fossil server` to produce more complicated
effects. Feel free to take it, study it, and modify it to suit your
local needs.
See the [online documentation](/help/server) for more information on the
options and arguments you can give to these commands.
*[Return to the top-level Fossil server article.](../)*
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Serving via SCGI
There is an alternative to running Fossil as a [standalone HTTP
server](./none.md), which is to run it in SimpleCGI (a.k.a. SCGI) mode,
which uses the same [`fossil server`](/help/server) command as for HTTP
service. Simply add the `--scgi` command-line option and the stand-alone
server will speak the SCGI protocol rather than raw HTTP.
This can be used with a web server such as [nginx](http://nginx.org)
which does not support [Fossil’s CGI mode](./cgi.md).
A basic nginx configuration to support SCGI with Fossil looks like this:
location /code/ {
include scgi_params;
scgi_param SCRIPT_NAME "/code";
scgi_pass localhost:9000;
}
The `scgi_params` file comes with nginx, and it simply translates nginx
internal variables to `scgi_param` directives to create SCGI environment
variables for the proxied program; in this case, Fossil. Our explicit
`scgi_param` call to define `SCRIPT_NAME` adds one more variable to this
set, which is necessary for this configuration to work properly, because
our repo isn’t at the root of the URL hierarchy. Without it, when Fossil
generates absolute URLs, they’ll be missing the `/code` part at the
start, which will typically cause [404 errors][404].
The final directive simply tells nginx to proxy all calls to URLs under
`/code` down to an SCGI program on TCP port 9000. We can temporarily
set Fossil up as a server on that port like so:
$ fossil server /path/to/repo.fossil --scgi --localhost --port 9000 &
The `--scgi` option switches Fossil into SCGI mode from its default,
which is [stand-alone HTTP server mode](./none.md). All of the other
options discussed in that linked document — such as the ability to serve
a directory full of Fossil repositories rather than just a single
repository — work the same way in SCGI mode.
The `--localhost` option is simply good security: we’re using nginx to
expose Fossil service to the outside world, so there is no good reason
to allow outsiders to contact this Fossil SCGI server directly.
Giving an explicit non-default TCP port number via `--port` is a good
idea to avoid conflicts with use of Fossil’s default TCP service port,
8080, which may conflict with local uses of `fossil ui` and such.
We characterized the SCGI service start command above as “temporary”
because running Fossil in the background like that means it won’t start
back up on a reboot of the server. A simple solution to that is to add
that command to `/etc/rc.local` on systems that have it. However, you
might want to consider setting Fossil up as an OS service instead, so
that you get the benefits of the platform’s service management
framework:
* [Linux (systemd)](../debian/service.md)
* [Windows service](../windows/service.md)
* [macOS (launchd)](../macos/service.md)
* [xinetd](../any/xinetd.md)
* [inetd](../any/inetd.md)
We go into more detail on nginx service setup with Fossil in our
[Debian/Ubuntu specific guide](../debian/nginx.md). Then in [a later
article](../../tls-nginx.md) that builds upon that, we show how to add
TLS encryption to this basic SCGI + nginx setup on Debian type OSes.
Similarly, our [OpenBSD specific guide](../openbsd/httpd.md) details how
to setup a Fossil server using httpd and FastCGI on OpenBSD.
*[Return to the top-level Fossil server article.](../)*
[404]: https://en.wikipedia.org/wiki/HTTP_404
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Serving via stunnel
[`stunnel`](https://www.stunnel.org/) is a TLS/SSL proxy for programs
that themselves serve only via HTTP, such as Fossil. (Fossil *can* speak
HTTPS, but only as a client.) `stunnel` decodes the HTTPS data from the
outside world as HTTP before passing it to Fossil, and it encodes the
HTTP replies from Fossil as HTTPS before sending them to the remote host
that made the request.
You can run `stunnel` in one of two modes: socket listener — much like
in our [`inetd` doc](./inetd.md) — and as an HTTP reverse proxy. We’ll
cover both cases here, separately.
## S<a name="sa"></a>ocket Activation
The following `stunnel.conf` configuration configures it to run Fossil
in socket listener mode, launching Fossil only when an HTTPS hit comes
in, then shutting it back down as soon as the transaction is complete:
```dosini
[fossil]
accept = 443
TIMEOUTclose = 0
exec = /usr/bin/fossil
execargs = /usr/bin/fossil http /home/fossil/ubercool.fossil --https
cert = /etc/letsencrypt/live/ubercool-project.org/fullchain.pem
key = /etc/letsencrypt/live/ubercool-project.org/privkey.pem
ciphers = ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES128-SHA:DES-CBC3-SHA
options = CIPHER_SERVER_PREFERENCE
```
This configuration shows the TLS certificate generated by the [Let’s
Encrypt](https://letsencrypt.org) [Certbot](https://certbot.eff.org) in
[certonly mode](https://certbot.eff.org/lets-encrypt/debianbuster-other).
There are other ways to get TLS certificates, but this is a popular and
free option.
You will need to adjust the site names and paths in this example. Where
this file goes varies by OS type, so check the man pages on your system
to find out where it should be locally.
See the `stunnel` documentation for further details about this
configuration file.
It is important that the [`fossil http`](/help/http) command in that
configuration include the `--https` option to let Fossil know to use
“`https://`” instead of “`http://`” in generated hyperlinks.
## <a name="proxy"></a>Reverse Proxy
You can instead have Fossil running in the background in [standalone
HTTP server mode](./none.md), bound to a high random TCP port number on
localhost via the `--localhost` and `--port` flags, then configure
`stunnel` to reverse proxy public HTTPS connections down to it via HTTP.
The configuration is the same as the above except that you drop the
`exec` and `execargs` directives and add this instead:
```dosini
connect = 9000
```
That tells `stunnel` to connect to an already-running process listening
on the given TCP port number.
There are a few advantages to this mode:
1. At the cost of some server memory and a tiny bit of idle CPU time,
Fossil remains running so that hits can be served a smidge faster
than in socket listener mode, where the Fossil binary has to be
loaded and re-initialized on each HTTPS hit.
2. The socket listener mode doesn’t work on all platforms that
`stunnel` runs on, particularly [on Windows](../windows/stunnel.md).
*[Return to the top-level Fossil server article.](../)*
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Serving via xinetd
Some operating systems have replaced the old Unix `inetd` daemon with
`xinetd`, which has a similar mission but with a very different
configuration file format.
The typical configuration file is either `/etc/xinetd.conf` or a subfile
in the `/etc/xinetd.d` directory. You need a configuration something
like this for Fossil:
service http
{
port = 80
socket_type = stream
wait = no
user = root
server = /usr/bin/fossil
server_args = http /home/fossil/repos/
}
This example configures Fossil to serve multiple repositories under the
`/home/fossil/repos/` directory.
Beyond this, see the general commentary in our article on [the `inetd`
method](./inetd.md) as they also apply to service via `xinetd`.
*[Return to the top-level Fossil server article.](../)*
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Serving via nginx on Debian and Ubuntu
This document is an extension of [the platform-independent SCGI
instructions][scgii], which may suffice for your purposes if your needs
are simple.
Here, we add more detailed information on nginx itself, plus details
about running it on Debian type OSes. We focus on Debian 10 (Buster) and
Ubuntu 18.04 here, which are common Tier 1 OS offerings for [virtual
private servers][vps]. This material may not work for older OSes. It is
known in particular to not work as given for Debian 9 and older!
If you want to add TLS to this configuration, that is covered [in a
separate document][tls] which was written with the assumption that
you’ve read this first.
[scgii]: ../any/scgi.md
[tls]: ../../tls-nginx.md
[vps]: https://en.wikipedia.org/wiki/Virtual_private_server
## <a name="benefits"></a>Benefits
This scheme is considerably more complicated than the [standalone HTTP
server](../any/none.md) and [CGI options](../any/cgi.md). Even with the
benefit of this guide and pre-built binary packages, it requires quite a
bit of work to set it up. Why should you put up with this complexity?
Because it gives many benefits that are difficult or impossible to get
with the less complicated options:
* **Power** — nginx is one of the most powerful web servers in the
world. The chance that you will run into a web serving wall that you
can’t scale with nginx is very low.
To give you some idea of the sort of thing you can readily
accomplish with nginx, your author runs a single public web server
that provides transparent name-based virtual hosting for four
separate domains:
* One is entirely static, not involving any dynamic content or
Fossil integration at all.
* Another is served almost entirely by Fossil, with a few select
static content exceptions punched past Fossil, which are handled
entirely via nginx.
* The other two domains are aliases for one another — e.g.
`example.com` and `example.net` — with most of the content being
static. This pair of domains has three different Fossil repo
proxies attached to various sections of the URI hierarchy.
By using nginx, I was able to do all of the above with minimal
repetition between the site configurations.
* **Integration** — Because nginx is so popular, it integrates with
many different technologies, and many other systems integrate with it in
turn. This makes it great middleware, sitting between the outer web
world and interior site services like Fossil. It allows Fossil to
participate seamlessly as part of a larger web stack.
* **Availability** — nginx is already in most operating system binary
package repositories, so you don’t need to go out of your way to get it.
## <a name="modes"></a>Fossil Service Modes
Fossil provides four major ways to access a repository it’s serving
remotely, three of which are straightforward to use with nginx:
* **HTTP** — Fossil has a built-in HTTP server: [`fossil
server`](../any/none.md). While this method is efficient and it’s
possible to use nginx to proxy access to another HTTP server, we
don’t see any particularly good reason to make nginx reinterpret
Fossil’s own implementation of HTTP when we have a better option.
(But see [below](#http).)
* **CGI** — This method is simple but inefficient, because it launches
a separate Fossil instance on every HTTP hit.
Since Fossil is a relatively small self-contained program, and it’s
designed to start up quickly, this method can work well in a
surprisingly large number of cases.
Nevertheless, we will avoid this option in this document because
we’re already buying into a certain amount of complexity here in
order to gain power. There’s no sense in throwing away any of that
hard-won performance on CGI overhead.
* **SCGI** — The [SCGI protocol][scgip] provides the simplicity of CGI
without its performance problems.
* **SSH** — This method exists primarily to avoid the need for HTTPS,
but we *want* HTTPS. (We’ll get to that in [another document][tls].)
There is probably a way to get nginx to proxy Fossil to HTTPS via
SSH, but it would be pointlessly complicated.
SCGI it is, then.
[scgip]: https://en.wikipedia.org/wiki/Simple_Common_Gateway_Interface
## <a name="deps"></a>Installing the Dependencies
The first step is to install some non-default packages we’ll need. SSH into
your server, then say:
$ sudo apt install fossil nginx
## <a name="scgi"></a>Running Fossil in SCGI Mode
For the following nginx configuration to work, it needs to contact a
Fossil instance speaking the SCGI protocol. There are [many ways](../)
to set that up. For Debian type systems, we recommend
following [our systemd system service guide](service.md).
There are other ways to arrange for Fossil to run as a service backing
nginx, but however you do it, you need to match up the TCP port numbers between it
and those in the nginx configuration below.
## <a name="config"></a>Configuration
On Debian and Ubuntu systems the primary user-level configuration file
for nginx is `/etc/nginx/sites-enabled/default`. I recommend that this
file contain only a list of include statements, one for each site that
server hosts:
include local/example.com
include local/foo.net
Those files then each define one domain’s configuration. Here,
`/etc/nginx/local/example.com` contains the configuration for
`*.example.com` and its alias `*.example.net`; and `local/foo.net`
contains the configuration for `*.foo.net`.
The configuration for our `example.com` web site, stored in
`/etc/nginx/sites-enabled/local/example.com` is:
server {
server_name .example.com .example.net "";
include local/generic;
access_log /var/log/nginx/example.com-https-access.log;
error_log /var/log/nginx/example.com-https-error.log;
# Bypass Fossil for the static documentation generated from
# our source code by Doxygen, so it merges into the embedded
# doc URL hierarchy at Fossil’s $ROOT/doc without requiring that
# these generated files actually be stored in the repo. This
# also lets us set aggressive caching on these docs, since
# they rarely change.
location /code/doc/html {
root /var/www/example.com/code/doc/html;
location ~* \.(html|ico|css|js|gif|jpg|png)$ {
expires 7d;
add_header Vary Accept-Encoding;
access_log off;
}
}
# Redirect everything else to the Fossil instance
location /code {
include scgi_params;
scgi_param SCRIPT_NAME "/code";
scgi_pass 127.0.0.1:12345;
}
}
As you can see, this is a pure extension of [the basic nginx service
configuration for SCGI][scgii], showing off a few ideas you might want to
try on your own site, such as static asset proxying.
The `local/generic` file referenced above helps us reduce unnecessary
repetition among the multiple sites this configuration hosts:
root /var/www/$host;
listen 80;
listen [::]:80;
charset utf-8;
There are some configuration directives that nginx refuses to substitute
variables into, citing performance considerations, so there is a limit
to how much repetition you can squeeze out this way. One such example is
the `access_log` and `error_log` directives, which follow an obvious
pattern from one host to the next. Sadly, you must tolerate some
repetition across `server { }` blocks when setting up multiple domains
on a single server.
The configuration for `foo.net` is similar.
See [the nginx docs](http://nginx.org/en/docs/) for more ideas.
## <a name="http"></a>Proxying HTTP Anyway
[Above](#modes), we argued that proxying SCGI is a better option than
making nginx reinterpret Fossil’s own implementation of HTTP. If you
want Fossil to speak HTTP, just [set Fossil up as a standalone
server](../any/none.md). And if you want nginx to [provide TLS
encryption for Fossil][tls], proxying HTTP instead of SCGI provides no
benefit.
However, it is still worth showing the proper method of proxying
Fossil’s HTTP server through nginx if only to make reading nginx
documentation on other sites easier:
location /code {
rewrite ^/code(/.*) $1 break;
proxy_pass http://127.0.0.1:12345;
}
The most common thing people get wrong when hand-rolling a configuration
like this is to get the slashes wrong. Fossil is senstitive to this. For
instance, Fossil will not collapse double slashes down to a single
slash, as some other HTTP servers will.
*[Return to the top-level Fossil server article.](../)*
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Serving via systemd on Debian and Ubuntu
[`systemd`][sdhome] is the default service management framework on
Debian [since version 8][wpa] and Ubuntu since version 15.04, both
released in April 2015.
There are multiple ways to get a service to launch under `systemd`.
We’re going to show two methods which correspond approximately to two of
our generic Fossil server setup methods, the [`inetd`](../any/inetd.md)
and [standalone HTTP server](../any/none.md) methods.
[sdhome]: https://www.freedesktop.org/wiki/Software/systemd/
[wpa]: https://en.wikipedia.org/wiki/Systemd#Adoption
## User Service
A fun thing you can easily do with `systemd` that you can’t directly do
with older technologies like `inetd` and `xinetd` is to set a server up
as a “user” service.
You can’t listen on TCP port 80 with this method due to security
restrictions on TCP ports in every OS where `systemd` runs, but you can
create a listener socket on a high-numbered (≥ 1024) TCP port,
suitable for sharing a Fossil repo to a workgroup on a private LAN.
To do this, write the following in
`~/.local/share/systemd/user/fossil.service`:
```dosini
[Unit]
Description=Fossil user server
After=network.target
[Service]
WorkingDirectory=/home/fossil/museum
ExecStart=/home/fossil/bin/fossil server --port 9000 repo.fossil
Restart=always
RestartSec=3
[Install]
WantedBy=sockets.target
WantedBy=multi-user.target
```
Unlike with `inetd` and `xinetd`, we don’t need to tell `systemd` which
user and group to run this service as, because we’ve installed it
under the account we’re logged into, which `systemd` will use as the
service’s owner.
We’ve told `systemd` that we want automatic service restarts with
back-off logic, making this much more robust than the by-hand launches
of `fossil` in the platform-independent Fossil server instructions. The
service will stay up until we explicitly tell it to shut down.
A simple and useful modification to the above scheme is to add the
`--scgi` and `--localhost` flags to the `ExecStart` line to replace the
use of `fslsrv` in [the generic SCGI instructions](../any/scgi.md),
giving a much more robust configuration.
Because we’ve set this up as a user service, the commands you give to
manipulate the service vary somewhat from the sort you’re more likely to
find online:
$ systemctl --user daemon-reload
$ systemctl --user enable fossil
$ systemctl --user start fossil
$ systemctl --user status -l fossil
$ systemctl --user stop fossil
That is, we don’t need to talk to `systemd` with `sudo` privileges, but
we do need to tell it to look at the user configuration rather than the
system-level configuration.
This scheme isolates the permissions needed by the Fossil server, which
reduces the amount of damage it can do if there is ever a
remotely-triggerable security flaw found in Fossil.
On some `systemd` based OSes, user services only run while that user is
logged in interactively. This is common on systems aiming to provide
desktop environments, where this is the behavior you often want. To
allow background services to continue to run after logout, say:
$ sudo loginctl enable-linger $USER
You can paste the command just like that into your terminal, since
`$USER` will expand to your login name.
### System Service Alternative
Another workaround for the problem with user services above is to
install the service as a system service instead. This is a better path
when you are proxying Fossil with a system-level service, such as
[nginx](./nginx.md).
There are just a small set of changes required:
1. Install the unit file to one of the persistent system-level unit
file directories. Typically, these are:
/etc/systemd/system
/lib/systemd/system
2. Add `User` and `Group` directives to the `[Service]` section so
Fossil runs as a normal user, preferrably one with access only to
the Fossil repo files, rather than running as `root`.
## Socket Activation
Another useful method to serve a Fossil repo via `systemd` is via a
socket listener, which `systemd` calls “[socket activation][sa].”
It’s more complicated, but it has some nice properties. It is the
feature that allows `systemd` to replace `inetd`, `xinetd`, Upstart, and
several other competing technologies.
We first need to define the privileged socket listener by writing
`/etc/systemd/system/fossil.socket`:
```dosini
[Unit]
Description=Fossil socket
[Socket]
Accept=yes
ListenStream=80
NoDelay=true
[Install]
WantedBy=sockets.target
```
Note the change of configuration directory from the `~/.local` directory
to the system level. We need to start this socket listener at the root
level because of the low-numbered TCP port restriction we brought up
above.
This configuration says more or less the same thing as the socket part
of an `inted` entry [exemplified elsewhere in this
documentation](../any/inetd.md).
Next, create the service definition file in that same directory as
`fossil@.service`:
```dosini
[Unit]
Description=Fossil socket server
After=network.target
[Service]
WorkingDirectory=/home/fossil/museum
ExecStart=/home/fossil/bin/fossil http repo.fossil
StandardInput=socket
[Install]
WantedBy=sockets.target
WantedBy=multi-user.target
```
We’ll explain the “`@`” in the file name below.
Notice that we haven’t told `systemd` which user and group to run Fossil
under. Since this is a system-level service definition, that means it
will run as root, which then causes Fossil to [automatically drop into a
`chroot(2)` jail](../../chroot.md) rooted at the `WorkingDirectory`
we’ve configured above, shortly each `fossil http` call starts.
The `Restart*` directives we had in the user service configuration above
are unnecessary for this method, since Fossil isn’t supposed to remain
running under it. Each HTTP hit starts one Fossil instance, which
handles that single client’s request and then immediately shuts down.
Next, you need to tell `systemd` to reload its system-level
configuration files and enable the listening socket:
$ sudo systemctl daemon-reload
$ sudo systemctl enable fossil.socket
And now you can manipulate the socket listener:
$ sudo systemctl start fossil.socket
$ sudo systemctl status -l fossil.socket
$ sudo systemctl stop fossil.socket
Notice that we’re working with the *socket*, not the *service*. The fact
that we’ve given them the same base name and marked the service as an
instantiated service with the “`@`” notation allows `systemd` to
automatically start an instance of the service each time a hit comes in
on the socket that `systemd` is monitoring on Fossil’s behalf. To see
this service instantiation at work, visit a long-running Fossil page
(e.g. `/tarball`) and then give a command like this:
$ sudo systemctl --full | grep fossil
This will show information about the `fossil` socket and service
instances, which should show your `/tarball` hit handler, if it’s still
running:
fossil@20-127.0.0.1:80-127.0.0.1:38304.service
You can feed that service instance description to a `systemctl kill`
command to stop that single instance without restarting the whole
`fossil` service, for example.
In all of this, realize that we’re able to manipulate a single socket
listener or single service instance at a time, rather than reload the
whole externally-facing network configuration as with the far more
primitive `inetd` service.
[sa]: http://0pointer.de/blog/projects/socket-activation.html
*[Return to the top-level Fossil server article.](../)*
|
|
| | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < | < < < < | < < | < < < < < < > > > | > > < < < < < | < | > > < < | < < | < < < | | < | < < < | > | | > > > > > > > | | | < < < < < < | | | < < < < < < < > | | < | | > | > | < | | | | | | < < | < < < < | < < < > | | < | | < < < | < < < > > | < < < > > > | < < < < > > > | | | | | | < < | < < | > | < < < | < | | < < | | < < < < > | | < < < | > | < | < < < | < > < < | < | < < | | > > > | > > | | | | | | < < < < < > | | | | | | | | | | < | | < < < < < < < < < < < < < < > > > > | | | | > > | < < | | | | | | | | | | < | < < < < < | | < < < < < < < | < < < | < > > | < > > > | | | | | | | | | | > > < < | | | | | | < | | | > | | | | | > | < < > | > > > < < < | < < < < < < | > < < < | | < < < | < > > | < < < < < > > | < < < | < > > > | < | | | | > | < | < < < < < < < < > > > | < < | < < > | | | | | < < < < < < < > < < < > > > > | < < < < > > > > | | 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 |
<div class='fossil-doc' data-title="How To Configure A Fossil Server">
<style type="text/css">
p {
margin-left: 4em;
margin-right: 3em;
}
li p {
margin-left: 0;
}
h2 {
margin-left: 1em;
}
h3 {
margin-left: 3em;
}
ol, ul {
margin-left: 3em;
}
a#all {
font-size: 90%;
margin-left: 1em;
}
div#tutpick.show {
max-height: 99em;
transition: max-height 1000ms ease-in;
}
div#tutpick {
max-height: 0;
overflow: hidden;
}
th.fep {
background-color: #e8e8e8;
min-width: 3em;
padding: 0.4em;
white-space: nowrap;
}
th.host {
background-color: #e8e8e8;
padding: 0.4em;
text-align: right;
}
td.doc {
text-align: center;
}
</style>
<h2>No Server Required</h2>
<p>Fossil does not require a central server, but <a
href="whyuseaserver.wiki">a server can be useful</a>.</p>
<p>A Fossil server does not require much memory, CPU, or disk space
and can run comfortably on a generic $5/month virtual host
or on a small device like a Raspberry Pi, or it can co-exist
on a host running other services without getting in the way.
<p>This article is a quick-reference guide for setting up your own
Fossil server, with links to more detailed instructions specific to
particular systems, should you want extra help.</p>
<h2 id="prep">Repository Prep</h2>
<p>Prior to serving a Fossil repository to others, consider running <a
href="$ROOT/help?cmd=ui"><tt>fossil ui</tt></a> locally and taking these
minimum recommended preparation steps:</p>
<ol>
<li><p>Fossil creates only one user in a <a
href="$ROOT/help?cmd=new">new repository</a> and gives it the <a
href="../caps/admin-v-setup.md#apsu">all-powerful Setup capability</a>.
The 10-digit random password generated for that user is fairly strong
against remote attack, even without explicit password guess rate
limiting, but because that user has so much power, you may want to
give it a much stronger password under Admin → Users.</a></li>
<li><p>Run the Admin → Security-Audit tool to verify that other
security-related permissions and settings are as you want them.
Consider clicking the “Take it private” link on that page to lock down
the security on that site to a level appropriate to a private
repository, even if you will eventually want some public service. It's
better to start from a secure position and open up service
feature-by-feature as necessary than it is to start from a fully open
position and lock down features one by one to achieve a secure
stance.</p></li>
</ol>
<p>With the repository secured, it is safe to upload a copy of the
repository file to your server and proceed with server setup, below.
Further configuration steps can wait until <a href="#postsetup">after
the server is running</a>.</p>
<h2 id="methods">Activation Methods</h2>
<p>There are basically four ways to run a Fossil server:</p>
<ol>
<li><a id="cgi" href="any/cgi.md">CGI</a>
<li>Socket listener
<li><a id="standalone" href="any/none.md">Stand-alone HTTP server</a>
<li><a id="scgi" href="any/scgi.md">SCGI</a>
</ol>
<p>All of these methods can serve either a single repository or a
directory hierarchy containing mulitiple repositories.</p>
<p>You are not restricted to a single server setup. The same Fossil
repository can be served using two or more of the above techniques at
the same time. These methods use clean, well-defined, standard
interfaces (CGI, SCGI, and HTTP) which allow you to easily migrate from
one method to another in response to changes in hosting providers or
administrator preferences.</p>
<h3>CGI</h3>
<p>Most ordinary web servers can <a href="any/cgi.md">run Fossil as a
CGI script</a>. This method is known to work with Apache,
<tt>lighttpd</tt>, and <a
href="any/althttpd.md"><tt>althttpd</tt></a>. The Fossil server
administrator places a <a href="$ROOT/help?cmd=cgi">short CGI script</a> in
the web server's document hierarchy and when a client requests the URL
that corresponds to that script, Fossil runs and generates the
response.</p>
<p>CGI is a good choice for merging Fossil into an existing web site,
particularly on hosts that have CGI set up and working.
The Fossil <a href="../selfhost.wiki">self-hosting repositories</a> are
implemented with CGI underneath <tt>althttpd</tt>.</p>
<h3>Socket Listener</h3>
<p>Socket listener daemons such as
<a id="inetd" href="any/inetd.md"><tt>inetd</tt></a>, <a id="xinetd"
href="any/xinetd.md"><tt>xinetd</tt></a>, <a id="stunnel"
href="any/stunnel.md"><tt>stunnel</tt></a>, <a
href="macos/service.md"><tt>launchd</tt></a>, and <a
href="debian/service.md"><tt>systemd</tt></a>
can be configured to invoke the the
<a href="$ROOT/help?cmd=http"><tt>fossil http</tt></a> command to handle
each incoming HTTP request. The "<tt>fossil http</tt>" command reads
the HTTP request off of standard input, computes an appropriate
reply, and writes the reply on standard output. There is a separate
invocation of the "<tt>fossil http</tt>" command for each HTTP request.
The socket listener daemon takes care of relaying content to and from
the client, and (in the case of <a href="any/stunnel.md">stunnel</a>)
handling TLS decryption and encryption.
<h3>Stand-alone HTTP Server</h3>
<p>This is the <a href="any/none.md">easiest method</a>.
A stand-alone server uses the
<a href="$ROOT/help?cmd=server"><tt>fossil server</tt></a> command to run a
process that listens for incoming HTTP requests on a socket and then
dispatches a copy of itself to deal with each incoming request. You can
expose Fossil directly to the clients in this way or you can interpose a
<a href="https://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy</a>
layer between the clients and Fossil.</p>
<h3>SCGI</h3>
<p>The Fossil standalone server can also handle <a href="any/scgi.md">SCGI</a>.
When the <a href="$ROOT/help?cmd=server"><tt>fossil server</tt></a> command is
run with the extra <tt>--scgi</tt> option, it listens for incoming
SCGI requests rather than HTTP requests. This allows Fossil to
respond to requests from web servers <a href="debian/nginx.md">such as
nginx</a> that don't support CGI. SCGI is a simpler protocol to proxy
than HTTP, since the HTTP doesn't have to be re-interpreted in terms of
the proxy's existing HTTP implementation, but it's more complex to set
up because you also have to set up an SCGI-to-HTTP proxy for it. It is
worth taking on this difficulty only when you need to integrate Fossil
into an existing web site already being served by an SCGI-capable web
server.</p>
<h2 id="matrix">Activation Tutorials</h2>
<p>We've broken the configuration for each method out into a series of
sub-articles. Some of these are generic, while others depend on
particular operating systems or front-end software:</p>
<div id="tutpick" class="show"></div>
<table style="margin-left: 6em;">
<tr>
<th class="host">⇩ OS / Method ⇨</th>
<th class="fep">direct</th>
<th class="fep">inetd</th>
<th class="fep">stunnel</th>
<th class="fep">CGI</th>
<th class="fep">SCGI</th>
<th class="fep">althttpd</th>
<th class="fep">proxy</th>
<th class="fep">service</th>
</tr>
<tr>
<th class="host">Any</th>
<td class="doc"><a href="any/none.md">✅</a></td>
<td class="doc"><a href="any/inetd.md">✅</a></td>
<td class="doc"><a href="any/stunnel.md">✅</a></td>
<td class="doc"><a href="any/cgi.md">✅</a></td>
<td class="doc"><a href="any/scgi.md">✅</a></td>
<td class="doc"><a href="any/althttpd.md">✅</a></td>
<td class="doc">❌</td>
<td class="doc">❌</td>
</tr>
<tr>
<th class="host">Debian/Ubuntu</th>
<td class="doc"><a href="any/none.md">✅</a></td>
<td class="doc"><a href="any/inetd.md">✅</a></td>
<td class="doc"><a href="any/stunnel.md">✅</a></td>
<td class="doc"><a href="any/cgi.md">✅</a></td>
<td class="doc"><a href="any/scgi.md">✅</a></td>
<td class="doc"><a href="any/althttpd.md">✅</a></td>
<td class="doc"><a href="debian/nginx.md">✅</a></td>
<td class="doc"><a href="debian/service.md">✅</a></td>
</tr>
<tr>
<th class="host">macOS</th>
<td class="doc"><a href="any/none.md">✅</a></td>
<td class="doc">❌</td>
<td class="doc"><a href="any/stunnel.md">✅</a></td>
<td class="doc"><a href="any/cgi.md">✅</a></td>
<td class="doc"><a href="any/scgi.md">✅</a></td>
<td class="doc"><a href="any/althttpd.md">✅</a></td>
<td class="doc">❌</td>
<td class="doc"><a href="macos/service.md">✅</a></td>
</tr>
<tr>
<th class="host">OpenBSD</th>
<td class="doc"><a href="any/none.md">✅</a></td>
<td class="doc">❌</td>
<td class="doc"><a href="any/stunnel.md">✅</a></td>
<td class="doc"><a href="any/cgi.md">✅</a></td>
<td class="doc"><a href="any/scgi.md">✅</a></td>
<td class="doc"><a href="any/althttpd.md">✅</a></td>
<td class="doc"><a href="openbsd/httpd.md">✅</a></td>
<td class="doc">❌</td>
</tr>
<tr>
<th class="host">Windows</th>
<td class="doc"><a href="windows/none.md">✅</a></td>
<td class="doc">❌</td>
<td class="doc"><a href="windows/stunnel.md">✅</a></td>
<td class="doc"><a href="windows/cgi.md">✅</a></td>
<td class="doc">❌</td>
<td class="doc">❌</td>
<td class="doc"><a href="windows/iis.md">✅</a></td>
<td class="doc"><a href="windows/service.md">✅</a></td>
</tr>
</table>
<p>Where there is a check mark in the "<b>Any</b>" row, the method for that is
generic enough that it works across OSes that Fossil is known to work
on. The check marks below that usually just link to this generic
documentation.</p>
<p>The method in the "<b>proxy</b>" column is for the platform's default
web server configured as a <a
href="https://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy</a> for
Fossil's built-in HTTP server: <a href="debian/nginx.md">nginx</a>, <a
href="windows/iis.md">IIS</a>, Apache, etc.</p>
<p>We welcome <a href="../contribute.wiki">contributions</a> to fill gaps
(<font size="-2">❌</font>) in the table above.</p>
</noscript>
<h2 id="postsetup">Post-Activation Configuration</h2>
<p>After the server is up and running, log into it as the Setup user and
visit the Admin menu to finish configuring that repository for
service:</p>
<ol>
<li><p>Add user accounts for your other team members. Use <a
href="../caps/index.md#ucat">categories</a> to define access policies
rather than redundantly give each new user the same <a
href="../caps/index.md#ucap">individual capabilities</a>.</p></li>
<li><p>Test access to the repository from each category of non-Setup
user that you created. You may have to give your user categories some
overlooked capabilities, particularly if you followed <a
href="#prep">our earlier advice</a> to take the repository private
prior to setting up the server.</p></li>
<li><p>Modify the repository's look and feel by <a
href="../customskin.md">customizing the skin</a>.</p></li>
<li><p>If the repository includes <a
href="../embeddeddoc.wiki">embedded documentation</a>, consider
activating the search feature (Admin → Search) so that visitors can do
full-text search on your documentation.</p></li>
<li><p>Now that others can be making changes to the repository,
consider monitoring them via <a href="../alerts.md">email alerts</a>
or the <a href="$ROOT/help?cmd=/timeline.rss">timeline RSS
feed</a>.</p></li>
<li><p>Turn on the various logging features.</p></li>
</ol>
<p>Reload the Admin → Security-Audit page occasionally during this
process to double check that you have not mistakenly configured the
server in a way that might expose information that you want to keep
private.</p>
<h2 id="more">Further Details</h2>
<ul>
<li><a id="chroot" href="../chroot.md" >The Server Chroot Jail</a>
<li><a id="loadmgmt" href="../loadmgmt.md" >Managing Server Load</a>
<li><a id="bkofc" href="../backoffice.md" >The Backoffice</a>
<li><a id="tls" href="../ssl.wiki" >Securing a Repository with TLS</a>
<li><a id="ext" href="../serverext.wiki">CGI Server Extensions</a>
<li><a id="about" href="../aboutcgi.wiki" >How CGI Works In Fossil</a>
<li><a id="sync" href="../sync.wiki" >The Fossil Sync Protocol</a>
</ul>
</div>
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Serving via launchd on macOS
[`launchd`][ldhome] is the default service management framework on macOS
[since the release of Tiger in 2005][wpa]. If you want a Fossil server
to launch in the background on a Mac, it’s the way Apple wants you to do
it. `launchd` is to macOS as `systemd` is to most modern Linux desktop
systems. (Indeed, `systemd` arguably reinvented the perfectly good,
pre-existing `launchd` wheel.)
Unlike in [our `systemd` article](../debian/service.md), we’re not going
to show the per-user method here, because those so-called
[LaunchAgents][la] only start when a user is logged into the GUI, and
they stop when that user logs out. This does not strike us as proper
“server” behavior, so we’ll stick to system-level LaunchDaemons instead.
However, we will still give two different configurations, just as in the
`systemd` article: one for a standalone HTTP server, and one using
socket activation.
For more information on `launchd`, the single best resource we’ve found
is [](launchd.info). The next best is:
$ man launchd.plist
[la]: http://www.grivet-tools.com/blog/2014/launchdaemons-vs-launchagents/
[ldhome]: https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html
[wpa]: https://en.wikipedia.org/wiki/Launchd
## Standalone HTTP Server
To configure `launchd` to start Fossil as a standalone HTTP server,
write the following as `com.example.dev.FossilHTTP.plist`:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.dev.FossilHTTP</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/fossil</string>
<string>server</string>
<string>--port</string>
<string>9000</string>
<string>repo.fossil</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/you/museum</string>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>/tmp/fossil-error.log</string>
<key>StandardOutPath</key>
<string>/tmp/fossil-info.log</string>
<key>UserName</key>
<string>you</string>
<key>GroupName</key>
<string>staff</string>
<key>InitGroups</key>
<true/>
</dict>
</plist>
```
In this example, we’re assuming your development organization uses the
domain name “`dev.example.org`”, that your short macOS login name is
“`you`”, and that you store your Fossils in “`~/museum`”. Adjust these
elements of the plist file to suit your local situation.
You might be wondering about the use of `UserName`: isn’t Fossil
supposed to drop privileges and enter [a `chroot(2)`
jail](../../chroot.md) when it’s started as root like this? Why do we
need to give it a user name? Won’t Fossil use the owner of the
repository file to set that? All I can tell you is that in testing here,
if you leave the user and group configuration at the tail end of that
plist file out, Fossil will remain running as root!
Install that file and set it to start with:
$ sudo install -o root -g wheel -m 644 com.example.dev.FossilHTTP.plist \
/Library/LaunchDaemons/
$ sudo launchctl load -w /Library/LaunchDaemons/com.example.dev.FossilHTTP.plist
Because we set the `RunAtLoad` key, this will also launch the daemon.
Stop the daemon with:
$ sudo launchctl unload -w /Library/LaunchDaemons/com.example.dev.FossilHTTP.plist
## Socket Listener
Another useful method to serve a Fossil repo via `launchd` is by setting
up a socket listener:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.dev.FossilSocket</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/fossil</string>
<string>http</string>
<string>repo.fossil</string>
</array>
<key>Sockets</key>
<dict>
<key>Listeners</key>
<dict>
<key>SockServiceName</key>
<string>9001</string>
<key>SockType</key>
<string>stream</string>
<key>SockProtocol</key>
<string>TCP</string>
<key>SockFamily</key>
<string>IPv4</string>
</dict>
</dict>
<key>inetdCompatibility</key>
<dict>
<key>Wait</key>
<false/>
</dict>
<key>WorkingDirectory</key>
<string>/Users/you/museum</string>
<key>UserName</key>
<string>you</string>
<key>GroupName</key>
<string>staff</string>
<key>InitGroups</key>
<true/>
</dict>
</plist>
```
Save it as “`com.example.dev.FossilSocket.plist`” and install and load
it into `launchd` as above.
This version differs in several key ways:
1. We’re calling Fossil as `fossil http` rather than `fossil server` to
make it serve a single request and then shut down immediately.
2. We’ve told `launchd` to listen on our TCP port number instead of
passing it to `fossil`.
3. We’re running the daemon in `inetd` compatibility mode of `launchd`
with “wait” mode off, which tells it to attach the connected socket
to the `fossil` process’s stdio handles.
4. We’ve removed the `Standard*Path` keys because they interfere with
our use of stdio handles for HTTP I/O. You might therefore want to
start with the first method and then switch over to this one only
once you’ve got the daemon launching debugged, since once you tie up
stdio this way, you won’t be able to get logging information from
Fossil via that path. (Fossil does have some internal logging
mechanisms, but you can’t get at them until Fossil is launching!)
5. We’ve removed the `KeepAlive` and `RunAtLoad` keys because those
options aren’t appropriate to this type of service.
6. Because we’re running it via a socket listener instead of as a
standalone HTTP server, the Fossil service only takes system
resources when it’s actually handling an HTTP hit. If your Fossil
server is mostly idle, this method will be a bit more efficient than
the first option.
*[Return to the top-level Fossil server article.](../)*
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Serving via httpd on OpenBSD
[`httpd`][httpd] is the default web server that is included in the base
install on OpenBSD. It's minimal and lightweight but secure and capable,
and provides a clean interface for setting up a Fossil server using
FastCGI.
This article will detail the steps required to setup a TLS-enabled
``httpd`` configuration that serves multiple Fossil repositories out of
a single directory within a chroot, and allow ``ssh`` access to create
new repositories remotely.
**NOTE:** The following instructions assume an OpenBSD 6.7 installation.
[httpd]: https://www.openbsd.org/papers/httpd-asiabsdcon2015.pdf
## <a name="fslinstall"></a>Install Fossil
Use the OpenBSD package manager ``pkg_add`` to install Fossil, making
sure to select the statically linked binary.
```console
$ doas pkg_add fossil
quirks-3.325 signed on 2020-06-12T06:24:53Z
Ambiguous: choose package for fossil
0: <None>
1: fossil-2.10v0
2: fossil-2.10v0-static
Your choice: 2
fossil-2.10v0-static: ok
```
This installs Fossil into the chroot. To facilitate local use, create a
symbolic link of the fossil executable into ``/usr/local/bin``.
```console
$ doas ln -s /var/www/bin/fossil /usr/local/bin/fossil
```
As a privileged user, create the file ``/var/www/cgi-bin/scm`` with the
following contents to make the CGI script that ``httpd`` will execute in
response to ``fsl.domain.tld`` requests; all paths are relative to the
``/var/www`` chroot.
```sh
#!/bin/fossil
directory: /htdocs/fsl.domain.tld
notfound: https://domain.tld
repolist
errorlog: /logs/fossil.log
```
The ``directory`` directive instructs Fossil to serve all repositories
found in ``/var/www/htdocs/fsl.domain.tld``, while ``errorlog`` sets
logging to be saved to ``/var/www/logs/fossil.log``; create the
repository directory and log file, and make the script executable.
```console
$ doas mkdir /var/www/htdocs/fsl.domain.tld
$ doas touch /var/www/logs/fossil.log
$ doas chmod 755 /var/www/cgi-bin/scm
```
## <a name="chroot"></a>Setup chroot
Fossil needs both ``/dev/random`` and ``/dev/null``, which aren't
accessible from within the chroot, so need to be constructed; ``/var``,
however, is mounted with the ``nodev`` option. Rather than removing
this default setting, create a small memory filesystem with
[`mount_mfs(8)`][mfs] upon which ``/var/www/dev`` will be mounted so
that the ``random`` and ``null`` device files can be created.
```console
$ doas mkdir /var/www/dev
$ doas mount_mfs -s 1M /dev/sd0b /var/www/dev
$ doas cd /var/www/dev
$ doas /dev/MAKEDEV urandom
$ doas mknod -m 666 null c 2 2
$ ls -l
total 0
crw-rw-rw- 1 root daemon 2, 2 Jun 20 08:56 null
lrwxr-xr-x 1 root daemon 7 Jun 18 06:30 random@ -> urandom
crw-r--r-- 1 root wheel 45, 0 Jun 18 06:30 urandom
```
[mfs]: https://man.openbsd.org/mount_mfs.8
To make the mountable memory filesystem permanent, open ``/etc/fstab``
as a privileged user and add the following line to automate creation of
the filesystem at startup:
```console
swap /var/www/dev mfs rw,-s=1048576 0 0
```
The same user that executes the fossil binary must have writable access
to the repository directory that resides within the chroot; on OpenBSD
this is ``www``. In addition, grant repository directory ownership to
the user who will push to, pull from, and create repositories.
```console
$ doas chown -R user:www /var/www/htdocs/fsl.domain.tld
```
## <a name="httpdconfig"></a>Configure httpd
On OpenBSD, [httpd.conf(5)][httpd] is the configuration file for
``httpd``. To setup the server to serve all Fossil repositores within
the directory specified in the CGI script, and automatically redirect
standard HTTP requests to HTTPS—apart from [Let's Encrypt][LE]
challenges issued in response to [acme-client(1)][acme] certificate
requests—create ``/etc/httpd.conf`` as a privileged user with the
following contents.
[LE]: https://letsencrypt.org
[acme]: https://man.openbsd.org/acme-client.1
[httpd.conf(5)]: https://man.openbsd.org/httpd.conf.5
```apache
server "fsl.domain.tld" {
listen on * port http
root "/htdocs/fsl.domain.tld"
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
location * {
block return 301 "https://$HTTP_HOST$REQUEST_URI"
}
location "/*" {
fastcgi { param SCRIPT_FILENAME "/cgi-bin/scm" }
}
}
server "fsl.domain.tld" {
listen on * tls port https
root "/htdocs/fsl.domain.tld"
tls {
certificate "/etc/ssl/domain.tld.fullchain.pem"
key "/etc/ssl/private/domain.tld.key"
}
hsts {
max-age 15768000
preload
subdomains
}
connection max request body 104857600
directory index "index.cgi"
location "/*" {
fastcgi { param SCRIPT_FILENAME "/cgi-bin/scm" }
}
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
}
```
**NOTE:** If not already in possession of a HTTPS certificate, comment
out the ``https`` server block and proceed to securing a free
[Let's Encrypt Certificate](#letsencrypt); otherwise skip to
[Start httpd](#starthttpd).
## <a name="letsencrypt"></a>Let's Encrypt Certificate
In order for ``httpd`` to serve HTTPS, secure a free certificate from
Let's Encrypt using ``acme-client``. Before issuing the
request, however, ensure you have a zone record for the subdomain with
your registrar or nameserver. Then open ``/etc/acme-client.conf`` as a
privileged user to configure the request.
```dosini
authority letsencrypt {
api url "https://acme-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-privkey.pem"
}
authority letsencrypt-staging {
api url "https://acme-staging.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-staging-privkey.pem"
}
domain domain.tld {
alternative names { www.domain.tld fsl.domain.tld }
domain key "/etc/ssl/private/domain.tld.key"
domain certificate "/etc/ssl/domain.tld.crt"
domain full chain certificate "/etc/ssl/domain.tld.fullchain.pem"
sign with letsencrypt
}
```
Issue the certificate request.
```console
$ doas acme-client -vv domain.tld
acme-client: /etc/acme/letsencrypt-privkey.pem: account key exists (not creating)
acme-client: /etc/acme/letsencrypt-privkey.pem: loaded RSA account key
acme-client: /etc/ssl/private/domain.tld.key: generated RSA domain key
acme-client: https://acme-v01.api.letsencrypt.org/directory: directories
acme-client: acme-v01.api.letsencrypt.org: DNS: 172.65.32.248
...
N(Q????Z???j?j?>W#????b???? H????eb??T??*? DNosz(???n{L}???D???4[?B] (1174 bytes)
acme-client: /etc/ssl/domain.tld.crt: created
acme-client: /etc/ssl/domain.tld.fullchain.pem: created
```
A successful result will output the public certificate, full chain of
trust, and private key into the ``/etc/ssl`` directory as specified in
``acme-client.conf``.
```console
$ doas ls -lR /etc/ssl
-r--r--r-- 1 root wheel 2.3K Mar 2 01:31:03 2018 domain.tld.crt
-r--r--r-- 1 root wheel 3.9K Mar 2 01:31:03 2018 domain.tld.fullchain.pem
/etc/ssl/private:
-r-------- 1 root wheel 3.2K Mar 2 01:31:03 2018 domain.tld.key
```
Make sure to reopen ``/etc/httpd.conf`` to uncomment the second server
block responsible for serving HTTPS requests before proceeding.
## <a name="starthttpd"></a>Start httpd
With ``httpd`` configured to serve Fossil repositories out of
``/var/www/htdocs/fsl.domain.tld``, and the certificates and key in
place, enable and start ``slowcgi``—OpenBSD's FastCGI wrapper server
that will execute the above Fossil CGI script—before checking the syntax
of the ``httpd.conf`` configuration file is correct, and starting the
server.
```console
$ doas rcctl enable slowcgi
$ doas rcctl start slowcgi
slowcgi(ok)
$ doas httpd -vnf /etc/httpd.conf
configuration OK
$ doas rcctl start httpd
httpd(ok)
```
## <a name="clientconfig"></a>Configure Client
To facilitate creating new repositories and pushing them to the server,
add the following function to your ``~/.cshrc`` or ``~/.zprofile`` or
the config file for whichever shell you are using on your development
box.
```sh
finit() {
fossil init $1.fossil && \
chmod 664 $1.fossil && \
fossil open $1.fossil && \
fossil user password $USER $PASSWD && \
fossil remote-url https://$USER:$PASSWD@fsl.domain.tld/$1 && \
rsync --perms $1.fossil $USER@fsl.domain.tld:/var/www/htdocs/fsl.domain.tld/ >/dev/null && \
chmod 644 $1.fossil && \
fossil ui
}
```
This enables a new repository to be made with ``finit repo``, which will
create the fossil repository file ``repo.fossil`` in the current working
directory; by default, the repository user is set to the environment
variable ``$USER``. It then opens the repository and sets the user
password to the ``$PASSWD`` environment variable (which you can either
set with ``export PASSWD 'password'`` on the command line or add to a
*secured* shell environment file), and the ``remote-url`` to
https://fsl.domain.tld/repo with the credentials of ``$USER`` who is
authenticated with ``$PASSWD``. Finally, it ``rsync``'s the file to the
server before opening the local repository in your browser where you
can adjust settings such as anonymous user access, and set pertinent
repository details. Thereafter, you can add files with ``fossil add``,
and commit with ``fossil ci -m 'commit message'`` where Fossil, by
default, will push to the ``remote-url``. It's suggested you read
the [Fossil documentation][documentation]; with a sane and consistent
development model, the system is much more efficient and cohesive than
``git``—so the learning curve is not steep at all.
[documentation]: https://fossil-scm.org/home/doc/trunk/www/permutedindex.html
*[Return to the top-level Fossil server article.](../)*
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
<title>Benefits Of A Fossil Server</title>
<h2>No Server Required</h2>
Fossil does not require a central server.
Data sharing and synchronization can be entirely peer-to-peer.
Fossil uses
[https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type|conflict-free replicated data types]
to ensure that (in the limit) all participating peers see the same content.
<h2>But, A Server Can Be Useful</h2>
Fossil does not require a server, but a server can be very useful.
Here are a few reasons to set up a Fossil server for your project:
1. <b>A server works as a complete project website.</b><p>
Fossil does more than just version control. It also supports
[../tickets.wiki|trouble-tickets],
[../wikitheory.wiki|wiki], and a [../forum.wiki|forum].
The [../embeddeddoc.wiki|embedded documentation]
feature provides a great mechanism for providing project documentation.
The [../unvers.wiki|unversioned files] feature is a convenient way
to host builds and downloads on the project website.
2. <b>A server gives developers a common point of rendezvous for
syncing their work.</b><p>
It is possible for developers to synchronize peer-to-peer but
that requires the developers coordinate the sync, which in turn
requires that the developers both want to sync at the same moment.
A server aleviates this time dependency by allowing each developer
to sync whenever it is convenient (for example, automatically syncing
after each commit and before each update). Developers all stay
in sync with each other, without having to interrupt each other
constantly to set up a peer-to-peer sync.
3. <b>A server provides project leaders with up-to-date status.</b><p>
Project coordinators and BDFLs can click on a link or two at the
central Fossil server for a project, and quickly tell what is
going on. They can do this from anywhere, even from their phones,
without needing to actually sync to the device they are using.
4. <b>A server provides automatic off-site backups.</b><p>
A Fossil server is an automatic remote backup for all the work
going into a project. You can even set up multiple servers, at
multiple sites, with automatic synchronization between them, for
added redundancy. Such a set up means that no work is lost due
to a single machine failure.
|
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Serving via IIS + CGI
## This Is Not the Method You Are Looking For
Setting up CGI service under IIS is surprisingly complicated compared to
running Fossil as a CGI under most other operating systems. We recommend
that you use the simpler [reverse proxying method](./iis.md) instead
unless there is some compelling reason why that method cannot work for
you, such as its dependence on non-stock IIS extensions. (Keep in mind
that both extensions it requires are by Microsoft, not third parties!)
Once you’ve got this scheme working, it gives the same benefits as those
listed at the top of the linked-to document.
There is a small benefit you get from using CGI over reverse proxying on
other OSes, which is that the Fossil program only runs briefly in order
to serve each HTTP hit. Once the request is done, that Fossil instance
shuts back down, releasing all of its resources. You don’t need to keep
a background Fossil HTTP server running full-time to provide CGI-based
Fossil service.
You lose a lot of that benefit on Windows:
1. It only matters to start with on servers that are highly RAM
constrained. (Roughly ≤ 128 MiB.) Our configuration steps below
assume you’re using the Windows and IIS GUIs, which have RAM
requirements well in excess of this, making Fossil’s resource
requirements a drop in the bucket next to them. On the [Azure
B1s][b1s] virtual machine I used to prepare these instructions, the
Windows Server Manager GUI kept filling the VM’s 1 GiB of RAM
during feature installation and crashing. I had to upgrade the VM’s
RAM to 2 GiB just to get useful work done!
2. Process creation on Windows is [much more expensive][cp] than on the
other OSes Fossil runs on, so the benefits of firing up a Fossil
executable to process each HTTP request are partially swamped by the
overhead of doing so.
Therefore, unless you’re willing to replace all of the GUI configuration
steps below with command line equivalents, or shut the GUI down entirely
after configuring IIS, CGI is a much less compelling option on Windows.
**WARNING:** The following tutorial appears to fail with the current
(2019-08-17) version of Fossil, [apparently][fbug] due to an inability
of Fossil to detect that it’s being run in CGI mode.
[b1s]: https://azure.microsoft.com/en-us/blog/introducing-b-series-our-new-burstable-vm-size/
[cp]: https://stackoverflow.com/a/48244/142454
[fbug]: https://fossil-scm.org/forum/forumpost/de18dc32c0
## Install IIS with CGI Support
The steps for this are identical to those for the [reverse proxying IIS
setup](./iis.md#install) except that you need to enable CGI in the last
step, since it isn’t installed by default. For Windows Server, the path
is:

The path is similar on the consumer-focused versions of Windows, once
you get to that last step.
## Setup
1. Install the Fossil executable to `c:\inetpub\wwwroot\bin` on the web
server. We can’t use an executable you might already have because IIS
runs under a separate user account, so we need to give that
executable special permissions, and that’s easiest to do under the
IIS tree:

2. In IIS Manager (a.k.a. `INETMGR`) drill down into the Sites folder
in the left-side pane and right-click your web site’s
configuration. (e.g. “Default Web Site”)
3. On that menu say “Add Virtual Directory.” Give it the alias “`cgi`”
and point it at a suitable directory, such as
“`c:\inetpub\wwwroot\cgi`”.
4. Double-click the “Handler Mappings” icon, then in the right-side
pane, click “Add Script Map...” Apply the following settings:

The Executable path must point to the path we set up in step 1, not
to some other `fossil.exe` you may have elsewhere on your system.
You will need to change the default “`*.dll`” filter in the Open
dialog to “`*.exe`” in order to see it when browsing via the “`...`”
button.
5. Create a file called `repo.fslcgi` within the CGI directory you
chose in step 3, with a single line like this:
repository: c:\Users\SOMEONE\museum\repo.fossil
Give the actual path to the repository, of course.
6. Up at the top level of IIS Manager, double-click the “ISAPI and CGI
Restrictions” icon, then click “Add...” in the right-side pane.
Give the script you just created permission to execute:

7. In the right-side pane, click “Restart” to apply this configuration,
then test it by visiting the newly-available URL in a browser:
http://localhost/cgi/repo.fslcgi
For more complicated setups such as “directory” mode, see [the generic
CGI instructions](../any/cgi.md).
*[Return to the top-level Fossil server article.](../)*
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Serving via IIS
## Why Bother?
The first part of the scheme below sets Fossil up as an HTTP server, so
you might be wondering why you wouldn’t just modify that to make it
listen on all network interfaces on TCP port 80, so you can avoid the
need for IIS entirely. For simple use cases, you can indeed do without
IIS, but there are several use cases where adding it is helpful:
1. Proxying Fossil with IIS lets you [add TLS encryption][tls], which
[Fossil does not currently speak](../../ssl.wiki) in its server role.
2. The URL rewriting we do below allows Fossil to be part of a larger
site already being served with IIS.
3. You can have a mixed-mode site, with Fossil acting as a powerful
dynamic content management service and IIS as a fast static content
server. The pure-Fossil alternative requires that you check all of
your static content into Fossil as versioned or unversioned
artifacts.
This article shows how you can get any combination of those benefits by
using IIS as a reverse proxy for `fossil server`.
There are other ways to use IIS to serve Fossil, such as [via
CGI](./cgi.md).
## Background Fossil Service Setup
You will need to have the Fossil HTTP server running in the background,
serving some local repository, bound to localhost on a fixed
high-numbered TCP port. For the purposes of testing, simply start it by
hand in your command shell of choice:
fossil serve --port 9000 --localhost repo.fossil
That command assumes you’ve got `fossil.exe` in your `%PATH%` and you’re
in a directory holding `repo.fossil`. See [the platform-independent
instructions](../any/none.md) for further details.
For a more robust setup, we recommend that you [install Fossil as a
Windows service](./service.md), which will allow Fossil to start at
system boot, before anyone has logged in interactively.
## <a name="install"></a>Install IIS
IIS might not be installed in your system yet, so follow the path
appropriate to your host OS. We’ve tested only the latest Microsoft
OSes as of the time of this writing, but the basic process should be
similar on older OSes.
### Windows Server 2019
1. Start “Server Manager”
2. Tell it you want to “Add roles and features”
3. Select “Role-based or feature-based installation”
4. Select your local server
5. In the Server Roles section, enable “Web Server (IIS)”
### Windows
1. Open Control Panel
2. Go to “Programs”
3. Select “Turn Windows features on or off” in the left-side pane
4. In the “Windows Features” dialog, enable “Internet Information
Services”
The default set of IIS features there will suffice for this tutorial,
but you might want to enable additional features.
## Setting up the Proxy
The stock IIS setup doesn’t have reverse proxying features, but they’re
easily added through extensions. You will need to install the
[Application Request Routing][arr] and [URL Rewrite][ure] extensions. In
my testing here, URL Rewrite showed up immediately after installing it,
but I had to reboot the server to get ARR to show up. (Yay Windows.)
You can install these things through the direct links above, or you can
do it via the Web Platform Installer feature of IIS Manager (a.k.a.
`INETMGR`).
Set these extensions up in IIS Manager like so:
1. Double-click the “Application Request Routing Cache” icon.
2. Right-click in the window that results, and select “Server Proxy
Settings...”
3. Check the “Enable Proxy” box in the dialog. Click the “Apply” text
in the right-side pane.
4. Return to the top server-level configuration area of IIS Manager and
double-click the “URL Rewrite” icon. Alternately, you might find
“URL Rewrite” in the right-side pane from within the ARR settings.
5. Right click in the window that results, and click “Add Rule(s)...”
Tell it you want a “Blank rule” under “Inbound rules”.
6. In the dialog that results, create a new rule called “Fossil repo
proxy.” Set the “Pattern” to “`^(.*)$`” and “Rewrite URL” set to
“`http://localhost:9000/{R:1}`”. That tells it to take everything in
the path part of the URL and send it down to localhost:9000, where
`fossil server` is listening.
7. Click “Apply” in the right-side pane, then get back to the top level
configuration for the server, and click “Restart” in that same pane.
At this point, if you go to `http://localhost/` in your browser, you
should see your Fossil repository’s web interface instead of the default
IIS web site, as before you did all of the above.
This is a very simple configuration. You can do more complicated and
interesting things with this, such as redirecting only `/code` URLs to
Fossil by setting the Pattern in step 6 to “`^/code(.*)$`”. (You would
also need to pass `--baseurl http://example.com/code` in the `fossil
server` command to make this work properly.) IIS would then directly
serve all other URLs. You could also intermix ASP.NET applications in
the URL scheme in this way.
See the documentation on [URL Rewrite rules][urr] for more ideas.
*[Return to the top-level Fossil server article.](../)*
[arr]: https://www.iis.net/downloads/microsoft/application-request-routing
[tls]: https://docs.microsoft.com/en-us/iis/manage/configuring-security/understanding-iis-url-authorization
[ure]: https://www.iis.net/downloads/microsoft/url-rewrite
[urr]: https://docs.microsoft.com/en-us/iis/extensions/url-rewrite-module/creating-rewrite-rules-for-the-url-rewrite-module
|
> > > > > > > > | 1 2 3 4 5 6 7 8 | # Using Windows as a Fossil Server - [Fossil server command](./none.md) - [Fossil as CGI (IIS)](./iis.md) - [Fossil as a Service](./service.md) - [Using stunnel with Fossil on Windows](./stunnel.md) *[Return to the top-level Fossil server article.](../)* |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | # Serving as a Standalone Server on Windows On Windows, this method works more or less identically to how it’s documented in [the generic instructions](../any/none.md). ...but only while `fossil.exe` is actually running, which is the source of much trouble on Windows. This problem has two halves: ## No App Startup Without Desktop The easy methods for starting a program in Windows at system start all require an interactive desktop. There is no *easy* way to start an arbitrary program on Windows at boot before anyone has logged in. In Unix terms, Windows has no simple equivalent to [the `/etc/rc.local` file][rcl]. You can partially get around the first problem by setting your `fossil server` call up as one of the user’s interactive startup items. Windows 10 has its own [idiosyncratic way of doing this][si10], and in older systems you have [several alternatives to this][si7]. Regardless of the actual mechanism, these will cause the Fossil standalone HTTP server to start on an *interactive desktop login* only. While you’re sitting at the Windows login screen, the Fossil server is *down*. [rcl]: http://nixdoc.net/man-pages/FreeBSD/man8/rc.local.8.html [si10]: https://www.tenforums.com/tutorials/2944-add-delete-enable-disable-startup-items-windows-10-a.html [si7]: https://www.wikihow.com/Change-Startup-Programs-in-Windows-7 ## No Simple Background Mode Windows also lacks a direct equivalent of the Bourne shell’s “`&`” control operator to run a program in the background, which you can give in Unix’s `rc.local` file, which is just a normal Bourne shell script. By “background,” I mean “not attached to any interactive user’s login session.” When the `rc.local` script exits in Unix, any program it backgrounded *stays running*. There is no simple and direct equivalent to this mechanism in Windows. If you set `fossil server` to run on interactive login, as above, it will shut right back down again when that user logs back out. With Windows 10, it’s especially problematic because you can no longer make the OS put off updates arbitrarily: your Fossil server will go down every time Windows Update decides it needs to reboot your computer, and then Fossil service will *stay* down until someone logs back into that machine interactively. ## Better Solutions Because of these problems, we only recommend setting `fossil server` up on Windows this way when you’re a solo developer or you work in a small office where everyone arrives more or less at the same time each day, and everyone goes home about the same time each day, so that one user can keep the Fossil server up through the working day. If your needs go at all beyond this, you should expect proper “server” behavior, which you can get on Windows by [registering Fossil as a Windows service](./service.md), which solves the interactive startup and shutdown problems above, at a bit of complexity over the Startup Items method. You may also want to consider putting that service behind [an IIS front-end proxy](./iis.md) to add additional web serving features. *[Return to the top-level Fossil server article.](../)* |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | # Fossil as a Windows Service If you need Fossil to start automatically on Windows, it is suggested to install Fossil as a Windows Service. ## Assumptions 1. You have Administrative access to a Windows 2012r2 or above server. 2. You have PowerShell 5.1 or above installed. ## Place Fossil on Server However you obtained your copy of Fossil, it is recommended that you follow Windows conventions and place it within `\Program Files\FossilSCM`. Since Fossil 2.10 is a 64bit binary, this is the proper location for the executable. This way Fossil is at an expected location and you will have minimal issues with Windows interfering in your ability to run Fossil as a service. You will need Administrative rights to place fossil at the recommended location. If you will only be running Fossil as a service, you do not need to add this location to the path, though you may do so if you wish. ## Installing Fossil as a Service Luckily the hard work to use Fossil as a Windows Service has been done by the Fossil team. We simply have to install it with the proper command line options. Fossil on Windows has a command `fossil winsrv` to allow installing Fossil as a service on Windows. This command is only documented on the windows executable of Fossil. You must also run the command as administrator for it to be successful. ### Fossil winsrv Example The simplest form of the command is: ``` fossil winsrv create --repository D:/Path/to/Repo.fossil ``` This will create a windows service named 'Fossil-DSCM' running under the local system account and accessible on port 8080 by default. `fossil winsrv` can also start, stop, and delete the service. For all available options, please execute `fossil help winsrv` on a windows install of Fossil. If you wish to server a directory of repositories, the `fossil winsrv` command requires a slightly different set of options vs. `fossil server`: ``` fossil winsrv create --repository D:/Path/to/Repos --repolist ``` <a name='PowerShell'></a> ### Advanced service installation using PowerShell As great as `fossil winsrv` is, it does not have one to one reflection of all of the `fossil server` [options](/help?cmd=server). When you need to use some of the more advanced options, such as `--https`, `--skin`, or `--extroot`, you will need to use PowerShell to configure and install the Windows service. PowerShell provides the [New-Service](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/new-service?view=powershell-5.1) command, which we can use to install and configure Fossil as a service. The below should all be entered as a single line in an Administrative PowerShell console. ```PowerShell New-Service -Name fossil -DisplayName fossil -BinaryPathName '"C:\Program Files\FossilSCM\fossil.exe" server --port 8080 --repolist "D:/Path/to/Repos"' -StartupType Automatic ``` Please note the use of forward slashes in the repolist path passed to Fossil. Windows will accept either back slashes or forward slashes in path names, but Fossil has a preference for forward slashes. The use of `--repolist` will make this a multiple repository server. If you want to serve only a single repository, then leave off the `--repolist` parameter and provide the full path to the proper repository file. Other options are listed in the [fossil server](/help?cmd=server) documentation. The service will be installed by default to use the Local Service account. Since Fossil only needs access to local files, this is fine and causes no issues. The service will not be running once installed. You will need to start it to proceed (the `-StartupType Automatic` parameter to `New-Service` will result in the service auto-starting on boot). This can be done by entering ```PowerShell Start-Service -Name fossil ``` in the PowerShell console. Congratulations, you now have a base http accessible Fossil server running on Windows. *[Return to the top-level Fossil server article.](../)* |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
# Using stunnel with Fossil on Windows
While there are many ways to configure Fossil as a server using various web
servers (Apache, IIS, nginx, etc.), this document will focus on setting up a
minimal Fossil server using only Fossil's native [server
capabilities](../any/none.md) and [stunnel](https://www.stunnel.org/)
to provide a TLS proxy. It is recommended for public repositories to go to the
extra step of configuring stunnel to provide a proper HTTPS setup.
## Assumptions
1. You have Administrative access to a Windows 2012r2 or above server.
2. You have PowerShell 5.1 or above installed.
3. You have acquired a certificate either from a Public CA or an Internal CA.
These instructions were tested with Fossil 2.10 and stunnel 5.55. Other
versions may not function in a similar manner. There is a bug in Fossil 2.9 and
earlier that prevents these versions of Fossil from properly constructing https
URLs when used with stunnel as a proxy. Please make sure you are using Fossil
2.10 or later on Windows.
## Configure Fossil Service for https
Due to the need for the `--https` option for successfully using Fossil with
stunnel, we will use [Advanced service installation using PowerShell](./service.md#PowerShell).
We will need to change the command to install the Fossil Service to configure
it properly for use with stunnel as an https proxy. Run the following:
```PowerShell
New-Service -Name fossil-secure -DisplayName fossil-secure -BinaryPathName '"C:\Program Files\FossilSCM\fossil.exe" server --localhost --port 9000 --https --repolist "D:/Path/to/Repos"' -StartupType Automatic
```
The use of `--localhost` means Fossil will only listen for traffic on the local
host on the designated port - 9000 in this case - and will not respond to
network traffic. Using `--https` will tell Fossil to generate HTTPS URLs rather
than HTTP ones.
`New-Service` does not automatically start a service on install, so you will
need to enter the following to avoid rebooting the server:
```PowerShell
Start-Service -Name fossil-secure
```
## Install stunnel 5.55
Download stunnel from the [downloads](https://www.stunnel.org/downloads.html)
page. Select the latest stunnel windows package (at the time of writing this is
`stunnel-5.55-win64-installer.exe`). Execute the installer and make sure you
install openSSL tools when you install stunnel. You will need this to convert
your certificate from PFX to PEM format.
Even though the installer says it is for win64, it installs stunnel by default
to `\Program Files (x86)\stunnel`.
## Get your certificate ready for Stunnel
Whether you use a Public Certificate Authority or Internal Certificate
Authority, the next step is exporting the certificate from Windows into a format
useable by Stunnel.
### Export Certificate from Windows
If your certificate is installed via Windows Certificate Management, you will
need to export the certificate into a usable format. You can do this either
using the Windows Certificate Management Console, or PowerShell.
#### Certificate Management Console
Start `mmc.exe` as an Administrator. Select 'File>Add/Remove Snapin', select
'Certificates' from the list, and click 'Add'. Select 'Computer Account',
'Next', 'Local Computer', and then 'Finish'. In the Console Root, expand
'Certificates', then 'Personal', and select 'Certificates'. In the middle pane
find and select your certificate. Right click the certificate and select
'All Tasks>Export'. You want to export as PFX the Private Key, include all
certificates in the certification path, and use a password only to secure the
file. Enter a path and file name to a working directory and complete the
export.
Continue with [Convert Certificate from PFX to PEM](#convert).
#### PowerShell
If you know the Friendly
Name of the Certificate this is relatively easy. Since you need to export
the private key as well, you must run the following from an Administrative
PowerShell console.
```PowerShell
$passwd = ConvertTo-SecureString -string "yourpassword" -Force -AsPlainText
Get-ChildItem Cert:\LocalMachine\My | Where{$_.FriendlyName -eq "FriendlyName"} |
Export-PfxCertificate -FilePath fossil-scm.pfx -Password $passwd
```
You will now have your certificate stored as a PFX file.
<a name="convert"></a>
### Convert Certificate from PFX to PEM
For this step you will need the openssl tools that were installed with stunnel.
```PowerShell
# Add stunnel\bin directory to path for this session.
$env:PATH += ";${env:ProgramFiles(x86)}\stunnel\bin"
# Export Private Key
openssl.exe pkcs12 -in fossil-scm.pfx -out fossil-scm.key -nocerts -nodes
# Export the Certificate
openssl.exe pkcs12 -in fossil-scm.pfx -out fossil-scm.pem -nokeys
```
Now move `fossil-scm.key` and `fossil-scm.pem` to your stunnel config directory
(by default this should be located at `\Program Files (x86)\stunne\config`).
## stunnel Configuration
Use the reverse proxy configuration given in the generic [Serving via
stunnel document](../any/stunnel.md#proxy). On Windows, the
`stunnel.conf` file is located at `\Program Files (x86)\stunnel\config`.
You will need to modify it to point at the PEM and key files generated
above.
After completing the above configuration restart the stunnel service in Windows
with the following:
```PowerShell
Restart-Service -Name stunnel
```
## Open up port 443 in the Windows Firewall
The following instructions are for the [Windows Advanced
Firewall](https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-firewall/windows-firewall-with-advanced-security).
If you are using a different Firewall, please consult your Firewall
documentation for how to open port 443 for inbound traffic.
The following command should be entered all on one line.
```PowerShell
New-NetFirewallRule -DisplayName "Allow Fossil Inbound" -Description "Allow Fossil inbound on port 443 using Stunnel as TLS Proxy."
-Direction Inbound -Protocol TCP -LocalPort 443 -Action Allow -Program "C:\Program Files (x86)\Stunnel\bin\stunnel.exe"
```
You should now be able to access your new Fossil Server via HTTPS.
*[Return to the top-level Fossil server article.](../)*
|
1 2 3 4 | <title>CGI Server Extensions</title> <h2>1.0 Introduction</h2> | | | 1 2 3 4 5 6 7 8 9 10 11 12 | <title>CGI Server Extensions</title> <h2>1.0 Introduction</h2> If you have a [./server/|Fossil server] for your project, you can add [https://en.wikipedia.org/wiki/Common_Gateway_Interface|CGI] extensions to that server. These extensions work like any other CGI program, except that they also have access to the Fossil login information and can (optionally) leverage the "skins" of Fossil so that they appear to be more tightly integrated into the project. An example of where this is useful is the |
| ︙ | ︙ | |||
24 25 26 27 28 29 30 | the checklist. <h2>2.0 How It Works</h2> CGI Extensions are disabled by default. An administrator activates the CGI extension mechanism by specifying an "Extension Root Directory" or "extroot" as part of the server setup. | | > | | > > | | 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 | the checklist. <h2>2.0 How It Works</h2> CGI Extensions are disabled by default. An administrator activates the CGI extension mechanism by specifying an "Extension Root Directory" or "extroot" as part of the server setup. If the Fossil server is itself run as [./server/any/cgi.md|CGI], then add a line to the [./cgi.wiki#extroot|CGI script file] that says: <blockquote><pre> extroot: <i>DIRECTORY</i> </pre></blockquote> Or, if the Fossil server is being run using the "[./server/any/none.md|fossil server]" or "[./server/any/none.md|fossil ui]" or "[./server/any/inetd.md|fossil http]" commands, then add an extra "--extroot <i>DIRECTORY</i>" option to that command. The <i>DIRECTORY</i> is the DOCUMENT_ROOT for the CGI. Files in the DOCUMENT_ROOT are accessed via URLs like this: <blockquote> https://example-project.org/ext/<i>FILENAME</i> |
| ︙ | ︙ | |||
78 79 80 81 82 83 84 | Then it takes the leftover "/checklist" part and appends it to the "extroot" to get the filename "/sqlite-src-ext/checklist". Fossil finds that file to be executable, so it runs it as CGI and returns the result. The /sqlite-src-ext/checklist file is a [https://wapp.tcl.tk|Wapp program]. The current source code to the this program can be seen at | | | | | | 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 | Then it takes the leftover "/checklist" part and appends it to the "extroot" to get the filename "/sqlite-src-ext/checklist". Fossil finds that file to be executable, so it runs it as CGI and returns the result. The /sqlite-src-ext/checklist file is a [https://wapp.tcl.tk|Wapp program]. The current source code to the this program can be seen at [https://www.sqlite.org/src/ext/checklist/3070700/self] and recent historical versions are available at [https://sqlite.org/docsrc/finfo/misc/checklist.tcl] with older legacy at [https://sqlite.org/checklistapp/timeline?n=all] There is a cascade of CGIs happening here. The web server that receives the initial HTTP request runs Fossil as a CGI based on the "https://sqlite.org/src" portion of the URL. The Fossil instance then runs the checklist sub-CGI based on the "/ext/checklists" suffix. The output of the sub-CGI is read by Fossil and then relayed on to the main web server which in turn relays the result back to the original client. <h3>2.2 Example #2</h3> The [https://fossil-scm.org/home|Fossil self-hosting repository] is also a CGI that looks like this: <blockquote><verbatim> #!/usr/bin/fossil repository: /fossil/fossil.fossil errorlog: /logs/errors.txt extroot: /fossil-extroot </verbatim></blockquote> The extroot for this Fossil server is /fossil-extroot and in that directory is an executable file named "fileup1" - another [https://wapp.tcl.tk|Wapp] script. (The extension mechanism is not required to use Wapp. You can use any kind of program you like. But the creator of SQLite and Fossil is fond of [https://www.tcl.tk|Tcl/Tk] and so he tends to gravitate toward Tcl-based technologies like Wapp.) The fileup1 script is a demo program that lets the user upload a file using a form, and then displays that file in the reply. There is a link on the page that causes the fileup1 script to return a copy of its own source-code, so you can see how it works. |
| ︙ | ︙ | |||
153 154 155 156 157 158 159 | "[https://duckduckgo.com/?q=cgi+environment_variables|cgi environment variables]" to find more detail about what each of the above variables mean and how they are used. Live listings of the values of some or all of these environment variables can be found at links like these: * [https://fossil-scm.org/home/test_env] | | > | | > > > > > > > > > > > > > > > > > | | | 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 | "[https://duckduckgo.com/?q=cgi+environment_variables|cgi environment variables]" to find more detail about what each of the above variables mean and how they are used. Live listings of the values of some or all of these environment variables can be found at links like these: * [https://fossil-scm.org/home/test_env] * [https://sqlite.org/src/ext/checklist/top/env] In addition to the standard CGI environment variables listed above, Fossil adds the following: * FOSSIL_CAPABILITIES * FOSSIL_NONCE * FOSSIL_REPOSITORY * FOSSIL_URI * FOSSIL_USER The FOSSIL_USER string is the name of the logged-in user. This variable is missing or is an empty string if the user is not logged in. The FOSSIL_CAPABILITIES string is a list of [./caps/ref.html|Fossil capabilities] that indicate what permissions the user has on the Fossil repository. The FOSSIL_REPOSITORY environment variable gives the filename of the Fossil repository that is running. The FOSSIL_URI variable shows the prefix of the REQUEST_URI that is the Fossil CGI script, or is an empty string if Fossil is being run by some method other than CGI. The [https://sqlite.org/src/ext/checklist|checklist application] uses the FOSSIL_USER environment variable to determine the name of the user and the FOSSIL_CAPABILITIES variable to determine if the user is allowed to mark off changes to the checklist. Only users with check-in permission to the Fossil repository are allowed to mark off checklist items. That means that the FOSSIL_CAPABILITIES string must contain the letter "i". Search for "FOSSIL_CAPABILITIES" in the [https://sqlite.org/src/ext/checklist/top/self|source listing] to see how this happens. If the CGI output is one of the forms for which Fossil inserts its own header and footer, then the inserted header will include a Content Security Policy (CSP) restriction on the use of javascript within the webpage. Any <script>...</script> elements within the CGI output must include a nonce or else they will be suppressed by the web browser. The FOSSIL_NONCE variable contains the value of that nonce. So, in other words, to get javascript to work, it must be enclosed in: <blockquote><verbatim> <script nonce='$FOSSIL_NONCE'>...</script> </verbatim></blockquote> Except, of course, the $FOSSIL_NONCE is replaced by the value of the FOSSIL_NONCE environment variable. <h3>3.1 Input Content</h3> If the HTTP request includes content (for example if this is a POST request) then the CONTENT_LENGTH value will be positive and the data for the content will be readable on standard input. <h2>4.0 CGI Outputs</h2> CGI programs construct a reply by writing to standard output. The first few lines of output are parameters intended for the web server that invoked the CGI. These are followed by a blank line and then the content. Typical parameter output looks like this: <blockquote><verbatim> Status: 200 Ok Content-Type: text/html </verbatim></blockquote> CGI programs can return any content type they want - they are not restricted to text replies. It is OK for a CGI program to return (for example) image/png. The fields of the CGI response header can be any valid HTTP header fields. Those that Fossil does not understand are simply relayed back to up the line to the requester. Fossil takes special action with some content types. If the Content-Type is "text/x-fossil-wiki" or "text/x-markdown" then Fossil converts the content from [/wiki_rules|Fossil-Wiki] or [/md_rules|Markdown] into HTML, adding its own header and footer text according to the repository skin. Content of type "text/html" is normally passed straight through unchanged. However, if the text/html content is of the form: <blockquote><verbatim> |
| ︙ | ︙ | |||
256 257 258 259 260 261 262 | by Fossil. <h2>6.0 Trouble-Shooting Hints</h2> Remember that the /ext will return any file in the extroot directory hierarchy as static content if the file is readable but not executable. When initially setting up the /ext mechanism, it is sometimes helpful | | | | | | 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 | by Fossil. <h2>6.0 Trouble-Shooting Hints</h2> Remember that the /ext will return any file in the extroot directory hierarchy as static content if the file is readable but not executable. When initially setting up the /ext mechanism, it is sometimes helpful to verify that you are able to receive static content prior to starting work on your CGIs. Also remember that CGIs must be executable files. Fossil likes to run inside a chroot jail, and will automatically put itself inside a chroot jail if it can. The sub-CGI program will also run inside this same chroot jail. Make sure all embedded pathnames have been adjusted accordingly and that all resources needed by the CGI program are available within the chroot jail. If anything goes wrong while trying to process an /ext page, Fossil returns a 404 Not Found error with no details. However, if the requester is logged in as a user that has <b>[./caps/ref.html#D | Debug]</b> capability then additional diagnostic information may be included in the output. If the /ext page has a "fossil-ext-debug=1" query parameter and if the requester is logged in as a user with Debug privilege, then the CGI output is returned verbatim, as text/plain and with the original header intact. This is useful for diagnosing problems with the CGI script. |
1 | <title>Deleting Content From Fossil</title> | | > | < > | | < | | | > > | | < | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
<title>Deleting Content From Fossil</title>
<h2>Good Reasons for Removing Content from a Fossil Repository</h2>
Fossil is designed to keep all historical content forever. Fossil
purposely makes it difficult for users to delete content. Old content
is part of the project's <i>*ahem*</i> fossil record and should be
maintained indefinitely to maintain an accurate history of the project.
Nevertheless, there may occasionally arise legitimate reasons for
deleting content. Such reasons include:
* Spammers inserted inappropriate content into a wiki page, forum post,
or ticket. Fossil lets you easily hide or amend such content, but
since it is not a legitimate part of the project's history, there
is no value in keeping it, so it is best removed permanently.
* A file that contains trade secrets or that is under someone else's
copyright was accidentally committed and needs to be backed out.
* A malformed control artifact was inserted and is disrupting the
operation of Fossil.
<h2>Alternatives</h2>
All of these are rare cases: Fossil is [./antibot.wiki | designed to
foil spammers up front], legally problematic check-ins should range from
rare to nonexistent, and you have to go way out of your way to force
Fossil to insert bad control artifacts. Therefore, before we get to
methods of permanently deleting content from a Fossil repos, let's give
some alternatives that usually suffice, which don't damage the project's
fossil record:
<ul>
<li><p>When a forum post or wiki article is "deleted," what actually
happens is that a new empty version is added to the Fossil
[./blockchain.md | block chain]. The web interface interprets this
as "deleted," but the prior version remains available if you go
digging for it.</p></li>
<li><p>When you close a ticket, it's marked in a way that causes it
to not show up in the normal ticket reports. You usually want to
give it a Resolution such as "Rejected" when this happens, plus
possibly a comment explaining why you're closing it. This is all new
information added to the ticket, not deletion.</p></li>
<li><p>When you <tt>fossil rm</tt> a file, a new manifest is
checked into the repository with the same file list as for the prior
version minus the "removed" file. The file is still present in the
repository; it just isn't part of that version forward on that
branch.</p></li>
<li><p>If you make a bad check-in, you can shunt it off to the side
by amending it to put it on a different branch, then continuing
development on the prior branch:
<p>
<tt>$ fossil amend abcd1234 --branch BOGUS --hide<br>
$ fossil up trunk</tt>
<p>
The first command moves check-in ID <tt>abcd1234</tt> (and any
subsequent check-ins on that branch!) to a branch called
<tt>BOGUS</tt>, then hides it so it doesn't show up on the
timeline. You can call this branch anything you like, and you can
re-use the same name as many times as you like. No content is
actually deleted: it's just shunted off to the side and hidden away.
You might find it easier to do this from the Fossil web UI in
the "edit" function for a check-in.
<p>
The second command returns to the last good check-in on that branch
so you can continue work from that point.</p></li>
<li><p>When the check-in you want to remove is followed by good
check-ins on the same branch, you can't use the previous method,
because it will move the good check-ins, too. The solution is:
<p>
<tt>$ fossil merge --backout abcd1234</tt>
<p>That creates a diff in the check-out directory that backs out the
bad check-in <tt>abcd1234</tt>. You then fix up any merge conflicts,
build, test, etc., then check the reverting change into the
repository. Again, nothing is actually deleted; you're just adding
more information to the repository which corrects a prior
check-in.</p></li>
</ul>
<h2>Shunning</h2>
Fossil provides a mechanism called "shunning" for removing content from
a repository.
Every Fossil repository maintains a list of the hash names of
|
| ︙ | ︙ |
|
| | | 1 2 3 4 5 6 7 8 | <title>Securing a Repository with TLS</title> <h2>Using TLS-Encrypted Communications with Fossil</h2> If you are storing sensitive information in a repository accessible over a network whose integrity you do not fully trust, you should use TLS to encrypt all communications with it. This is most true for repositories accessed over the Internet, especially if they will be accessed from |
| ︙ | ︙ | |||
102 103 104 105 106 107 108 | Beware, taking this path typically opens you up to new problems, which are conveniently covered in the next section! <h3 id="certs">Certificates</h3> To verify the identify of a server, TLS uses | | > > > > > > > | > | | > > | > > > | > > > | > | > > > > | | > > | | | | 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 |
Beware, taking this path typically opens you up to new problems, which
are conveniently covered in the next section!
<h3 id="certs">Certificates</h3>
To verify the identify of a server, TLS uses
[https://en.wikipedia.org/wiki/X.509#Certificates|X.509 certificates], a
scheme that depends on a trust hierarchy of so-called
[https://en.wikipedia.org/wiki/Certificate_authority | Certificate
Authorities]. The tree of trust relationships ultimately ends in the
CA roots, which are considered the ultimate arbiters of who to trust in
this scheme.
The question then is, what CA roots does Fossil trust?
If you are using a self-signed certificate, Fossil will initially not
know that it can trust your certificate, so you'll be asked if you want
to accept the certificate the first time you communicate with the
server. Verify the certificate fingerprint is correct, then answer
"always" if you want Fossil to remember your decision.
If you are cloning from or syncing to Fossil servers that use a
certificate signed by a well-known CA or one of its delegates, Fossil
still has to know which CA roots to trust. When this fails, you get an
error message that looks like this in Fossil 2.11 and newer:
<pre>
Unable to verify SSL cert from www.fossil-scm.org
subject: CN = sqlite.org
issuer: C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
sha256: bf26092dd97df6e4f7bf1926072e7e8d200129e1ffb8ef5276c1e5dd9bc95d52
accept this cert and continue (y/N)?
</pre>
In older versions, the message was much longer and began with this line:
<pre>
SSL verification failed: unable to get local issuer certificate
</pre>
Fossil relies on the OpenSSL library to have some way to check a trusted
list of CA signing keys. There are two common ways this fails:
# <p>The OpenSSL library Fossil is linked to doesn't have a CA
signing key set at all, so that it initially trusts no certificates
at all.</p>
# <p>The OpenSSL library does have a CA cert set, but your Fossil server's
TLS certificate was signed by a CA that isn't in that set.</p>
A common reason to fall into the second trap is that you're using
certificates signed by a local private CA, as often happens in large
enterprises. You can solve this sort of problem by getting your local
CA's signing certificate in PEM format and pointing OpenSSL at it:
<pre>
fossil set --global ssl-ca-location /path/to/local-ca.pem
</pre>
The use of <tt>--global</tt> with this option is common, since you may
have multiple reposotories served under certificates signed by that same
CA. However, if you have a mix of publicly-signed and locally-signed
certificates, you might want to drop the <tt>--global</tt> flag and set
this option on a per-repository basis instead.
A common way to run into the broader first problem is that you're on
FreeBSD, which does not install a CA certificate set by default, even as
a dependency of the OpenSSL library. If you're using a certificate
signed by one of the major public CAs, you can solve this by installing
the <tt>ca_root_nss</tt> package. That package contains the Mozilla NSS
certificate bundle, which gets installed in a location that OpenSSL
checks by default, so you don't need to change any Fossil settings.
(This is the same certificate set that ships with Firefox, by the way.)
The same sort of thing happens with the Windows build of OpenSSL, but
for a different core reason: Windows does ship with a stock CA
certificate set, but it's not in a format that OpenSSL understands how
to use. Rather than try to find a way to convert the data format, you
may find it acceptable to use the same Mozilla NSS cert set. I do not
know of a way to easily get this from Mozilla themselves, but I did find
a [https://curl.haxx.se/docs/caextract.html|third party source] for the
<tt>cacert.pem</tt> file. I suggest placing the file into your Windows
user home directory so that you can then point Fossil at it like so:
<pre>
fossil set --global ssl-ca-location %userprofile%\cacert.pem
</pre>
This can also happen if you've linked Fossil to a version of OpenSSL
[#openssl-src|built from source]. That same <tt>cacert.pem</tt> fix can
work in that case, too.
When you build Fossil on Linux platforms against the binary OpenSSL
|
| ︙ | ︙ | |||
204 205 206 207 208 209 210 | <h2 id="server">Fossil TLS Configuration: Server Side</h2> Fossil's built-in HTTP server feature does not currently have a built-in way to serve via HTTP over TLS, a.k.a. HTTPS, even when you've linked Fossil to OpenSSL. To serve a Fossil repository via HTTPS, you must put | | | | < < < < < < < < | < < < < < < < | < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | <h2 id="server">Fossil TLS Configuration: Server Side</h2> Fossil's built-in HTTP server feature does not currently have a built-in way to serve via HTTP over TLS, a.k.a. HTTPS, even when you've linked Fossil to OpenSSL. To serve a Fossil repository via HTTPS, you must put it behind some kind of HTTPS proxy. We have a number of documents elsewhere in this repository that cover your options for [./server/ | serving Fossil repositories]. A few of the most useful of these are: * <a id="stunnel" href="./server/any/stunnel.md">Serving via stunnel</a> * <a id="althttpd" href="./server/any/althttpd.md">Serving via stunnel + althttpd</a> * <a id="nginx" href="./server/any/scgi.md">Serving via SCGI (nginx)</a> <h2 id="enforcing">Enforcing TLS Access</h2> To use TLS encryption in cloning and syncing to a remote Fossil repository, be sure to use the <tt>https:</tt> URI scheme in <tt>clone</tt> and <tt>sync</tt> commands. If your server is configured |
| ︙ | ︙ |
| ︙ | ︙ | |||
123 124 125 126 127 128 129 | "GB" means gigabytes (10<sup><small>9</small></sup> bytes) not <a href="http://en.wikipedia.org/wiki/Gibibyte">gibibytes</a> (2<sup><small>30</small></sup> bytes). Similarly, "MB" and "KB" means megabytes and kilobytes, not mebibytes and kibibytes. <h2>Analysis And Supplemental Data</h2> | | | 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | "GB" means gigabytes (10<sup><small>9</small></sup> bytes) not <a href="http://en.wikipedia.org/wiki/Gibibyte">gibibytes</a> (2<sup><small>30</small></sup> bytes). Similarly, "MB" and "KB" means megabytes and kilobytes, not mebibytes and kibibytes. <h2>Analysis And Supplemental Data</h2> Perhaps the two most interesting data points in the above table are SQLite and SLT. SQLite is a long-running project with long revision chains. Some of the files in SQLite have been edited over a thousand times. Each of these edits is stored as a delta, and hence the SQLite project gets excellent 80:1 compression. SLT, on the other hand, consists of many large (megabyte-sized) SQL scripts that have one or maybe two edits each. There is very little delta compression occurring and so the overall repository compression ratio is much lower. Note also that |
| ︙ | ︙ |
| ︙ | ︙ | |||
57 58 59 60 61 62 63 | issues one or more HTTP requests and receives replies for each request.</p> <p>The server might be running as an independent server using the <b>server</b> command, or it might be launched from inetd or xinetd using the <b>http</b> command. Or the server might be launched from CGI. | | | 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | issues one or more HTTP requests and receives replies for each request.</p> <p>The server might be running as an independent server using the <b>server</b> command, or it might be launched from inetd or xinetd using the <b>http</b> command. Or the server might be launched from CGI. (See "[./server/|How To Configure A Fossil Server]" for details.) The specifics of how the server listens for incoming HTTP requests is immaterial to this protocol. The important point is that the server is listening for requests and the client is the issuer of the requests.</p> <p>A single push, pull, or sync might involve multiple HTTP requests. The client maintains state between all requests. But on the server |
| ︙ | ︙ | |||
81 82 83 84 85 86 87 | that responds. Nothing more.</p> <h4>2.0.1 Encrypted Transport</h4> <p>In the current implementation of Fossil, the server only understands HTTP requests. The client can send either clear-text HTTP requests or encrypted HTTPS requests. But when | | | 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | that responds. Nothing more.</p> <h4>2.0.1 Encrypted Transport</h4> <p>In the current implementation of Fossil, the server only understands HTTP requests. The client can send either clear-text HTTP requests or encrypted HTTPS requests. But when HTTPS requests are sent, they first must be decrypted by a web server or proxy before being passed to the Fossil server. This limitation may be relaxed in a future release.</p> <h3>2.1 Server Identification</h3> <p>The server is identified by a URL argument that accompanies the push, pull, or sync command on the client. (As a convenience to |
| ︙ | ︙ | |||
419 420 421 422 423 424 425 426 427 428 429 430 431 432 | The receiver of an igot card will typically check to see if it also holds the same artifact and if not it will request the artifact using a gimme card in either the reply or in the next message.</p> <p>If the second argument exists and is "1", then the artifact identified by the first argument is private on the sender and should be ignored unless a "--private" [/help?cmd=sync|sync] is occurring. <h4>3.6.1 Unversioned Igot Cards</h4> <p>Zero or more "uvigot" cards are sent from server to client when synchronizing unversioned content. The format of a uvigot card is as follows: | > > > | 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 | The receiver of an igot card will typically check to see if it also holds the same artifact and if not it will request the artifact using a gimme card in either the reply or in the next message.</p> <p>If the second argument exists and is "1", then the artifact identified by the first argument is private on the sender and should be ignored unless a "--private" [/help?cmd=sync|sync] is occurring. <p>The name "igot" comes from the English slang expression "I got" meaning "I have". <h4>3.6.1 Unversioned Igot Cards</h4> <p>Zero or more "uvigot" cards are sent from server to client when synchronizing unversioned content. The format of a uvigot card is as follows: |
| ︙ | ︙ | |||
446 447 448 449 450 451 452 | holds. The client will use this information to figure out which unversioned files need to be synchronized. The server might also send a uvigot card when it receives a uvgimme card but its reply message size is already oversized and hence unable to hold the usual uvfile reply. <p>When a client receives a "uvigot" card, it checks to see if the | | > > > > | 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 | holds. The client will use this information to figure out which unversioned files need to be synchronized. The server might also send a uvigot card when it receives a uvgimme card but its reply message size is already oversized and hence unable to hold the usual uvfile reply. <p>When a client receives a "uvigot" card, it checks to see if the file needs to be transferred from client to server or from server to client. If a client-to-server transmission is needed, the client schedules that transfer to occur on a subsequent HTTP request. If a server-to-client transfer is needed, then the client sends a "uvgimme" card back to the server to request the file content. <h3>3.7 Gimme Cards</h3> <p>A gimme card is sent from either client to server or from server to client. The gimme card asks the receiver to send a particular artifact back to the sender. The format of a gimme card is this:</p> <blockquote> <b>gimme</b> <i>artifact-id</i> </blockquote> <p>The argument to the gimme card is the ID of the artifact that the sender wants. The receiver will typically respond to a gimme card by sending a file card in its reply or in the next message.</p> <p>The "gimme" name means "give me". The imperative "give me" is pronounced as if it were a single word "gimme" in some dialects of English (including the dialect spoken by the original author of Fossil). <h4>3.7.1 Unversioned Gimme Cards</h4> <p>Sync synchronizing unversioned content, the client may send "uvgimme" cards to the server. A uvgimme card requests that the server send unversioned content to the client. The format of a uvgimme card is as follows: |
| ︙ | ︙ | |||
687 688 689 690 691 692 693 | content on the server exactly matches the content on the client and no further synchronization is required. <li><p><b>uv-pull-only</b></i> <p>A server sends the uv-pull-only pragma to the client in response to a uv-hash pragma with a mismatched content hash argument. This pragma indicates that there are differences in unversioned content | | | | 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 | content on the server exactly matches the content on the client and no further synchronization is required. <li><p><b>uv-pull-only</b></i> <p>A server sends the uv-pull-only pragma to the client in response to a uv-hash pragma with a mismatched content hash argument. This pragma indicates that there are differences in unversioned content between the client and server but that content can only be transferred from server to client. The server is unwilling to accept content from the client because the client login lacks the "write-unversioned" permission.</p> <li><p><b>uv-push-ok</b></i> <p>A server sends the uv-push-ok pragma to the client in response to a uv-hash pragma with a mismatched content hash argument. This pragma indicates that there are differences in unversioned content between the client and server and that content can only be transferred in either direction. The server is willing to accept content from the client because the client login has the "write-unversioned" permission.</p> <li><p><b>ci-lock</b> <i>CHECKIN-HASH CLIENT-ID</i></p> <p>A client sends the "ci-lock" pragma to the server to indicate that it is about to add a new check-in as a child of the |
| ︙ | ︙ |
| ︙ | ︙ | |||
66 67 68 69 70 71 72 | The chart below provides a quick summary of how each of these database files are used by Fossil, with detailed discussion following. <table border="1" width="80%" cellpadding="0" align="center"> <tr> <td width="33%" valign="top"> | | > | | 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 |
The chart below provides a quick summary of how each of these
database files are used by Fossil, with detailed discussion following.
<table border="1" width="80%" cellpadding="0" align="center">
<tr>
<td width="33%" valign="top">
<h3 align="center">Configuration Database<br>"~/.fossil" or<br>
"~/.config/fossil.db"</h3>
<ul>
<li>Global [/help/settings |settings]
<li>List of active repositories used by the [/help/all | all] command
</ul>
</td>
<td width="34%" valign="top">
<h3 align="center">Repository Database<br>"<i>project</i>.fossil"</h3>
<ul>
<li>[./fileformat.wiki | Global state of the project]
encoded using delta-compression
<li>Local [/help/settings|settings]
<li>Web interface display preferences
<li>User credentials and permissions
<li>Metadata about the global state to facilitate rapid
queries
</ul>
</td>
<td width="33%" valign="top">
<h3 align="center">Checkout Database<br>"_FOSSIL_" or ".fslckout"</h3>
<ul>
<li>The repository database used by this checkout
<li>The version currently checked out
<li>Other versions [/help/merge | merged] in but not
yet [/help/commit | committed]
<li>Changes from the [/help/add | add], [/help/delete | delete],
and [/help/rename | rename] commands that have not yet been committed
|
| ︙ | ︙ | |||
122 123 124 125 126 127 128 | a way to change settings for all repositories with a single command, rather than having to change the setting individually on each repository. The configuration database also maintains a list of repositories. This list is used by the [/help/all | fossil all] command in order to run various operations such as "sync" or "rebuild" on all repositories managed by a user. | > > > | > | > > > > > > > > > > | > | > > > > | > > > > > > > > | > > > > > | > > > > > > > > | > | | | | > | | | | 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 |
a way to change settings for all repositories with a single command, rather
than having to change the setting individually on each repository.
The configuration database also maintains a list of repositories. This
list is used by the [/help/all | fossil all] command in order to run various
operations such as "sync" or "rebuild" on all repositories managed by a user.
<a name='configloc'></a>
<h4>2.1.1 Location Of The Configuration Database</h4>
On Unix systems, the configuration database is named by the following
algorithm:
<blockquote><table border="0">
<tr><td>1. if environment variable FOSSIL_HOME exists
<td> → <td>$FOSSIL_HOME/.fossil
<tr><td>2. if file ~/.fossil exists<td> →<td>~/.fossil
<tr><td>3. if environment variable XDG_CONFIG_HOME exists
<td> →<td>$XDG_CONFIG_HOME/fossil.db
<tr><td>4. if the directory ~/.config exists
<td> →<td>~/.config/fossil.db
<tr><td>5. Otherwise<td> →<td>~/.fossil
</table></blockquote>
Another way of thinking of this algorithm is the following:
* Use "$FOSSIL_HOME/.fossil" if the FOSSIL_HOME variable is defined
* Use the XDG-compatible name (usually ~/.config/fossil.db) on XDG systems
if the ~/.fossil file does not already exist
* Otherwise, use the traditional unix name of "~/.fossil"
This algorithm is complex due to the need for historical compatibility.
Originally, the database was always just "~/.fossil". Then support
for the FOSSIL_HOME environment variable as added. Later, support for the
[https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html|XDG-compatible configation filenames]
was added. Each of these changes needed to continue to support legacy
installations.
On Windows, the configuration database is the first of the following
for which the corresponding environment variables exist:
* %FOSSIL_HOME%/_fossil
* %LOCALAPPDATA%/_fossil
* %APPDATA%/_fossil
* %USERPROFILES%/_fossil
* %HOMEDRIVE%%HOMEPATH%/_fossil
The second case is the one that usually determines the name Note that the
FOSSIL_HOME environment variable can always be set to determine the
location of the configuration database. Note also that the configuration
database file itself is called ".fossil" or "fossil.db" on unix but
"_fossil" on windows.
The [/help?cmd=info|fossil info] command will show the location of
the configuration database on a line that starts with "config-db:".
<h3>2.2 Repository Databases</h3>
The repository database is the file that is commonly referred to as
"the repository". This is because the repository database contains,
among other things, the complete revision, ticket, and wiki history for
a project. It is customary to name the repository database after then
name of the project, with a ".fossil" suffix. For example, the repository
database for the self-hosting Fossil repository is called "fossil.fossil"
and the repository database for SQLite is called "sqlite.fossil".
<h4>2.2.1 Global Project State</h4>
The bulk of the repository database (typically 75 to 85%) consists
of the artifacts that comprise the
[./fileformat.wiki | enduring, global, shared state] of the project.
The artifacts are stored as BLOBs, compressed using
[http://www.zlib.net/ | zlib compression] and, where applicable,
using [./delta_encoder_algorithm.wiki | delta compression].
The combination of zlib and delta compression results in a considerable
space savings. For the SQLite project (when this paragraph was last
updated on 2020-02-08)
the total size of all artifacts is over 7.1 GB but thanks to the
combined zlib and delta compression, that content only takes less than
97 MB of space in the repository database, for a compression ratio
of about 74:1. The median size of all content BLOBs after delta
and zlib compression have been applied is 156 bytes.
The median size of BLOBs without compression is 45,312 bytes.
Note that the zlib and delta compression is not an inherent part of the
Fossil file format; it is just an optimization.
The enduring file format for Fossil is the unordered
set of artifacts. The compression techniques are just a detail of
how the current implementation of Fossil happens to store these artifacts
efficiently on disk.
All of the original uncompressed and un-delta'd artifacts can be extracted
from a Fossil repository database using
the [/help/deconstruct | fossil deconstruct]
command. Individual artifacts can be extracted using the
[/help/artifact | fossil artifact] command.
When accessing the repository database using raw SQL and the
[/help/sqlite3 | fossil sql] command, the extension function
"<tt>content()</tt>" with a single argument which is the SHA1 or
SHA3-256 hash
of an artifact will return the complete uncompressed
content of that artifact.
Going the other way, the [/help/reconstruct | fossil reconstruct]
command will scan a directory hierarchy and add all files found to
a new repository database. The [/help/import | fossil import] command
works by reading the input git-fast-export stream and using it to construct
corresponding artifacts which are then written into the repository database.
|
| ︙ | ︙ | |||
312 313 314 315 316 317 318 | * State information for the [/help/bisect | bisect] command. For Fossil commands that run from within a working checkout, the first thing that happens is that Fossil locates the checkout database. Fossil first looks in the current directory. If not found there, it looks in the parent directory. If not found there, the parent of the parent. And so forth until either the checkout database is found | | | 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | * State information for the [/help/bisect | bisect] command. For Fossil commands that run from within a working checkout, the first thing that happens is that Fossil locates the checkout database. Fossil first looks in the current directory. If not found there, it looks in the parent directory. If not found there, the parent of the parent. And so forth until either the checkout database is found or the search reaches the root of the file system. (In the latter case, Fossil returns an error, of course.) Once the checkout database is located, it is used to locate the repository database. Notice that the checkout database contains a pointer to the repository database but that the repository database has no record of the checkout databases. That means that a working checkout directory tree can be freely renamed or copied or deleted without consequence. But the |
| ︙ | ︙ |
| ︙ | ︙ | |||
10 11 12 13 14 15 16 | TH1 began as a minimalist re-implementation of the Tcl scripting language. There was a need to test the SQLite library on Symbian phones, but at that time all of the test cases for SQLite were written in Tcl and Tcl could not be easily compiled on the SymbianOS. So TH1 was developed as a cut-down version of Tcl that would facilitate running the SQLite test scripts on SymbianOS. | < | > | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | TH1 began as a minimalist re-implementation of the Tcl scripting language. There was a need to test the SQLite library on Symbian phones, but at that time all of the test cases for SQLite were written in Tcl and Tcl could not be easily compiled on the SymbianOS. So TH1 was developed as a cut-down version of Tcl that would facilitate running the SQLite test scripts on SymbianOS. Fossil was first being designed at about the same time that TH1 was being developed for testing SQLite on SymbianOS. Early prototypes of Fossil were written in pure Tcl. But as the development shifted toward the use of C-code, the need arose to have a Tcl-like scripting language to help with code generation. TH1 was small and light-weight and used minimal resources and seemed ideally suited for the task. The name "TH1" stands "Test Harness 1", since that was its original purpose. |
| ︙ | ︙ | |||
39 40 41 42 43 44 45 | The text of the command (excluding the newline or semicolon terminator) is broken into space-separated tokens. The first token is the command name and subsequent tokens are the arguments. In this sense, TH1 syntax is similar to the familiar command-line shell syntax. A token is any sequence of characters other than whitespace and semicolons. Or, all text without double-quotes is a single token even if it includes | | | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
The text of the command (excluding the newline or semicolon terminator)
is broken into space-separated tokens. The first token is the command
name and subsequent tokens are the arguments. In this sense, TH1 syntax
is similar to the familiar command-line shell syntax.
A token is any sequence of characters other than whitespace and semicolons.
Or, all text without double-quotes is a single token even if it includes
whitespace and semicolons. Or, all text within nested {...} pairs is a
single token.
The nested {...} form of tokens is important because it allows TH1 commands
to have an appearance similar to C/C++. It is important to remember, though,
that a TH1 script is really just a list of text commands, not a context-free
language with a grammar like C/C++. This can be confusing to long-time
C/C++ programmers because TH1 does look a lot like C/C++, but the semantics
|
| ︙ | ︙ | |||
213 214 215 216 217 218 219 220 221 222 223 224 225 226 | * tclMakeSafe * tclReady * trace * unversioned content * unversioned list * utime * verifyCsrf * wiki Each of the commands above is documented by a block comment above their implementation in the th\_main.c or th\_tcl.c source files. All commands starting with "tcl", with the exception of "tclReady", require the Tcl integration subsystem be included at compile-time. | > | 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | * tclMakeSafe * tclReady * trace * unversioned content * unversioned list * utime * verifyCsrf * verifyLogin * wiki Each of the commands above is documented by a block comment above their implementation in the th\_main.c or th\_tcl.c source files. All commands starting with "tcl", with the exception of "tclReady", require the Tcl integration subsystem be included at compile-time. |
| ︙ | ︙ | |||
731 732 733 734 735 736 737 738 739 740 741 742 743 744 | * verifyCsrf Before using the results of a form, first call this command to verify that this Anti-CSRF token is present and is valid. If the Anti-CSRF token is missing or is incorrect, that indicates a cross-site scripting attack. If the event of an attack is detected, an error message is generated and all further processing is aborted. <a name="wiki"></a>TH1 wiki Command ----------------------------------- * wiki STRING Renders STRING as wiki content. | > > > > > > > > | 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 | * verifyCsrf Before using the results of a form, first call this command to verify that this Anti-CSRF token is present and is valid. If the Anti-CSRF token is missing or is incorrect, that indicates a cross-site scripting attack. If the event of an attack is detected, an error message is generated and all further processing is aborted. <a name="verifyLogin"></a>TH1 verifyLogin Command ------------------------------------------------- * verifyLogin Returns non-zero if the specified user name and password represent a valid login for the repository. <a name="wiki"></a>TH1 wiki Command ----------------------------------- * wiki STRING Renders STRING as wiki content. |
| ︙ | ︙ |
| ︙ | ︙ | |||
8 9 10 11 12 13 14 | to a ticket. The act of creating a ticket is considered a change. Each ticket change artifact contains the following information: <ul> <li>The ID of the ticket that was changed | | | | | 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 | to a ticket. The act of creating a ticket is considered a change. Each ticket change artifact contains the following information: <ul> <li>The ID of the ticket that was changed <li>The time stamp for when the change occurred <li>The user who made the change <li>A list of key/value pairs that show what changed in the ticket </ul> To determine the current state of a particular ticket, Fossil orders the change artifacts for that ticket from oldest to most recent, then applies each change in time stamp order. On each change artifact, there are one or more key/value pairs that implement the change. The key corresponds to a field of the ticket that is modified. The value may either replace the earlier value for that key, or the value may be appended to the prior value. <h2>2.0 Ticket Tables</h2> The low-level artifact format for ticket content is tedious and cumbersome to access in real time. To facility reporting and display of tickets, the low-level artifact information is collected and summarized in a pair of SQL tables in each local repository. Display and reporting of tickets is accomplished by querying these two tables. Note that only the low-level ticket change artifacts are synced. The content of the two ticket tables can always be reconstructed from the ticket change artifacts. And, indeed, the reconstruction of the ticket |
| ︙ | ︙ | |||
130 131 132 133 134 135 136 | <h3>2.2 Translating Artifacts To Tables</h3> Each row in the TICKETCHNG table corresponds to a single ticket change artifact. The tkt_id field is the integer primary key of the TICKET table entry for the corresponding ticket. The tkt_rid field is the integer primary key for the BLOB table entry that contains the low-level | | | | | 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 | <h3>2.2 Translating Artifacts To Tables</h3> Each row in the TICKETCHNG table corresponds to a single ticket change artifact. The tkt_id field is the integer primary key of the TICKET table entry for the corresponding ticket. The tkt_rid field is the integer primary key for the BLOB table entry that contains the low-level artifact text. The tkt_mtime field is the time stamp on the ticket change artifact, expressed as a Julian day number. If the ticket change artifact contains a key/value pair where the key is "login", then the corresponding value is stored in the login field of the TICKETCHNG table. The same it true for "username", "mimetype", and "icomment" fields. Any time there is a key/value pair in the ticket change artifact and the key corresponds to the name of a field in the TICKETCHNG table, then the value of that key/value pair is stored in the TICKETCHNG table. If the TICKETCHNG table has a field for which there is no corresponding key/value pair in the artifact, then that field of the TICKETCHNG table is NULL. If there are key/value pairs in the artifact that have no corresponding field in the TICKETCHNG table, those key/value pairs are silently ignored. Each row in the TICKET table records the overall status of a ticket. The tkt_id field is a unique integer primary key for the ticket. the tkt_uuid field is the global ticket identifier - a larger random hexadecimal constant. The tkt_mtime and tkt_ctime fields hold the times of the most recent and the oldest ticket change artifacts for this ticket, respectively. To reconstruct the TICKET table, the ticket change artifacts are visited in time stamp order. As each ticket change artifact is visited, its key/value pairs are examined. For any key/value pair in which the key is the same as a field in the TICKET table, the value of that pair either replaces or is appended to the previous value of the corresponding field in the TICKET table. Whether a value is replaced or appended is determined by markings in the ticket change artifact itself. Most fields are usually replaced. (For example, to change the status from "Open" to "Fixed" would involve a key value pair |
| ︙ | ︙ | |||
192 193 194 195 196 197 198 | support the "new-style" tickets. The TICKETCHNG table was added to support new-style tickets. In the new style, comment text is stored with the "icomment" (for "Incremental Comment") key and appears separately, and with its on mimetype, in multiple rows of the TICKETCHNG table. It then falls to the TH1 script code on the View Ticket Page to query the TICKETCHNG table and extract and format | | | 192 193 194 195 196 197 198 199 | support the "new-style" tickets. The TICKETCHNG table was added to support new-style tickets. In the new style, comment text is stored with the "icomment" (for "Incremental Comment") key and appears separately, and with its on mimetype, in multiple rows of the TICKETCHNG table. It then falls to the TH1 script code on the View Ticket Page to query the TICKETCHNG table and extract and format the various comments in time stamp order. |
1 2 3 4 5 6 7 | # Proxying Fossil via HTTPS with nginx One of the [many ways](./ssl.wiki) to provide TLS-encrypted HTTP access (a.k.a. HTTPS) to Fossil is to run it behind a web proxy that supports TLS. This document explains how to use the powerful [nginx web server](http://nginx.org/) to do that. | | < | < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < < < | < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < | < < < < < < | > | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | < | < < | | < < < < < | > | < < > | > | 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 |
# Proxying Fossil via HTTPS with nginx
One of the [many ways](./ssl.wiki) to provide TLS-encrypted HTTP access
(a.k.a. HTTPS) to Fossil is to run it behind a web proxy that supports
TLS. This document explains how to use the powerful [nginx web
server](http://nginx.org/) to do that.
This document is an extension of the [Serving via nginx on Debian][nod]
document. Please read that first, then come back here to extend its
configuration with TLS.
[nod]: ./server/debian/nginx.md
## Install Certbot
The [nginx-on-Debian document][nod] had you install a few non-default
packages to the system, but there’s one more you need for this guide:
$ sudo apt install certbot
You can extend this guide to other operating systems by following the
instructions found via [the front Certbot web page][cb] instead, telling
it what OS and web stack you’re using. Chances are good that they’ve got
a good guide for you already.
# Configuring Let’s Encrypt, the Easy Way
If your web serving needs are simple, [Certbot][cb] can configure nginx
for you and keep its certificates up to date. Simply follow Certbot’s
[nginx on Ubuntu 18.04 LTS guide][cbnu]. We’d recommend one small
change: to use the version of Certbot in the Ubuntu package repository
rather than download it from the Certbot site.
You should be able to use the nginx configuration given in our [Serving
via nginx on Debian][nod] guide with little to no change. The main thing
to watch out for is that the TCP port number in the nginx configuration
needs to match the value you gave when starting Fossil. If you followed
that guide’s advice, it will be 9000. Another option is to use [the
`fslsrv` script](/file/tools/fslsrv), in which case the TCP port number
will be 12345 or higher.
# Configuring Let’s Encrypt, the Hard Way
If you’re finding that you can’t get certificates to be issued or
renewed using the Easy Way instructions, the problem is usually that
your nginx configuration is too complicated for Certbot’s `--nginx`
plugin to understand. It attempts to rewrite your nginx configuration
files on the fly to achieve the renewal, and if it doesn’t put its
directives in the right locations, the domain verification can fail.
Let’s Encrypt uses the [Automated Certificate Management
Environment][acme] protocol (ACME) to determine whether a given client
actually has control over the domain(s) for which it wants a certificate
minted. Let’s Encrypt will not blithely let you mint certificates for
`google.com` and `paypal.com` just because you ask for it!
Your author’s configuration, glossed [in the HTTP-only guide][nod],
is complicated enough that
the current version of Certbot (0.28 at the time of this writing) can’t
cope with it. That’s the primary motivation for me to write this guide:
I’m addressing the “me” years hence who needs to upgrade to Ubuntu 20.04
or 22.04 LTS and has forgotten all of this stuff. 😉
## Step 1: Shifting into Manual
|
| ︙ | ︙ | |||
214 215 216 217 218 219 220 | nginx plugins. You’re looking for two lines setting the “install” and “auth” plugins to “nginx”. You can comment them out or remove them entirely. ## Step 2: Configuring nginx | < < < < | < < | < < < < < < | 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
nginx plugins. You’re looking for two lines setting the “install” and
“auth” plugins to “nginx”. You can comment them out or remove them
entirely.
## Step 2: Configuring nginx
This is a straightforward extension to [the HTTP-only
configuration](./server/debian/nginx.md#config):
server {
server_name .foo.net;
include local/tls-common;
charset utf-8;
|
| ︙ | ︙ | |||
266 267 268 269 270 271 272 |
server_name .foo.net;
root /var/www/foo.net;
include local/http-certbot-only;
access_log /var/log/nginx/foo.net-http-access.log;
error_log /var/log/nginx/foo.net-http-error.log;
}
| > | | | 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
server_name .foo.net;
root /var/www/foo.net;
include local/http-certbot-only;
access_log /var/log/nginx/foo.net-http-access.log;
error_log /var/log/nginx/foo.net-http-error.log;
}
One big difference between this and the HTTP-only case is
that we need two `server { }` blocks: one for HTTPS service, and
one for HTTP-only service.
### HTTP over TLS (HTTPS) Service
The first `server { }` block includes this file, `local/tls-common`:
listen 443 ssl;
|
| ︙ | ︙ | |||
369 370 371 372 373 374 375 |
challenge used by the Let’s Encrypt service only runs over HTTP. This is
not only because it has to work before HTTPS is first configured,
but also because it might need to work after a certificate is
accidentally allowed to lapse, to get that server back into a state
where it can speak HTTPS safely again.
So, from the second `service { }` block, we include this file to set up
| | < | < | < < < | < < < < | < < < < < < < < < < < < < < < < < | 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 |
challenge used by the Let’s Encrypt service only runs over HTTP. This is
not only because it has to work before HTTPS is first configured,
but also because it might need to work after a certificate is
accidentally allowed to lapse, to get that server back into a state
where it can speak HTTPS safely again.
So, from the second `service { }` block, we include this file to set up
the minimal HTTP service we require, `local/http-certbot-only`:
listen 80;
listen [::]:80;
# This is expressed as a rewrite rule instead of an "if" because
# http://wiki.nginx.org/IfIsEvil
#rewrite ^(/.well-known/acme-challenge/.*) $1 break;
# Force everything else to HTTPS with a permanent redirect.
#return 301 https://$host$request_uri;
As written above, this configuration does nothing other than to tell
nginx that it’s allowed to serve content via HTTP on port 80 as well.
We’ll uncomment the `rewrite` and `return` directives below, when we’re
ready to begin testing.
Notice that this configuration is very different from that in the
[HTTP-only nginx on Debian][nod] guide. Most of that guide’s nginx
directives moved up into the TLS `server { }` block, because we
eventually want this site to be as close to HTTPS-only as we can get it.
## Step 3: Dry Run
We want to first request a dry run, because Let’s Encrypt puts some
rather low limits on how often you’re allowed to request an actual
certificate. You want to be sure everything’s working before you do
|
| ︙ | ︙ | |||
539 540 541 542 543 544 545 | "Redirect to HTTPS on the Login page" setting to be enabled. Not only is it unnecessary with this HTTPS redirect at the front-end proxy level, it would actually [cause an infinite redirect loop if enabled](./ssl.wiki#rloop). | | | > | | | < < < < < < < < < < < | 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
"Redirect to HTTPS on the Login page" setting to be enabled. Not only
is it unnecessary with this HTTPS redirect at the front-end proxy level,
it would actually [cause an infinite redirect loop if
enabled](./ssl.wiki#rloop).
## Step 6: Re-Point Fossil at Your Repositories
As of Fossil 2.9, the permanent HTTP-to-HTTPS redirect we enabled above
causes Fossil to remember the new URL automatically the first time it’s
redirected to it. All you need to do to switch your syncs to HTTPS is:
$ cd ~/path/to/checkout
$ fossil sync
## Step 7: Renewing Automatically
Now that the configuration is solid, you can renew the LE cert with the
`certbot` command from above without the `--dry-run` flag plus a restart
of nginx:
|
| ︙ | ︙ | |||
586 587 588 589 590 591 592 | ----------- <a id=”evolution”></a> **Document Evolution** | | < < < < | < | < < < | 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 | ----------- <a id=”evolution”></a> **Document Evolution** Large parts of this article have been rewritten several times now due to shifting technology in the TLS and proxying spheres. There is no particularly good reason to expect that this sort of thing will not continue to happen, so we consider this to be a living document. If you do not have commit access on the `fossil-scm.org` repository to update this document as the world changes around it, you can discuss this document [on the forum][fd]. This document’s author keeps an eye on the forum and expects to keep this document updated with ideas that appear in that thread. [acme]: https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment [cb]: https://certbot.eff.org/ [cbnu]: https://certbot.eff.org/lets-encrypt/ubuntubionic-nginx [fd]: https://fossil-scm.org/forum/forumpost/ae6a4ee157 [hsts]: https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security [lja]: https://en.wikipedia.org/wiki/Logjam_(computer_security) [mitm]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack [nest]: https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/ [ocsp]: https://en.wikipedia.org/wiki/OCSP_stapling [qslc]: https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices [qslt]: https://www.ssllabs.com/ssltest/ |
1 2 3 4 5 6 7 8 | <title>Unversioned Content</title> <h1 align="center">Unversioned Content</h1> "Unversioned content" or "unversioned files" are files stored in a Fossil repository without history. Only the newest version of each unversioned file is retained. Though history is omitted, unversioned content is synced between | | | | | 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 | <title>Unversioned Content</title> <h1 align="center">Unversioned Content</h1> "Unversioned content" or "unversioned files" are files stored in a Fossil repository without history. Only the newest version of each unversioned file is retained. Though history is omitted, unversioned content is synced between repositories. In the event of a conflict during a sync, the most recent version of each unversioned file is retained and older versions are discarded. Unversioned files are useful for storing ephemeral content such as builds or frequently changing web pages. The [https://www.fossil-scm.org/fossil/uv/download.html|download] page of the self-hosting Fossil repository is stored as unversioned content, for example. <h2>Accessing Unversioned Files</h2> Unversioned files are <u>not</u> a part of a check-out. Unversioned files are intended to be accessible as web pages using URLs of the form: "http://domain/cgi-script/<b>uv</b>/<i>FILENAME</i>". In other words, the URI method "<b>uv</b>" (short for "unversioned") followed by the name of the unversioned file will retrieve the content of the file. The MIME type is inferred from the filename suffix. The content of unversioned files can also be retrieved using the [/help?cmd=unversioned|fossil unvers cat <i>FILENAME</i>] command. A list of all unversioned files on a server can be seen using the [/help?cmd=/uvlist|/uvlist] URL. ([/uvlist|example]). <h2>Syncing Unversioned Files</h2> Unversioned content is synced between repositories, though not by default. Special commands or command-line options are required. Unversioned content can be synced using the following commands: <blockquote><pre> fossil sync <b>-u</b> fossil clone <b>-u</b> <i>URL local-repo-name</i> fossil unversioned sync |
| ︙ | ︙ |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 |
<title>Links For Fossil Users:</title>
* [./permutedindex.html | Documentation index] with [/search?c=d | full text search].
* [./reviews.wiki | Testimonials] from satisfied Fossil users and
[./quotes.wiki | Quotes] about Fossil and other DVCSes.
* [./faq.wiki | Frequently Asked Questions]
* The [./concepts.wiki | concepts] behind Fossil.
[./whyusefossil.wiki#definitions | Another viewpoint].
* [./quickstart.wiki | Quick Start] guide to using Fossil.
* [./qandc.wiki | Questions & Criticisms] directed at Fossil.
* [./build.wiki | Compiling and Installing]
* Fossil supports [./embeddeddoc.wiki | embedded documentation]
that is versioned along with project source code.
* Fossil uses an [./fileformat.wiki | enduring file format] that is
designed to be readable, searchable, and extensible by people
not yet born.
* A tutorial on [./branching.wiki | branching], what it means and how
to do it using Fossil.
* The [./selfcheck.wiki | automatic self-check] mechanism
helps insure project integrity.
* Fossil contains a [./wikitheory.wiki | built-in wiki].
* An [./event.wiki | Event] is a special kind of wiki page associated
with a point in time rather than a name.
* [./settings.wiki | Settings] control the behaviour of Fossil.
* [./ssl.wiki | Use SSL] to encrypt communication with the server.
* The [https://fossil-scm.org/forum|Fossil forum] is, as of mid-2018,
the project's central communication channel. The
[https://www.mail-archive.com/fossil-users@lists.fossil-scm.org
| read-only mailing list archives] house discussions spanning Fossil's
first decade.
* [./stats.wiki | Performance statistics] taken from real-world projects
hosted on Fossil.
* How to [./shunning.wiki | delete content] from a Fossil repository.
* How Fossil does [./password.wiki | password management].
* On-line [/help | help].
* Documentation on the
[http://www.sqliteconcepts.org/THManual.pdf | TH1 scripting language],
used to customize [./custom_ticket.wiki | ticketing], and several other
subsystems, including [./customskin.md | theming].
* List of [./th1.md | TH1 commands provided by Fossil itself] that expose
its key functionality to TH1 scripts.
* List of [./th1-hooks.md | TH1 hooks exposed by Fossil] that enable
customization of commands and web pages.
* A free hosting server for Fossil repositories is available at
[http://chiselapp.com/].
* How to [./server/ | set up a server] for your repository.
* Customizing the [./custom_ticket.wiki | ticket system].
* Methods to [./checkin_names.wiki | identify a specific check-in].
* [./inout.wiki | Import and export] from and to Git.
* [./fossil-v-git.wiki | Fossil versus Git].
* [./fiveminutes.wiki | Up and running in 5 minutes as a single user]
(contributed by Gilles Ganault on 2013-01-08).
* [./antibot.wiki | How Fossil defends against abuse by spiders and bots].
|
1 2 3 4 5 6 | Web-Page Examples ================= Here are just a few examples of the many web pages supported by Fossil. Follow hyperlinks on the examples below to see many other examples. | < < < < < < < | < < | | | | | | | | | | | | | | | | | | | | | | | | | | | 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 |
Web-Page Examples
=================
Here are just a few examples of the many web pages supported
by Fossil. Follow hyperlinks on the examples below to see many
other examples.
* <a target='_blank' class='exbtn'
href='$ROOT/timeline?y=ci&n=100'>(Example)</a> →
100 most recent check-ins.
* <a target='_blank' class='exbtn'
href='$ROOT/finfo?name=src/file.c'>(Example)</a> →
All changes to the <b>src/file.c</b> source file.
* <a target='_blank' class='exbtn'
href='$ROOT/timeline?n=200&uf=0c3c2d086a'>(Example)</a> →
All check-ins using a particular version of the <b>src/file.c</b>
source file.
* <a target='_blank' class='exbtn'
href='$ROOT/timeline?n=11&y=ci&c=2014-01-01'>(Example)</a> →
Check-ins proximate to an historical point in time (2014-01-01).
* <a target='_blank' class='exbtn'
href='$ROOT/timeline?n=11&y=ci&c=2014-01-01&v=1'>(Example)</a> →
The previous example augmented with file changes.
* <a target='_blank' class='exbtn'
href='$ROOT/timeline?n=25&y=ci&a=1970-01-01'>(Example)</a> →
First 25 check-ins after 1970-01-01. (The first 25 check-ins of
the project.)
* <a target='_blank' class='exbtn'
href='$ROOT/timeline?n=200&r=svn-import'>(Example)</a> →
All check-ins of the "svn-import" branch together with check-ins
that merge with that branch.
* <a target='_blank' class='exbtn'
href='$ROOT/timeline?n=200&t=svn-import'>(Example)</a> →
All check-ins of the "svn-import" branch only.
* <a target='_blank' class='exbtn'
href='$ROOT/timeline?n=100&y=ci&ubg'>(Example)</a> →
100 most recent check-ins color coded by committer rather than by branch.
* <a target='_blank' class='exbtn'
href='$ROOT/timeline?from=version-1.27&to=version-1.28'>(Example)</a> →
All check-ins on the most direct path from
version-1.27 to version-1.28
* <a target='_blank' class='exbtn'
href='$ROOT/timeline?namechng'>(Example)</a> →
Show check-ins that contain file name changes
* <a target='_blank' class='exbtn'
href='$ROOT/timeline?u=drh&c=2014-01-08&y=ci'>(Example)</a> →
Show check-ins circa 2014-01-08 by user "drh".
* <a target='_blank' class='exbtn'
href='$ROOT/timeline?from=version-1.34&to=version-1.35&chng=src/timeline.c,src/doc.c'>(Example)</a> →
Show all check-ins between version-1.34 and version-1.35 that make
changes to either of the files src/timeline.c or src/doc.c.
<big><b>→</b></big> (Hint: In the pages above, click the graph nodes
for any two check-ins or files to see a diff.)
<big><b>←</b></big>
* <a target='_blank' class='exbtn'
href='$ROOT/search?s=interesting+pages'>(Example)</a> →
Full-text search for "interesting pages".
* <a target='_blank' class='exbtn'
href='$ROOT/tree?ci=daff9d20621&type=tree'>(Example)</a> →
All files for a particular check-in (daff9d20621480)
* <a target='_blank' class='exbtn'
href='$ROOT/tree?ci=trunk&type=tree&mtime=1'>(Example)</a> →
All files for the latest check-in on a branch (trunk) sorted by
last modification time.
* <a target='_blank' class='exbtn'
href='$ROOT/fileage?name=svn-import'>(Example)</a> →
Age of all files in the latest checking for branch "svn-import".
* <a target='_blank' class='exbtn'
href='$ROOT/brlist'>(Example)</a> →
Table of branches. (Click on column headers to sort.)
* <a target='_blank' class='exbtn'
href='$ROOT/stat'>(Example)</a> →
Overall repository status.
* <a target='_blank' class='exbtn'
href='$ROOT/reports?type=ci&view=byuser'>(Example)</a> →
Number of check-ins per committer.
* <a target='_blank' class='exbtn'
href='$ROOT/reports?view=byfile'>(Example)</a> →
Number of check-ins for each source file.
(Click on column headers to sort.)
* <a target='_blank' class='exbtn'
href='$ROOT/blame?checkin=5260fbf63287&filename=src/rss.c&limit=-1'>
(Example)</a> →
Most recent change to each line of a particular source file in a
particular check-in.
* <a target='_blank' class='exbtn'
href='$ROOT/taglist'>(Example)</a> →
List of tags on check-ins.
* <a target='_blank' class='exbtn'
href='$ROOT/bigbloblist'>(Example)</a> →
The largest objects in the repository.
* <a target='_blank' class='exbtn'
href='$ROOT/hash-collisions'>(Example)</a> →
Hash prefix collisions
* <a target='_blank' class='exbtn'
href='$ROOT/sitemap'>(Example)</a> →
The "sitemap" containing links to many other pages
|
| ︙ | ︙ | |||
50 51 52 53 54 55 56 | To start using the built-in Fossil web interface on an existing Fossil repository, simply type this: <b>fossil ui existing-repository.fossil</b> Substitute the name of your repository, of course. | | | 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | To start using the built-in Fossil web interface on an existing Fossil repository, simply type this: <b>fossil ui existing-repository.fossil</b> Substitute the name of your repository, of course. The "ui" command will start a web server running (it figures out an available TCP port to use on its own) and then automatically launches your web browser to point at that server. If you run the "ui" command from within an open check-out, you can omit the repository name: <b>fossil ui</b> The latter case is a very useful short-cut when you are working on a |
| ︙ | ︙ | |||
106 107 108 109 110 111 112 | your edits will automatically merge with those of your co-workers when your repository synchronizes. You can view summary reports of <b>branches</b> in the check-in graph by visiting the "Branches" link on the menu bar. From those pages you can follow hyperlinks to get additional details. These screens allow you to easily keep track of what is going | | | 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | your edits will automatically merge with those of your co-workers when your repository synchronizes. You can view summary reports of <b>branches</b> in the check-in graph by visiting the "Branches" link on the menu bar. From those pages you can follow hyperlinks to get additional details. These screens allow you to easily keep track of what is going on with separate sub-teams within your project team. The "Files" link on the menu allows you to browse through the <b>file hierarchy</b> of the project and to view complete changes histories on individual files, with hyperlinks to the check-ins that made those changes, and with diffs and annotated diffs between versions. The web interface supports [./embeddeddoc.wiki | embedded documentation]. |
| ︙ | ︙ |
| ︙ | ︙ | |||
197 198 199 200 201 202 203 |
</ul>
<li><p>Two users (or the same user working in different check-outs)
might commit different changes against the same check-in. This
results in one parent node having two or more children.
<li><p>Command: <b>merge</b> →
combines the work of multiple check-ins into
a single check-out. That check-out can then be committed to create
| | | > | > > | 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 |
</ul>
<li><p>Two users (or the same user working in different check-outs)
might commit different changes against the same check-in. This
results in one parent node having two or more children.
<li><p>Command: <b>merge</b> →
combines the work of multiple check-ins into
a single check-out. That check-out can then be committed to create
a new check-in that has two (or more) parents.
<ul>
<li><p>Most check-ins have just one parent, and either zero or
one child.
<li><p>When a check-in has two or more parents, one of those parents
is the "primary parent". All the other parent nodes are "secondary"
or "merge" parents.
Conceptually, the primary parent shows the main line of
development. Content from the merge parents is added
into the main line.
<li><p>The "direct children" of a check-in X are all children that
have X as their primary parent.
<li><p>A check-in node with no direct children is sometimes called
a "leaf".
<li><p>The "merge" command changes only the check-out.
The "commit" command must be run subsequently to make the merge
a permanent part of project.
</ul>
<li><p>Definition: <b>branch</b> →
a sequence of check-ins that are all linked
together in the DAG through the primary parent.
<ul>
<li><p>Branches are often given names which propagate to direct children.
The tradition in Fossil is to call the main branch "trunk". In
Git, the main branch is usually called "master".
<li><p>It is possible to have multiple branches with the same name.
Fossil has no problem with this, but it can be confusing to
humans, so best practice is to give each branch a unique name.
<li><p>The name of a branch can be changed by adding special tags
to the first check-in of a branch. The name assigned by this
special tag automatically propagates to all direct children.
</ul>
|
| ︙ | ︙ |
| ︙ | ︙ | |||
25 26 27 28 29 30 31 | <h2>Stand-alone Wiki Pages</h2> Each wiki page has its own revision history which is independent of the sequence of check-ins (check-ins). Wiki pages can branch and merge just like check-ins, though as of this writing (2008-07-29) there is no mechanism in the user interface to support branching and merging. The current implementation of the wiki shows the version of the wiki | | | 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <h2>Stand-alone Wiki Pages</h2> Each wiki page has its own revision history which is independent of the sequence of check-ins (check-ins). Wiki pages can branch and merge just like check-ins, though as of this writing (2008-07-29) there is no mechanism in the user interface to support branching and merging. The current implementation of the wiki shows the version of the wiki page that has the most recent time stamp. In other words, if two users make unrelated changes to the same wiki page on separate repositories and those repositories are synced, the wiki page will fork. The web interface will display whichever edit was checked in last. The other edit can be found in the history. The file format will support merging the branches back together, but there is no mechanism in the user interface (yet) to perform the merge. |
| ︙ | ︙ | |||
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | <h2>Bug-reports and check-in comments and Forum messages</h2> The comments on check-ins and the text in the descriptions of bug reports both use wiki formatting. Exactly the same set of formatting rules apply. There is never a need to learn one formatting language for documentation and a different markup for bugs or for check-in comments. <h2>Auxiliary notes attached to check-ins or branches</h2> Stand-alone wiki pages with special names "branch/<i>BRANCHNAME</i>" or "checkin/<i>HASH</i>" are associated with the corresponding branch or check-in. The wiki text appears in an "About" section of timelines and info screens. Examples: * [/timeline?r=graph-test-branch] shows the text of the | > | | | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
<h2>Bug-reports and check-in comments and Forum messages</h2>
The comments on check-ins and the text in the descriptions of bug reports
both use wiki formatting. Exactly the same set of formatting rules apply.
There is never a need to learn one formatting language for documentation
and a different markup for bugs or for check-in comments.
<a name="assocwiki"></a>
<h2>Auxiliary notes attached to check-ins or branches</h2>
Stand-alone wiki pages with special names "branch/<i>BRANCHNAME</i>"
or "checkin/<i>HASH</i>" are associated with the corresponding
branch or check-in. The wiki text appears in an "About" section of
timelines and info screens. Examples:
* [/timeline?r=graph-test-branch] shows the text of the
[/wiki?name=branch/graph-test-branch&p|branch/graph-test-branch]
wiki page at the top of the timeline
* [/info/19c60b7fc9e2] shows the text of the
[/wiki?name=checkin/19c60b7fc9e2400e56a6f938bbad0e34ca746ca2eabdecac10945539f1f5e8c6&p|checkin/19c60b7fc9e2...]
wiki page in the "About" section.
This special wiki pages are very useful for recording historical
notes.
|