Index: BUILD.txt
==================================================================
--- BUILD.txt
+++ BUILD.txt
@@ -1,42 +1,60 @@
-All of the source code for fossil is contained in the src/ subdirectory.
-But there is a lot of generated code, so you will probably want to
-use the Makefile. To do a complete build, just type:
-
- make
-
-That should work out-of-the-box on Macs and Linux systems. If you are
-building on a Windows box, install MinGW as well as MinGW's make (or
-MSYS). You can then type:
-
- make -f Makefile.w32
+To do a complete build, just type:
+
+ ./configure; make
+
+The ./configure script builds Makefile from Makefile.in based on
+your system and any options you select (run "./configure --help"
+for a listing of the available options.)
+
+If you wish to use the original Makefile with no configuration, you can
+instead use:
+
+ make -f Makefile.classic
+
+On a windows box, use one of the Makefiles in the win/ subdirectory,
+according to your compiler and environment. If you have GCC and MSYS
+installed on your system, then consider:
+
+ make -f win/Makefile.mingw
+
+If you have VC++ installed on your system, then consider:
+
+ cd win; nmake /f Makefile.msc
If you have trouble, or you want to do something fancy, just look at
-top level makefile. There are 6 configuration options that are all well
-commented. Instead of editing the Makefile, consider copying the Makefile
-to an alternative name such as "GNUMakefile", "BSDMakefile", or "makefile"
-and editing the copy.
+Makefile.classic. There are 6 configuration options that are all well
+commented. Instead of editing the Makefile.classic, consider copying
+Makefile.classic to an alternative name such as "GNUMakefile",
+"BSDMakefile", or "makefile" and editing the copy.
+
-Out of source builds?
---------------------------------------------------------------------------
+BUILDING OUTSIDE THE SOURCE TREE
An out of source build is pretty easy:
- 1. Make a new directory to do the builds in.
- 2. Copy "Makefile" from the source into the build directory and
- modify the SRCDIR macro along the lines of:
+ 1. Make and change to a new directory to do the builds in.
+ 2. Run the "configure" script from this directory.
+ 3. Type: "make"
+
+For example:
- SRCDIR=../src
-
- 3. type: "make"
+ mkdir build
+ cd build
+ ../configure
+ make
This will now keep all generates files seperate from the maintained
source code.
--------------------------------------------------------------------------
Here are some notes on what is happening behind the scenes:
+
+* The configure script (if used) examines the options given
+ and runs various tests with the C compiler to create Makefile
+ from the Makefile.in template as well as autoconfig.h
* The Makefile just sets up a few macros and then invokes the
real makefile in src/main.mk. The src/main.mk makefile is
automatically generated by a TCL script found at src/makemake.tcl.
Do not edit src/main.mk directly. Update src/makemake.tcl and
@@ -52,5 +70,8 @@
A header comment in src/translate.c explains in detail what it does.
* The src/mkindex.c program generates some C code that implements
static lookup tables. See the header comment in the source code
for details on what it does.
+
+Additional information on the build process is available from
+http://www.fossil-scm.org/fossil/doc/trunk/www/makefile.wiki
DELETED Makefile
Index: Makefile
==================================================================
--- Makefile
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/make
-#
-#### The toplevel directory of the source tree. Fossil can be built
-# in a directory that is separate from the source tree. Just change
-# the following to point from the build directory to the src/ folder.
-#
-SRCDIR = ./src
-
-#### The directory into which object code files should be written.
-#
-#
-OBJDIR = ./obj
-
-#### C Compiler and options for use in building executables that
-# will run on the platform that is doing the build. This is used
-# to compile code-generator programs as part of the build process.
-# See TCC below for the C compiler for building the finished binary.
-#
-BCC = gcc -g -O2
-
-#### The suffix to add to executable files. ".exe" for windows.
-# Nothing for unix.
-#
-E =
-
-#### C Compile and options for use in building executables that
-# will run on the target platform. This is usually the same
-# as BCC, unless you are cross-compiling. This C compiler builds
-# the finished binary for fossil. The BCC compiler above is used
-# for building intermediate code-generator tools.
-#
-#TCC = gcc -O6
-#TCC = gcc -g -O0 -Wall -fprofile-arcs -ftest-coverage
-TCC = gcc -g -Os -Wall
-
-#### Extra arguments for linking the finished binary. Fossil needs
-# to link against the Z-Lib compression library. There are no
-# other dependencies. We sometimes add the -static option here
-# so that we can build a static executable that will run in a
-# chroot jail.
-#
-LIB = -lz $(LDFLAGS)
-# If you're on OpenSolaris:
-# LIB += lsocket
-# Solaris 10 needs:
-# LIB += -lsocket -lnsl
-# My assumption is that the Sol10 flags will work for Sol8/9 and possibly 11.
-#
-
-#### Tcl shell for use in running the fossil testsuite.
-#
-TCLSH = tclsh
-
-# You should not need to change anything below this line
-###############################################################################
-include $(SRCDIR)/main.mk
ADDED Makefile.classic
Index: Makefile.classic
==================================================================
--- /dev/null
+++ Makefile.classic
@@ -0,0 +1,75 @@
+#!/usr/bin/make
+#
+# This is the top-level makefile for Fossil when the build is occurring
+# on a unix platform. This works out-of-the-box on most unix platforms.
+# But you are free to vary some of the definitions if desired.
+#
+#### The toplevel directory of the source tree. Fossil can be built
+# in a directory that is separate from the source tree. Just change
+# the following to point from the build directory to the src/ folder.
+#
+SRCDIR = ./src
+
+#### The directory into which object code files should be written.
+#
+#
+OBJDIR = ./bld
+
+#### C Compiler and options for use in building executables that
+# will run on the platform that is doing the build. This is used
+# to compile code-generator programs as part of the build process.
+# See TCC below for the C compiler for building the finished binary.
+#
+BCC = gcc
+
+#### The suffix to add to final executable file. When cross-compiling
+# to windows, make this ".exe". Otherwise leave it blank.
+#
+E =
+
+#### C Compile and options for use in building executables that
+# will run on the target platform. This is usually the same
+# as BCC, unless you are cross-compiling. This C compiler builds
+# the finished binary for fossil. The BCC compiler above is used
+# for building intermediate code-generator tools.
+#
+#TCC = gcc -O6
+#TCC = gcc -g -O0 -Wall -fprofile-arcs -ftest-coverage
+TCC = gcc -g -Os -Wall
+
+# To add support for HTTPS
+TCC += -DFOSSIL_ENABLE_SSL
+
+#### Extra arguments for linking the finished binary. Fossil needs
+# to link against the Z-Lib compression library. There are no
+# other dependencies. We sometimes add the -static option here
+# so that we can build a static executable that will run in a
+# chroot jail.
+#
+LIB = -lz $(LDFLAGS)
+
+# If using HTTPS:
+LIB += -lcrypto -lssl
+
+#### Tcl shell for use in running the fossil testsuite. If you do not
+# care about testing the end result, this can be blank.
+#
+TCLSH = tclsh
+
+# You should not need to change anything below this line
+###############################################################################
+#
+# Automatic platform-specific options.
+HOST_OS_CMD = uname -s
+HOST_OS = $(HOST_OS_CMD:sh)
+
+LIB.SunOS= -lsocket -lnsl
+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
ADDED Makefile.in
Index: Makefile.in
==================================================================
--- /dev/null
+++ Makefile.in
@@ -0,0 +1,45 @@
+#!/usr/bin/make
+#
+# This is the top-level makefile for Fossil when the build is occurring
+# on a unix platform. This works out-of-the-box on most unix platforms.
+# But you are free to vary some of the definitions if desired.
+#
+#### The toplevel directory of the source tree. Fossil can be built
+# in a directory that is separate from the source tree. Just change
+# the following to point from the build directory to the src/ folder.
+#
+SRCDIR = @srcdir@/src
+
+#### The directory into which object code files should be written.
+#
+#
+OBJDIR = ./bld
+
+#### C Compiler and options for use in building executables that
+# will run on the platform that is doing the build. This is used
+# to compile code-generator programs as part of the build process.
+# See TCC below for the C compiler for building the finished binary.
+#
+BCC = @CC_FOR_BUILD@
+
+#### The suffix to add to final executable file. When cross-compiling
+# to windows, make this ".exe". Otherwise leave it blank.
+#
+E = @EXEEXT@
+
+TCC = @CC@
+
+#### Tcl shell for use in running the fossil testsuite. If you do not
+# care about testing the end result, this can be blank.
+#
+TCLSH = tclsh
+
+LIB = @LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@
+TCC += @EXTRA_CFLAGS@ @CPPFLAGS@ @CFLAGS@ -DHAVE_AUTOCONFIG_H
+INSTALLDIR = $(DESTDIR)@prefix@/bin
+USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@
+
+include $(SRCDIR)/main.mk
+
+distclean: clean
+ rm -f autoconfig.h config.log Makefile
DELETED Makefile.w32
Index: Makefile.w32
==================================================================
--- Makefile.w32
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/usr/bin/make
-#
-#### The toplevel directory of the source tree. Fossil can be built
-# in a directory that is separate from the source tree. Just change
-# the following to point from the build directory to the src/ folder.
-#
-SRCDIR = ./src
-OBJDIR = ./wobj
-
-#### C Compiler and options for use in building executables that
-# will run on the platform that is doing the build. This is used
-# to compile code-generator programs as part of the build process.
-# See TCC below for the C compiler for building the finished binary.
-#
-BCC = gcc -g -O2
-
-#### The suffix to add to executable files. ".exe" for windows.
-# Nothing for unix.
-#
-E = .exe
-
-#### Enable HTTPS support via OpenSSL (links to libssl and libcrypto)
-#
-# FOSSIL_ENABLE_SSL=1
-
-#### C Compile and options for use in building executables that
-# will run on the target platform. This is usually the same
-# as BCC, unless you are cross-compiling. This C compiler builds
-# the finished binary for fossil. The BCC compiler above is used
-# for building intermediate code-generator tools.
-#
-#TCC = gcc -O6
-#TCC = gcc -g -O0 -Wall -fprofile-arcs -ftest-coverage
-#TCC = gcc -g -Os -Wall
-#TCC = gcc -g -Os -Wall -DFOSSIL_I18N=0 -L/usr/local/lib -I/usr/local/include
-TCC = gcc -Os -Wall -DFOSSIL_I18N=0 -L/mingw/lib -I/mingw/include
-
-# With HTTPS support
-ifdef FOSSIL_ENABLE_SSL
-TCC += -DFOSSIL_ENABLE_SSL=1
-endif
-
-#### Extra arguments for linking the finished binary. Fossil needs
-# to link against the Z-Lib compression library. There are no
-# other dependencies. We sometimes add the -static option here
-# so that we can build a static executable that will run in a
-# chroot jail.
-#
-#LIB = -lz
-#LIB = -lz -lws2_32
-LIB = -lmingwex -lz -lws2_32
-# OpenSSL:
-ifdef FOSSIL_ENABLE_SSL
-LIB += -lcrypto -lssl
-endif
-
-#### Tcl shell for use in running the fossil testsuite.
-#
-TCLSH = tclsh
-
-#### Include a configuration file that can override any one of these settings.
-#
--include config.w32
-
-# You should not need to change anything below this line
-###############################################################################
-include $(SRCDIR)/main.mk
ADDED VERSION
Index: VERSION
==================================================================
--- /dev/null
+++ VERSION
@@ -0,0 +1,1 @@
+1.20
ADDED auto.def
Index: auto.def
==================================================================
--- /dev/null
+++ auto.def
@@ -0,0 +1,179 @@
+# System autoconfiguration. Try: ./configure --help
+
+use cc cc-lib
+
+options {
+ with-openssl:path|auto|none
+ => {Look for openssl in the given path, or auto or none}
+ with-zlib:path => {Look for zlib in the given path}
+ internal-sqlite=1 => {Don't use the internal sqlite, use the system one}
+ static=0 => {Link a static executable}
+ lineedit=1 => {Disable line editing}
+ fossil-debug=0 => {Build with fossil debugging enabled}
+}
+
+# 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. Can't yet use jimsh for this.
+cc-check-progs tclsh
+
+define EXTRA_CFLAGS ""
+define EXTRA_LDFLAGS ""
+define USE_SYSTEM_SQLITE ""
+
+if {![opt-bool internal-sqlite]} {
+ proc find_internal_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_wal_checkpoint() can be found as well. If we can find
+ # open() but not wal_checkpoint(), then the system SQLite is too old
+ # to link against fossil.
+ #
+ if {[cc-check-function-in-lib sqlite3_open sqlite3 $extralibs]} {
+ if {![cc-check-function-in-lib sqlite3_wal_checkpoint sqlite3 $extralibs]} {
+ user-error "system sqlite3 too old (require >= 3.7.0)"
+ }
+
+ # Success. Update symbols and return.
+ #
+ define USE_SYSTEM_SQLITE 1
+ define-append LIBS $extralibs
+ return
+ }
+ }
+ user-error "system sqlite3 not found"
+ }
+
+ find_internal_sqlite
+}
+
+if {[opt-bool fossil-debug]} {
+ define-append EXTRA_CFLAGS -DFOSSIL_DEBUG
+}
+
+if {[opt-bool static]} {
+ # XXX: This will not work on all systems.
+ define-append EXTRA_LDFLAGS -static
+}
+
+
+# Check for zlib, using the given location if specified
+set zlibpath [opt-val with-zlib]
+if {$zlibpath ne ""} {
+ cc-with [list -cflags "-I$zlibpath -L$zlibpath"]
+ define-append EXTRA_CFLAGS -I$zlibpath
+ define-append EXTRA_LDFLAGS -L$zlibpath
+}
+if {![cc-check-includes zlib.h] || ![cc-check-function-in-lib inflateEnd z]} {
+ user-error "zlib not found please install it or specify the location with --with-zlib"
+}
+
+# Helper for openssl checking
+proc check-for-openssl {msg {cflags {}}} {
+ msg-checking "Checking for $msg..."
+ set rc 0
+ msg-quiet cc-with [list -cflags $cflags -libs {-lssl -lcrypto}] {
+ if {[cc-check-includes openssl/ssl.h] && [cc-check-functions SSL_new]} {
+ incr rc
+ }
+ }
+ if {$rc} {
+ msg-result "ok"
+ return 1
+ } else {
+ msg-result "no"
+ return 0
+ }
+}
+
+set ssldirs [opt-val with-openssl]
+if {$ssldirs ne "none"} {
+ set found 0
+ 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"]
+ } msg
+ if {!$found} {
+ set ssldirs "{} /usr/sfw /usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr"
+ }
+ }
+ if {!$found} {
+ foreach dir $ssldirs {
+ if {$dir eq ""} {
+ set msg "system ssl"
+ set cflags ""
+ set ldflags ""
+ } else {
+ set msg "ssl in $dir"
+ set cflags "-I$dir/include"
+ set ldflags "-L$dir/include"
+ }
+ if {[check-for-openssl $msg "$cflags $ldflags"]} {
+ incr found
+ break
+ }
+ }
+ }
+ if {$found} {
+ define FOSSIL_ENABLE_SSL
+ define-append EXTRA_CFLAGS $cflags
+ define-append EXTRA_LDFLAGS $ldflags
+ define-append LIBS -lssl -lcrypto
+ msg-result "HTTP support enabled"
+
+ # Silence OpenSSL deprecation warnings on Mac OS X 10.7.
+ if {[string match *-darwin* [get-define host]]} {
+ if {[cctest -cflags {-Wdeprecated-declarations}]} {
+ define-append EXTRA_CFLAGS -Wdeprecated-declarations
+ }
+ }
+ } else {
+ user-error "OpenSSL not found. Consider --with-openssl=none to disable HTTPS support"
+ }
+}
+
+if {[opt-bool lineedit]} {
+ # Need readline-compatible line editing
+ cc-with {-includes stdio.h} {
+ if {[cc-check-includes readline/readline.h] && [cc-check-function-in-lib readline readline]} {
+ msg-result "Using readline for line editing"
+ } elseif {[cc-check-includes editline/readline.h] && [cc-check-function-in-lib readline edit]} {
+ define-feature editline
+ msg-result "Using editline for line editing"
+ }
+ }
+}
+
+# Network functions require libraries on some systems
+cc-check-function-in-lib gethostbyname nsl
+if {![cc-check-function-in-lib socket {socket network}]} {
+ # Last resort, may be Windows
+ if {[string match *mingw* [get-define host]]} {
+ define-append LIBS -lwsock32
+ }
+}
+
+# Check for getpassphrase() for Solaris 10 where getpass() truncates to 10 chars
+if {![cc-check-functions getpassphrase]} {
+ # Haiku needs this
+ cc-check-function-in-lib getpass bsd
+}
+
+make-template Makefile.in
+make-config-header autoconfig.h -auto {USE_* FOSSIL_*}
ADDED autosetup/LICENSE
Index: autosetup/LICENSE
==================================================================
--- /dev/null
+++ autosetup/LICENSE
@@ -0,0 +1,35 @@
+Unless explicitly stated, all files which form part of autosetup
+are released under the following license:
+
+---------------------------------------------------------------------
+autosetup - A build environment "autoconfigurator"
+
+Copyright (c) 2010-2011, WorkWare Systems
+ @
|
Redirect to %h
\n\n", zURL); + cgi_printf("\nRedirect to %h
\n\n", zLocation); cgi_set_status(302, "Moved Temporarily"); free(zLocation); cgi_reply(); - exit(0); + fossil_exit(0); } -void cgi_redirectf(const char *zFormat, ...){ +NORETURN void cgi_redirectf(const char *zFormat, ...){ va_list ap; va_start(ap, zFormat); cgi_redirect(vmprintf(zFormat, ap)); va_end(ap); } @@ -383,12 +413,11 @@ ** deallocated after this routine returns. */ void cgi_set_parameter_nocopy(const char *zName, const char *zValue){ if( nAllocQP<=nUsedQP ){ nAllocQP = nAllocQP*2 + 10; - aParamQP = realloc( aParamQP, nAllocQP*sizeof(aParamQP[0]) ); - if( aParamQP==0 ) exit(1); + aParamQP = fossil_realloc( aParamQP, nAllocQP*sizeof(aParamQP[0]) ); } aParamQP[nUsedQP].zName = zName; aParamQP[nUsedQP].zValue = zValue; if( g.fHttpTrace ){ fprintf(stderr, "# cgi: %s = [%s]\n", zName, zValue); @@ -413,11 +442,11 @@ ** Replace a parameter with a new value. */ void cgi_replace_parameter(const char *zName, const char *zValue){ int i; for(i=0; i%h
%s
", z, zRebuildMsg); cgi_reply(); }else{ - fprintf(stderr, "%s: %s\n\n%s", g.argv[0], z, zRebuildMsg); + fprintf(stderr, "%s: %s\n\n%s", fossil_nameofexe(), z, zRebuildMsg); } db_force_rollback(); - exit(1); + fossil_exit(1); } static int nBegin = 0; /* Nesting depth of BEGIN */ -static int isNewRepo = 0; /* True if the repository is newly created */ static int doRollback = 0; /* True to force a rollback */ static int nCommitHook = 0; /* Number of commit hooks */ static struct sCommitHook { int (*xHook)(void); /* Functions to call at db_end_transaction() */ int sequence; /* Call functions in sequence order */ } aHook[5]; static Stmt *pAllStmt = 0; /* List of all unfinalized statements */ +static int nPrepare = 0; /* Number of calls to sqlite3_prepare() */ +static int nDeleteOnFail = 0; /* Number of entries in azDeleteOnFail[] */ +static char *azDeleteOnFail[3]; /* Files to delete on a failure */ + + +/* +** Arrange for the given file to be deleted on a failure. +*/ +void db_delete_on_failure(const char *zFilename){ + assert( nDeleteOnFail| Old title | +** | New title | ||
|---|---|---|---|---|
cannot compute + @ difference between binary files
+ return 0; + } + + collim = collim < 4 ? 0 : collim; + + /* Compute the difference */ + diff_all(&c); + + linebuf = fossil_malloc(LENGTH_MASK+1); + if( !linebuf ){ + free(c.aFrom); + free(c.aTo); + free(c.aEdit); + return 0; + } + + iFrom=iTo=0; + i=0; + while( ifor(i=0; i/* -** Shell-escape the given string. Append the result to a blob. -*/ -static void shell_escape(Blob *pBlob, const char *zIn){ - int n = blob_size(pBlob); - int k = strlen(zIn); - int i, c; - char *z; - for(i=0; (c = zIn[i])!=0; i++){ - if( isspace(c) || c=='"' || (c=='\\' && zIn[i+1]!=0) ){ - blob_appendf(pBlob, "\"%s\"", zIn); - z = blob_buffer(pBlob); - for(i=n+1; i<=n+k; i++){ - if( z[i]=='"' ) z[i] = '_'; - } - return; - } - } - blob_append(pBlob, zIn, -1); +** Diff option flags +*/ +#define DIFF_NEWFILE 0x01 /* Treat non-existing fails as empty files */ +#define DIFF_NOEOLWS 0x02 /* Ignore whitespace at the end of lines */ + +/* +** Output the results of a diff. Output goes to stdout for command-line +** or to the CGI/HTTP result buffer for web pages. +*/ +static void diff_printf(const char *zFormat, ...){ + va_list ap; + va_start(ap, zFormat); + if( g.cgiOutput ){ + cgi_vprintf(zFormat, ap); + }else{ + vprintf(zFormat, ap); + } + va_end(ap); } /* -** This function implements a cross-platform "system()" interface. -*/ -int portable_system(const char *zOrigCmd){ - int rc; -#ifdef __MINGW32__ - /* On windows, we have to put double-quotes around the entire command. - ** Who knows why - this is just the way windows works. - */ - char *zNewCmd = mprintf("\"%s\"", zOrigCmd); - rc = system(zNewCmd); - free(zNewCmd); -#else - /* On unix, evaluate the command directly. - */ - rc = system(zOrigCmd); -#endif - return rc; +** Print the "Index:" message that patch wants to see at the top of a diff. +*/ +void diff_print_index(const char *zFile){ + diff_printf("Index: %s\n=======================================" + "============================\n", zFile); } /* ** Show the difference between two files, one in memory and one on disk. ** @@ -69,29 +57,42 @@ ** zFile2. The content of pFile1 is in memory. zFile2 exists on disk. ** ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the ** command zDiffCmd to do the diffing. */ -static void diff_file( +void diff_file( Blob *pFile1, /* In memory content to compare from */ const char *zFile2, /* On disk content to compare to */ const char *zName, /* Display name of the file */ - const char *zDiffCmd /* Command for comparison */ + const char *zDiffCmd, /* Command for comparison */ + int ignoreEolWs /* Ignore whitespace at end of line */ ){ if( zDiffCmd==0 ){ - Blob out; /* Diff output text */ - Blob file2; /* Content of zFile2 */ + Blob out; /* Diff output text */ + Blob file2; /* Content of zFile2 */ + const char *zName2; /* Name of zFile2 for display */ /* Read content of zFile2 into memory */ blob_zero(&file2); - blob_read_from_file(&file2, zFile2); + if( file_wd_size(zFile2)<0 ){ + zName2 = "/dev/null"; + }else{ + if( file_wd_islink(zFile2) ){ + blob_read_link(&file2, zFile2); + }else{ + blob_read_from_file(&file2, zFile2); + } + zName2 = zName; + } /* Compute and output the differences */ blob_zero(&out); - text_diff(pFile1, &file2, &out, 5); - printf("--- %s\n+++ %s\n", zName, zName); - printf("%s\n", blob_str(&out)); + text_diff(pFile1, &file2, &out, 5, ignoreEolWs); + if( blob_size(&out) ){ + diff_printf("--- %s\n+++ %s\n", zName, zName2); + diff_printf("%s\n", blob_str(&out)); + } /* Release memory resources */ blob_reset(&file2); blob_reset(&out); }else{ @@ -103,11 +104,11 @@ ** zFile2 */ blob_zero(&nameFile1); do{ blob_reset(&nameFile1); blob_appendf(&nameFile1, "%s~%d", zFile2, cnt++); - }while( access(blob_str(&nameFile1),0)==0 ); + }while( file_access(blob_str(&nameFile1),0)==0 ); blob_write_to_file(pFile1, blob_str(&nameFile1)); /* Construct the external diff command */ blob_zero(&cmd); blob_appendf(&cmd, "%s ", zDiffCmd); @@ -114,14 +115,14 @@ shell_escape(&cmd, blob_str(&nameFile1)); blob_append(&cmd, " ", 1); shell_escape(&cmd, zFile2); /* Run the external diff command */ - portable_system(blob_str(&cmd)); + fossil_system(blob_str(&cmd)); /* Delete the temporary file and clean up memory used */ - unlink(blob_str(&nameFile1)); + file_delete(blob_str(&nameFile1)); blob_reset(&nameFile1); blob_reset(&cmd); } } @@ -132,23 +133,24 @@ ** pFile2. ** ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the ** command zDiffCmd to do the diffing. */ -static void diff_file_mem( +void diff_file_mem( Blob *pFile1, /* In memory content to compare from */ Blob *pFile2, /* In memory content to compare to */ const char *zName, /* Display name of the file */ - const char *zDiffCmd /* Command for comparison */ + const char *zDiffCmd, /* Command for comparison */ + int ignoreEolWs /* Ignore whitespace at end of lines */ ){ if( zDiffCmd==0 ){ Blob out; /* Diff output text */ blob_zero(&out); - text_diff(pFile1, pFile2, &out, 5); - printf("--- %s\n+++ %s\n", zName, zName); - printf("%s\n", blob_str(&out)); + text_diff(pFile1, pFile2, &out, 5, ignoreEolWs); + diff_printf("--- %s\n+++ %s\n", zName, zName); + diff_printf("%s\n", blob_str(&out)); /* Release memory resources */ blob_reset(&out); }else{ Blob cmd; @@ -167,76 +169,94 @@ shell_escape(&cmd, zTemp1); blob_append(&cmd, " ", 1); shell_escape(&cmd, zTemp2); /* Run the external diff command */ - portable_system(blob_str(&cmd)); + fossil_system(blob_str(&cmd)); /* Delete the temporary file and clean up memory used */ - unlink(zTemp1); - unlink(zTemp2); + file_delete(zTemp1); + file_delete(zTemp2); blob_reset(&cmd); } } /* -** Do a diff against a single file named in g.argv[2] from version zFrom +** Do a diff against a single file named in zFileTreeName from version zFrom ** against the same file on disk. */ -static void diff_one_against_disk(const char *zFrom, const char *zDiffCmd){ +static void diff_one_against_disk( + const char *zFrom, /* Name of file */ + const char *zDiffCmd, /* Use this "diff" command */ + int ignoreEolWs, /* Ignore whitespace changes at end of lines */ + const char *zFileTreeName +){ Blob fname; Blob content; - file_tree_name(g.argv[2], &fname, 1); - historical_version_of_file(zFrom, blob_str(&fname), &content, 0); - diff_file(&content, g.argv[2], g.argv[2], zDiffCmd); + int isLink; + file_tree_name(zFileTreeName, &fname, 1); + historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0, 0); + if( !isLink != !file_wd_islink(zFrom) ){ + diff_printf("cannot compute difference between symlink and regular file\n"); + }else{ + diff_file(&content, zFileTreeName, zFileTreeName, zDiffCmd, ignoreEolWs); + } blob_reset(&content); blob_reset(&fname); } /* ** Run a diff between the version zFrom and files on disk. zFrom might ** be NULL which means to simply show the difference between the edited ** files on disk and the check-out on which they are based. */ -static void diff_all_against_disk(const char *zFrom, const char *zDiffCmd){ +static void diff_all_against_disk( + const char *zFrom, /* Version to difference from */ + const char *zDiffCmd, /* Use this diff command. NULL for built-in */ + int diffFlags /* Flags controlling diff output */ +){ int vid; Blob sql; Stmt q; + int ignoreEolWs; /* Ignore end-of-line whitespace */ + int asNewFile; /* Treat non-existant files as empty files */ + ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0; + asNewFile = (diffFlags & DIFF_NEWFILE)!=0; vid = db_lget_int("checkout", 0); - vfile_check_signature(vid, 1); + vfile_check_signature(vid, 1, 0); blob_zero(&sql); db_begin_transaction(); if( zFrom ){ - int rid = name_to_rid(zFrom); + int rid = name_to_typed_rid(zFrom, "ci"); if( !is_a_version(rid) ){ fossil_fatal("no such check-in: %s", zFrom); } load_vfile_from_rid(rid); blob_appendf(&sql, - "SELECT v2.pathname, v2.deleted, v2.chnged, v2.rid==0, v1.rid" + "SELECT v2.pathname, v2.deleted, v2.chnged, v2.rid==0, v1.rid, v1.islink" " FROM vfile v1, vfile v2 " " WHERE v1.pathname=v2.pathname AND v1.vid=%d AND v2.vid=%d" - " AND (v2.deleted OR v2.chnged OR v1.rid!=v2.rid)" + " AND (v2.deleted OR v2.chnged OR v1.mrid!=v2.rid)" "UNION " - "SELECT pathname, 1, 0, 0, 0" + "SELECT pathname, 1, 0, 0, 0, islink" " FROM vfile v1" " WHERE v1.vid=%d" " AND NOT EXISTS(SELECT 1 FROM vfile v2" " WHERE v2.vid=%d AND v2.pathname=v1.pathname)" "UNION " - "SELECT pathname, 0, 0, 1, 0" + "SELECT pathname, 0, 0, 1, 0, islink" " FROM vfile v2" " WHERE v2.vid=%d" " AND NOT EXISTS(SELECT 1 FROM vfile v1" " WHERE v1.vid=%d AND v1.pathname=v2.pathname)" " ORDER BY 1", rid, vid, rid, vid, vid, rid ); }else{ blob_appendf(&sql, - "SELECT pathname, deleted, chnged , rid==0, rid" + "SELECT pathname, deleted, chnged , rid==0, rid, islink" " FROM vfile" " WHERE vid=%d" " AND (deleted OR chnged OR rid==0)" " ORDER BY pathname", vid @@ -246,81 +266,181 @@ while( db_step(&q)==SQLITE_ROW ){ const char *zPathname = db_column_text(&q,0); int isDeleted = db_column_int(&q, 1); int isChnged = db_column_int(&q,2); int isNew = db_column_int(&q,3); + int srcid = db_column_int(&q, 4); + int isLink = db_column_int(&q, 5); char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname); + char *zToFree = zFullName; + int showDiff = 1; if( isDeleted ){ - printf("DELETED %s\n", zPathname); - }else if( access(zFullName, 0) ){ - printf("MISSING %s\n", zPathname); + diff_printf("DELETED %s\n", zPathname); + if( !asNewFile ){ showDiff = 0; zFullName = "/dev/null"; } + }else if( file_access(zFullName, 0) ){ + diff_printf("MISSING %s\n", zPathname); + if( !asNewFile ){ showDiff = 0; } }else if( isNew ){ - printf("ADDED %s\n", zPathname); - }else if( isDeleted ){ - printf("DELETED %s\n", zPathname); + diff_printf("ADDED %s\n", zPathname); + srcid = 0; + if( !asNewFile ){ showDiff = 0; } }else if( isChnged==3 ){ - printf("ADDED_BY_MERGE %s\n", zPathname); - }else{ - int srcid = db_column_int(&q, 4); + diff_printf("ADDED_BY_MERGE %s\n", zPathname); + srcid = 0; + if( !asNewFile ){ showDiff = 0; } + } + if( showDiff ){ Blob content; - content_get(srcid, &content); - printf("Index: %s\n=======================================" - "============================\n", - zPathname - ); - diff_file(&content, zFullName, zPathname, zDiffCmd); + if( !isLink != !file_wd_islink(zFullName) ){ + diff_print_index(zPathname); + diff_printf("--- %s\n+++ %s\n", zPathname, zPathname); + diff_printf("cannot compute difference between symlink and regular file\n"); + continue; + } + if( srcid>0 ){ + content_get(srcid, &content); + }else{ + blob_zero(&content); + } + diff_print_index(zPathname); + diff_file(&content, zFullName, zPathname, zDiffCmd, ignoreEolWs); blob_reset(&content); } - free(zFullName); + free(zToFree); } db_finalize(&q); - db_end_transaction(1); + db_end_transaction(1); /* ROLLBACK */ } /* ** Output the differences between two versions of a single file. ** zFrom and zTo are the check-ins containing the two file versions. -** The filename is contained in g.argv[2]. */ static void diff_one_two_versions( const char *zFrom, const char *zTo, - const char *zDiffCmd + const char *zDiffCmd, + int ignoreEolWs, + const char *zFileTreeName ){ char *zName; Blob fname; Blob v1, v2; - file_tree_name(g.argv[2], &fname, 1); + int isLink1, isLink2; + file_tree_name(zFileTreeName, &fname, 1); zName = blob_str(&fname); - historical_version_of_file(zFrom, zName, &v1, 0); - historical_version_of_file(zTo, zName, &v2, 0); - diff_file_mem(&v1, &v2, zName, zDiffCmd); + historical_version_of_file(zFrom, zName, &v1, &isLink1, 0, 0); + historical_version_of_file(zTo, zName, &v2, &isLink2, 0, 0); + if( isLink1 != isLink2 ){ + diff_printf("--- %s\n+++ %s\n", zName, zName); + diff_printf("cannot compute difference between symlink and regular file\n"); + }else{ + diff_file_mem(&v1, &v2, zName, zDiffCmd, ignoreEolWs); + } blob_reset(&v1); blob_reset(&v2); blob_reset(&fname); } + +/* +** Show the difference between two files identified by ManifestFile +** entries. +*/ +static void diff_manifest_entry( + struct ManifestFile *pFrom, + struct ManifestFile *pTo, + const char *zDiffCmd, + int ignoreEolWs +){ + Blob f1, f2; + int rid; + const char *zName = pFrom ? pFrom->zName : pTo->zName; + diff_print_index(zName); + if( pFrom ){ + rid = uuid_to_rid(pFrom->zUuid, 0); + content_get(rid, &f1); + }else{ + blob_zero(&f1); + } + if( pTo ){ + rid = uuid_to_rid(pTo->zUuid, 0); + content_get(rid, &f2); + }else{ + blob_zero(&f2); + } + diff_file_mem(&f1, &f2, zName, zDiffCmd, ignoreEolWs); + blob_reset(&f1); + blob_reset(&f2); +} /* ** Output the differences between two check-ins. */ static void diff_all_two_versions( const char *zFrom, const char *zTo, - const char *zDiffCmd + const char *zDiffCmd, + int diffFlags ){ + Manifest *pFrom, *pTo; + ManifestFile *pFromFile, *pToFile; + int ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0 ? 1 : 0; + int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0; + + pFrom = manifest_get_by_name(zFrom, 0); + manifest_file_rewind(pFrom); + pFromFile = manifest_file_next(pFrom,0); + pTo = manifest_get_by_name(zTo, 0); + manifest_file_rewind(pTo); + pToFile = manifest_file_next(pTo,0); + + while( pFromFile || pToFile ){ + int cmp; + if( pFromFile==0 ){ + cmp = +1; + }else if( pToFile==0 ){ + cmp = -1; + }else{ + cmp = fossil_strcmp(pFromFile->zName, pToFile->zName); + } + if( cmp<0 ){ + diff_printf("DELETED %s\n", pFromFile->zName); + if( asNewFlag ){ + diff_manifest_entry(pFromFile, 0, zDiffCmd, ignoreEolWs); + } + pFromFile = manifest_file_next(pFrom,0); + }else if( cmp>0 ){ + diff_printf("ADDED %s\n", pToFile->zName); + if( asNewFlag ){ + diff_manifest_entry(0, pToFile, zDiffCmd, ignoreEolWs); + } + pToFile = manifest_file_next(pTo,0); + }else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){ + /* No changes */ + pFromFile = manifest_file_next(pFrom,0); + pToFile = manifest_file_next(pTo,0); + }else{ + /* diff_printf("CHANGED %s\n", pFromFile->zName); */ + diff_manifest_entry(pFromFile, pToFile, zDiffCmd, ignoreEolWs); + pFromFile = manifest_file_next(pFrom,0); + pToFile = manifest_file_next(pTo,0); + } + } + manifest_destroy(pFrom); + manifest_destroy(pTo); } /* ** COMMAND: diff ** COMMAND: gdiff ** -** Usage: %fossil diff|gdiff ?options? ?FILE? +** Usage: %fossil diff|gdiff ?OPTIONS? ?FILE1? ?FILE2 ...? ** -** Show the difference between the current version of FILE (as it -** exists on disk) and that same file as it was checked out. Or -** if the FILE argument is omitted, show the unsaved changed currently -** in the working check-out. +** Show the difference between the current version of each of the FILEs +** specified (as they exist on disk) and that same file as it was checked +** out. Or if the FILE arguments are omitted, show the unsaved changed +** currently in the working check-out. ** ** If the "--from VERSION" or "-r VERSION" option is used it specifies ** the source check-in for the diff operation. If not specified, the ** source check-in is the base check-in for the current check-out. ** @@ -331,45 +451,78 @@ ** ** The "-i" command-line option forces the use of the internal diff logic ** rather than any external diff program that might be configured using ** the "setting" command. If no external diff program is configured, then ** the "-i" option is a no-op. The "-i" option converts "gdiff" into "diff". +** +** The "-N" or "--new-file" option causes the complete text of added or +** deleted files to be displayed. +** +** Options: +** --from|-r VERSION select VERSION as source for the diff +** --new-file|-N output complete text of added or deleted files +** -i use internal diff logic +** --to VERSION select VERSION as target for the diff */ void diff_cmd(void){ int isGDiff; /* True for gdiff. False for normal diff */ int isInternDiff; /* True for internal diff */ + int hasNFlag; /* True if -N or --new-file flag is used */ const char *zFrom; /* Source version number */ const char *zTo; /* Target version number */ const char *zDiffCmd = 0; /* External diff command. NULL for internal diff */ + int diffFlags = 0; /* Flags to control the DIFF */ + int f; isGDiff = g.argv[1][0]=='g'; isInternDiff = find_option("internal","i",0)!=0; zFrom = find_option("from", "r", 1); zTo = find_option("to", 0, 1); + hasNFlag = find_option("new-file","N",0)!=0; + + if( hasNFlag ) diffFlags |= DIFF_NEWFILE; if( zTo==0 ){ db_must_be_within_tree(); verify_all_options(); - if( !isInternDiff && g.argc==3 ){ + if( !isInternDiff ){ zDiffCmd = db_get(isGDiff ? "gdiff-command" : "diff-command", 0); } - if( g.argc==3 ){ - diff_one_against_disk(zFrom, zDiffCmd); + if( g.argc>=3 ){ + for(f=2; f =3 ){ + for(f=2; f =0 ){ + fossil_fatal("mimetypes out of sequence: %s before %s", + aMime[i-1].zSuffix, aMime[i].zSuffix); + } + } + return "ok"; + } +#endif z = zName; for(i=0; zName[i]; i++){ if( zName[i]=='.' ) z = &zName[i+1]; } len = strlen(z); if( len %s\n", g.argv[i], mimetype_from_name(g.argv[i])); + } +} /* ** WEBPAGE: doc ** URL: /doc?name=BASELINE/PATH ** URL: /doc/BASELINE/PATH ** ** BASELINE can be either a baseline uuid prefix or magic words "tip" -** to me the most recently checked in baseline or "ckout" to mean the +** to mean the most recently checked in baseline or "ckout" to mean the ** content of the local checkout, if any. PATH is the relative pathname ** of some file. This method returns the file content. ** ** If PATH matches the patterns *.wiki or *.txt then formatting content ** is added before returning the file. For all other names, the content @@ -331,11 +363,11 @@ int i; /* Loop counter */ Blob filebody; /* Content of the documentation file */ char zBaseline[UUID_SIZE+1]; /* Baseline UUID */ login_check_credentials(); - if( !g.okRead ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(); return; } zName = PD("name", "tip/index.wiki"); for(i=0; zName[i] && zName[i]!='/'; i++){} if( zName[i]==0 || i>UUID_SIZE ){ goto doc_not_found; } @@ -344,14 +376,14 @@ zName += i; while( zName[0]=='/' ){ zName++; } if( !file_is_simple_pathname(zName) ){ goto doc_not_found; } - if( strcmp(zBaseline,"ckout")==0 && db_open_local()==0 ){ - strcpy(zBaseline,"tip"); + if( fossil_strcmp(zBaseline,"ckout")==0 && db_open_local()==0 ){ + sqlite3_snprintf(sizeof(zBaseline), zBaseline, "tip"); } - if( strcmp(zBaseline,"ckout")==0 ){ + if( fossil_strcmp(zBaseline,"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) ){ @@ -360,15 +392,15 @@ if( blob_read_from_file(&filebody, zFullpath)<0 ){ goto doc_not_found; } }else{ db_begin_transaction(); - if( strcmp(zBaseline,"tip")==0 ){ + if( fossil_strcmp(zBaseline,"tip")==0 ){ vid = db_int(0, "SELECT objid FROM event WHERE type='ci'" " ORDER BY mtime DESC LIMIT 1"); }else{ - vid = name_to_rid(zBaseline); + vid = name_to_typed_rid(zBaseline, "ci"); } /* Create the baseline cache if it does not already exist */ db_multi_exec( "CREATE TABLE IF NOT EXISTS vcache(\n" @@ -387,37 +419,36 @@ goto doc_not_found; } if( rid==0 ){ Stmt s; - Blob baseline; - Manifest m; + Manifest *pM; + ManifestFile *pFile; /* Add the vid baseline to the cache */ if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){ db_multi_exec("DELETE FROM vcache"); } - if( content_get(vid, &baseline)==0 ){ - goto doc_not_found; - } - if( manifest_parse(&m, &baseline)==0 || m.type!=CFTYPE_MANIFEST ){ + pM = manifest_get(vid, CFTYPE_MANIFEST); + if( pM==0 ){ goto doc_not_found; } db_prepare(&s, "INSERT INTO vcache(vid,fname,rid)" " SELECT %d, :fname, rid FROM blob" " WHERE uuid=:uuid", vid ); - for(i=0; i zName); + db_bind_text(&s, ":uuid", pFile->zUuid); db_step(&s); db_reset(&s); } db_finalize(&s); - manifest_clear(&m); + manifest_destroy(pM); /* Try again to find the file */ rid = db_int(0, "SELECT rid FROM vcache" " WHERE vid=%d AND fname=%Q", vid, zName); } @@ -437,21 +468,26 @@ */ zMime = P("mimetype"); if( zMime==0 ){ zMime = mimetype_from_name(zName); } - if( strcmp(zMime, "application/x-fossil-wiki")==0 ){ + Th_Store("doc_name", zName); + Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'" + " FROM blob WHERE rid=%d", vid)); + Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" + " WHERE objid=%d AND type='ci'", vid)); + if( fossil_strcmp(zMime, "application/x-fossil-wiki")==0 ){ Blob title, tail; if( wiki_find_title(&filebody, &title, &tail) ){ style_header(blob_str(&title)); wiki_convert(&tail, 0, 0); }else{ style_header("Documentation"); wiki_convert(&filebody, 0, 0); } style_footer(); - }else if( strcmp(zMime, "text/plain")==0 ){ + }else if( fossil_strcmp(zMime, "text/plain")==0 ){ style_header("Documentation"); @ style_footer(); Index: src/encode.c ================================================================== --- src/encode.c +++ src/encode.c @@ -44,12 +44,11 @@ default: count++; break; } i++; } i = 0; - zOut = malloc( count+1 ); - if( zOut==0 ) return 0; + zOut = fossil_malloc( count+1 ); while( n-->0 && (c = *zIn)!=0 ){ switch( c ){ case '<': zOut[i++] = '&'; zOut[i++] = 'l'; @@ -100,11 +99,12 @@ int i = 0; int count = 0; char *zOut; int other; # define IsSafeChar(X) \ - (isalnum(X) || (X)=='.' || (X)=='$' || (X)=='-' || (X)=='_' || (X)==other) + (fossil_isalnum(X) || (X)=='.' || (X)=='$' \ + || (X)=='~' || (X)=='-' || (X)=='_' || (X)==other) if( zIn==0 ) return 0; if( n<0 ) n = strlen(zIn); other = encodeSlash ? 'a' : '/'; while( i@ %h(blob_str(&filebody)) @0 && (c = *zIn)!=0 ){ if( IsSafeChar(c) ){ zOut[i++] = c; }else if( c==' ' ){ zOut[i++] = '+'; @@ -233,21 +232,21 @@ c = zIn[i]; if( c==0 || c==' ' || c=='\n' || c=='\t' || c=='\r' || c=='\f' || c=='\v' || c=='\\' ) n++; } n += nIn; - zOut = malloc( n+1 ); + zOut = fossil_malloc( n+1 ); if( zOut ){ for(i=j=0; i >2) & 0x3f ]; z64[n++] = zBase[ ((zData[i]<<4) & 0x30) | ((zData[i+1]>>4) & 0x0f) ]; z64[n++] = zBase[ ((zData[i+1]<<2) & 0x3c) | ((zData[i+2]>>6) & 0x03) ]; z64[n++] = zBase[ zData[i+2] & 0x3f ]; @@ -340,11 +340,11 @@ void test_encode64_cmd(void){ char *z; int i; for(i=2; i 0 && z64[n64-1]=='=' ) n64--; - zData = malloc( (n64*3)/4 + 4 ); + zData = fossil_malloc( (n64*3)/4 + 4 ); for(i=j=0; i+3 %s (%s)\n", g.argv[i], z, z2); + free(z); + free(z2); + z = unobscure(g.argv[i]); + fossil_print("UNOBSCURE: %s -> %s\n", g.argv[i], z); + free(z); + } +} ADDED src/event.c Index: src/event.c ================================================================== --- /dev/null +++ src/event.c @@ -0,0 +1,437 @@ +/* +** Copyright (c) 2010 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 do formatting of event messages: +** +** Milestones +** Blog posts +** New articles +** Process checkpoints +** Announcements +*/ +#include +#include +#include "config.h" +#include "event.h" + +/* +** Output a hyperlink to an event given its tagid. +*/ +void hyperlink_to_event_tagid(int tagid){ + char *zEventId; + char zShort[12]; + + zEventId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d", + tagid); + sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zEventId); + if( g.perm.History ){ + @ [%s(zShort)] + }else{ + @ [%s(zShort)] + } + free(zEventId); +} + +/* +** WEBPAGE: event +** URL: /event +** PARAMETERS: +** +** name=EVENTID // Identify the event to display EVENTID must be complete +** detail=BOOLEAN // Show details if TRUE. Default is FALSE. Optional. +** aid=ARTIFACTID // Which specific version of the event. Optional. +** +** Display an existing event identified by EVENTID +*/ +void event_page(void){ + int rid = 0; /* rid of the event artifact */ + char *zUuid; /* UUID corresponding to rid */ + const char *zEventId; /* Event identifier */ + char *zETime; /* Time of the event */ + char *zATime; /* Time the artifact was created */ + int specRid; /* rid specified by aid= parameter */ + int prevRid, nextRid; /* Previous or next edits of this event */ + Manifest *pEvent; /* Parsed event artifact */ + Blob fullbody; /* Complete content of the event body */ + Blob title; /* Title extracted from the event body */ + Blob tail; /* Event body that comes after the title */ + Stmt q1; /* Query to search for the event */ + int showDetail; /* True to show details */ + + + /* wiki-read privilege is needed in order to read events. + */ + login_check_credentials(); + if( !g.perm.RdWiki ){ + login_needed(); + return; + } + + zEventId = P("name"); + if( zEventId==0 ){ fossil_redirect_home(); return; } + zUuid = (char*)P("aid"); + specRid = zUuid ? uuid_to_rid(zUuid, 0) : 0; + rid = nextRid = prevRid = 0; + db_prepare(&q1, + "SELECT rid FROM tagxref" + " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB 'event-%q*')" + " ORDER BY mtime DESC", + zEventId + ); + while( db_step(&q1)==SQLITE_ROW ){ + nextRid = rid; + rid = db_column_int(&q1, 0); + if( specRid==0 || specRid==rid ){ + if( db_step(&q1)==SQLITE_ROW ){ + prevRid = db_column_int(&q1, 0); + } + break; + } + } + db_finalize(&q1); + if( rid==0 || (specRid!=0 && specRid!=rid) ){ + style_header("No Such Event"); + @ Cannot locate specified event + style_footer(); + return; + } + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + showDetail = atoi(PD("detail","0")); + + /* Extract the event content. + */ + pEvent = manifest_get(rid, CFTYPE_EVENT); + if( pEvent==0 ){ + fossil_panic("Object #%d is not an event", rid); + } + blob_init(&fullbody, pEvent->zWiki, -1); + if( wiki_find_title(&fullbody, &title, &tail) ){ + style_header(blob_str(&title)); + }else{ + style_header("Event %S", zEventId); + tail = fullbody; + } + if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){ + style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s", + g.zTop, zEventId); + } + zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); + style_submenu_element("Context", "Context", "%s/timeline?c=%T", + g.zTop, zETime); + if( g.perm.History ){ + if( showDetail ){ + style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s", + g.zTop, zEventId, zUuid); + if( nextRid ){ + char *zNext; + zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid); + style_submenu_element("Next", "Next", + "%s/event?name=%s&aid=%s&detail=1", + g.zTop, zEventId, zNext); + free(zNext); + } + if( prevRid ){ + char *zPrev; + zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid); + style_submenu_element("Prev", "Prev", + "%s/event?name=%s&aid=%s&detail=1", + g.zTop, zEventId, zPrev); + free(zPrev); + } + }else{ + style_submenu_element("Detail", "Detail", + "%s/event?name=%s&aid=%s&detail=1", + g.zTop, zEventId, zUuid); + } + } + + if( showDetail && g.perm.History ){ + int i; + const char *zClr = 0; + Blob comment; + + zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate); + @ Event [%S(zUuid)] at + @ [%s(zETime)] + @ entered by user %h(pEvent->zUser) on + @ [%s(zATime)]:
+ @+ for(i=0; inTag; i++){ + if( fossil_strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){ + zClr = pEvent->aTag[i].zValue; + } + } + if( zClr && zClr[0]==0 ) zClr = 0; + if( zClr ){ + @ + }else{ + @+ } + blob_init(&comment, pEvent->zComment, -1); + wiki_convert(&comment, 0, WIKI_INLINE); + blob_reset(&comment); + @+ @
+ } + + wiki_convert(&tail, 0, 0); + style_footer(); + manifest_destroy(pEvent); +} + +/* +** WEBPAGE: eventedit +** URL: /eventedit?name=EVENTID +** +** Edit an event. If name is omitted, create a new event. +*/ +void eventedit_page(void){ + char *zTag; + int rid = 0; + Blob event; + const char *zEventId; + char *zHtmlPageName; + int n; + const char *z; + char *zBody = (char*)P("w"); + char *zETime = (char*)P("t"); + const char *zComment = P("c"); + const char *zTags = P("g"); + const char *zClr; + + if( zBody ){ + zBody = mprintf("%s", zBody); + } + login_check_credentials(); + zEventId = P("name"); + if( zEventId==0 ){ + zEventId = db_text(0, "SELECT lower(hex(randomblob(20)))"); + }else{ + int nEventId = strlen(zEventId); + if( nEventId!=40 || !validate16(zEventId, 40) ){ + fossil_redirect_home(); + return; + } + } + zTag = mprintf("event-%s", zEventId); + rid = db_int(0, + "SELECT rid FROM tagxref" + " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" + " ORDER BY mtime DESC", zTag + ); + free(zTag); + + /* Need both check-in and wiki-write or wiki-create privileges in order + ** to edit/create an event. + */ + if( !g.perm.Write || (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){ + login_needed(); + return; + } + + /* Figure out the color */ + if( rid ){ + zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid); + }else{ + zClr = ""; + } + zClr = PD("clr",zClr); + if( fossil_strcmp(zClr,"##")==0 ) zClr = PD("cclr",""); + + + /* If editing an existing event, extract the key fields to use as + ** a starting point for the edit. + */ + if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){ + Manifest *pEvent; + pEvent = manifest_get(rid, CFTYPE_EVENT); + if( pEvent && pEvent->type==CFTYPE_EVENT ){ + if( zBody==0 ) zBody = pEvent->zWiki; + if( zETime==0 ){ + zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); + } + if( zComment==0 ) zComment = pEvent->zComment; + } + if( zTags==0 ){ + zTags = db_text(0, + "SELECT group_concat(substr(tagname,5),', ')" + " FROM tagxref, tag" + " WHERE tagxref.rid=%d" + " AND tagxref.tagid=tag.tagid" + " AND tag.tagname GLOB 'sym-*'", + rid + ); + } + } + zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime); + if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){ + char *zDate; + Blob cksum; + int nrid; + blob_zero(&event); + db_begin_transaction(); + login_verify_csrf_secret(); + blob_appendf(&event, "C %F\n", zComment); + zDate = date_in_standard_format("now"); + blob_appendf(&event, "D %s\n", zDate); + free(zDate); + zETime[10] = 'T'; + blob_appendf(&event, "E %s %s\n", zETime, zEventId); + zETime[10] = ' '; + if( rid ){ + char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + blob_appendf(&event, "P %s\n", zUuid); + free(zUuid); + } + if( zClr && zClr[0] ){ + blob_appendf(&event, "T +bgcolor * %F\n", zClr); + } + if( zTags && zTags[0] ){ + Blob tags, one; + int i, j; + Stmt q; + char *zBlob; + + /* Load the tags string into a blob */ + blob_zero(&tags); + blob_append(&tags, zTags, -1); + + /* Collapse all sequences of whitespace and "," characters into + ** a single space character */ + zBlob = blob_str(&tags); + for(i=j=0; zBlob[i]; i++, j++){ + if( fossil_isspace(zBlob[i]) || zBlob[i]==',' ){ + while( fossil_isspace(zBlob[i+1]) ){ i++; } + zBlob[j] = ' '; + }else{ + zBlob[j] = zBlob[i]; + } + } + blob_resize(&tags, j); + + /* Parse out each tag and load it into a temporary table for sorting */ + db_multi_exec("CREATE TEMP TABLE newtags(x);"); + while( blob_token(&tags, &one) ){ + db_multi_exec("INSERT INTO newtags VALUES(%B)", &one); + } + blob_reset(&tags); + + /* Extract the tags in sorted order and make an entry in the + ** artifact for each. */ + db_prepare(&q, "SELECT x FROM newtags ORDER BY x"); + while( db_step(&q)==SQLITE_ROW ){ + blob_appendf(&event, "T +sym-%F *\n", db_column_text(&q, 0)); + } + db_finalize(&q); + } + if( g.zLogin ){ + blob_appendf(&event, "U %F\n", g.zLogin); + } + blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody); + md5sum_blob(&event, &cksum); + blob_appendf(&event, "Z %b\n", &cksum); + blob_reset(&cksum); + nrid = content_put(&event); + db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid); + manifest_crosslink(nrid, &event); + assert( blob_is_reset(&event) ); + content_deltify(rid, nrid, 0); + db_end_transaction(0); + cgi_redirectf("event?name=%T", zEventId); + } + if( P("cancel")!=0 ){ + cgi_redirectf("event?name=%T", zEventId); + return; + } + if( zBody==0 ){ + zBody = mprintf("Event Text"); + } + zHtmlPageName = mprintf("Edit Event %S", zEventId); + style_header(zHtmlPageName); + if( P("preview")!=0 ){ + Blob title, tail, com; + @Timeline comment preview:
+ @+ @+ @+ if( zClr && zClr[0] ){ + @
+ @+ }else{ + @ + } + blob_zero(&com); + blob_append(&com, zComment, -1); + wiki_convert(&com, 0, WIKI_INLINE); + @ Page content preview:
+ @
+ blob_zero(&event); + blob_append(&event, zBody, -1); + if( wiki_find_title(&event, &title, &tail) ){ + @%h(blob_str(&title))
+ wiki_convert(&tail, 0, 0); + }else{ + wiki_convert(&event, 0, 0); + } + @
+ blob_reset(&event); + } + for(n=2, z=zBody; z[0]; z++){ + if( z[0]=='\n' ) n++; + } + if( n<20 ) n = 20; + if( n>40 ) n = 40; + @ + style_footer(); +} ADDED src/export.c Index: src/export.c ================================================================== --- /dev/null +++ src/export.c @@ -0,0 +1,348 @@ +/* +** Copyright (c) 2010 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 +** +******************************************************************************* +** +** This file contains code used to export the content of a Fossil +** repository in the git-fast-import format. +*/ +#include "config.h" +#include "export.h" +#include+ +/* +** Output a "committer" record for the given user. +*/ +static void print_person(const char *zUser){ + static Stmt q; + const char *zContact; + char *zName; + char *zEmail; + int i, j; + + if( zUser==0 ){ + printf(" "); + return; + } + db_static_prepare(&q, "SELECT info FROM user WHERE login=:user"); + db_bind_text(&q, ":user", zUser); + if( db_step(&q)!=SQLITE_ROW ){ + db_reset(&q); + for(i=0; zUser[i] && zUser[i]!='>' && zUser[i]!='<'; i++){} + if( zUser[i]==0 ){ + printf(" %s <%s>", zUser, zUser); + return; + } + zName = mprintf("%s", zUser); + for(i=j=0; zName[i]; i++){ + if( zName[i]!='<' && zName[i]!='>' ){ + zName[j++] = zName[i]; + } + } + zName[j] = 0; + printf(" %s <%s>", zName, zUser); + free(zName); + return; + } + zContact = db_column_text(&q, 0); + for(i=0; zContact[i] && zContact[i]!='>' && zContact[i]!='<'; i++){} + if( zContact[i]==0 ){ + printf(" %s <%s>", zContact[0] ? zContact : zUser, zUser); + db_reset(&q); + return; + } + if( zContact[i]=='<' ){ + zEmail = mprintf("%s", &zContact[i]); + for(i=0; zEmail[i] && zEmail[i]!='>'; i++){} + if( zEmail[i]=='>' ) zEmail[i+1] = 0; + }else{ + zEmail = mprintf("<%s>", zUser); + } + zName = mprintf("%.*s", i, zContact); + for(i=j=0; zName[i]; i++){ + if( zName[i]!='"' ) zName[j++] = zName[i]; + } + zName[j] = 0; + printf(" %s %s", zName, zEmail); + free(zName); + free(zEmail); + db_reset(&q); +} + +#define BLOBMARK(rid) ((rid) * 2) +#define COMMITMARK(rid) ((rid) * 2 + 1) + +/* +** COMMAND: export +** +** Usage: %fossil export --git ?OPTIONS? ?REPOSITORY? +** +** Write an export of all check-ins to standard output. The export is +** written in the git-fast-export file format assuming the --git option is +** provided. The git-fast-export format is currently the only VCS +** interchange format supported, though other formats may be added in +** the future. +** +** Run this command within a checkout. Or use the -R or --repository +** option to specify a Fossil repository to be exported. +** +** Only check-ins are exported using --git. Git does not support tickets +** or wiki or events or attachments, so none of those are exported. +** +** If the "--import-marks FILE" option is used, it contains a list of +** rids to skip. +** +** If the "--export-marks FILE" option is used, the rid of all commits and +** blobs written on exit for use with "--import-marks" on the next run. +** +** Options: +** --export-marks FILE export rids of exported data to FILE +** --import-marks FILE read rids of data to ignore from FILE +** --repository|-R REPOSITORY export the given REPOSITORY +** +** See also: import +*/ +void export_cmd(void){ + Stmt q, q2, q3; + int i; + Bag blobs, vers; + const char *markfile_in; + const char *markfile_out; + + bag_init(&blobs); + bag_init(&vers); + + find_option("git", 0, 0); /* Ignore the --git option for now */ + markfile_in = find_option("import-marks", 0, 1); + markfile_out = find_option("export-marks", 0, 1); + + db_find_and_open_repository(0, 2); + verify_all_options(); + if( g.argc!=2 && g.argc!=3 ){ usage("--git ?REPOSITORY?"); } + + db_multi_exec("CREATE TEMPORARY TABLE oldblob(rid INTEGER PRIMARY KEY)"); + db_multi_exec("CREATE TEMPORARY TABLE oldcommit(rid INTEGER PRIMARY KEY)"); + if( markfile_in!=0 ){ + Stmt qb,qc; + char line[100]; + FILE *f; + + f = fopen(markfile_in, "r"); + if( f==0 ){ + fossil_panic("cannot open %s for reading", markfile_in); + } + db_prepare(&qb, "INSERT OR IGNORE INTO oldblob VALUES (:rid)"); + db_prepare(&qc, "INSERT OR IGNORE INTO oldcommit VALUES (:rid)"); + while( fgets(line, sizeof(line), f)!=0 ){ + if( *line == 'b' ){ + db_bind_text(&qb, ":rid", line + 1); + db_step(&qb); + db_reset(&qb); + bag_insert(&blobs, atoi(line + 1)); + }else if( *line == 'c' ){ + db_bind_text(&qc, ":rid", line + 1); + db_step(&qc); + db_reset(&qc); + bag_insert(&vers, atoi(line + 1)); + }else{ + fossil_panic("bad input from %s: %s", markfile_in, line); + } + } + db_finalize(&qb); + db_finalize(&qc); + fclose(f); + } + + /* Step 1: Generate "blob" records for every artifact that is part + ** of a check-in + */ + fossil_binary_mode(stdout); + db_multi_exec("CREATE TEMPORARY TABLE newblob(rid INTEGER KEY, srcid INTEGER)"); + db_multi_exec("CREATE INDEX newblob_src ON newblob(srcid)"); + db_multi_exec( + "INSERT INTO newblob" + " SELECT DISTINCT fid," + " CASE WHEN EXISTS(SELECT 1 FROM delta WHERE rid=fid AND NOT EXISTS(SELECT 1 FROM oldblob WHERE srcid=fid))" + " THEN (SELECT srcid FROM delta WHERE rid=fid)" + " ELSE 0" + " END" + " FROM mlink" + " WHERE fid>0 AND NOT EXISTS(SELECT 1 FROM oldblob WHERE rid=fid)"); + db_prepare(&q, + "SELECT DISTINCT fid FROM mlink" + " WHERE fid>0 AND NOT EXISTS(SELECT 1 FROM oldblob WHERE rid=fid)"); + db_prepare(&q2, "INSERT INTO oldblob VALUES (:rid)"); + db_prepare(&q3, "SELECT rid FROM newblob WHERE srcid= (:srcid)"); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + Blob content; + + while( !bag_find(&blobs, rid) ){ + content_get(rid, &content); + db_bind_int(&q2, ":rid", rid); + db_step(&q2); + db_reset(&q2); + printf("blob\nmark :%d\ndata %d\n", BLOBMARK(rid), blob_size(&content)); + bag_insert(&blobs, rid); + fwrite(blob_buffer(&content), 1, blob_size(&content), stdout); + printf("\n"); + blob_reset(&content); + + db_bind_int(&q3, ":srcid", rid); + if( db_step(&q3) != SQLITE_ROW ){ + db_reset(&q3); + break; + } + rid = db_column_int(&q3, 0); + db_reset(&q3); + } + } + db_finalize(&q); + db_finalize(&q2); + db_finalize(&q3); + + /* Output the commit records. + */ + db_prepare(&q, + "SELECT strftime('%%s',mtime), objid, coalesce(comment,ecomment)," + " coalesce(user,euser)," + " (SELECT value FROM tagxref WHERE rid=objid AND tagid=%d)" + " FROM event" + " WHERE type='ci' AND NOT EXISTS (SELECT 1 FROM oldcommit WHERE objid=rid)" + " ORDER BY mtime ASC", + TAG_BRANCH + ); + db_prepare(&q2, "INSERT INTO oldcommit VALUES (:rid)"); + while( db_step(&q)==SQLITE_ROW ){ + Stmt q4; + const char *zSecondsSince1970 = db_column_text(&q, 0); + int ckinId = db_column_int(&q, 1); + const char *zComment = db_column_text(&q, 2); + const char *zUser = db_column_text(&q, 3); + const char *zBranch = db_column_text(&q, 4); + char *zBr; + + bag_insert(&vers, ckinId); + db_bind_int(&q2, ":rid", ckinId); + db_step(&q2); + db_reset(&q2); + if( zBranch==0 ) zBranch = "trunk"; + zBr = mprintf("%s", zBranch); + for(i=0; zBr[i]; i++){ + if( !fossil_isalnum(zBr[i]) ) zBr[i] = '_'; + } + printf("commit refs/heads/%s\nmark :%d\n", zBr, COMMITMARK(ckinId)); + free(zBr); + printf("committer"); + print_person(zUser); + printf(" %s +0000\n", zSecondsSince1970); + if( zComment==0 ) zComment = "null comment"; + printf("data %d\n%s\n", (int)strlen(zComment), zComment); + db_prepare(&q3, "SELECT pid FROM plink WHERE cid=%d AND isprim", ckinId); + if( db_step(&q3) == SQLITE_ROW ){ + printf("from :%d\n", COMMITMARK(db_column_int(&q3, 0))); + db_prepare(&q4, + "SELECT pid FROM plink" + " WHERE cid=%d AND NOT isprim" + " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)" + " ORDER BY pid", + ckinId); + while( db_step(&q4)==SQLITE_ROW ){ + printf("merge :%d\n", COMMITMARK(db_column_int(&q4,0))); + } + db_finalize(&q4); + }else{ + printf("deleteall\n"); + } + + db_prepare(&q4, + "SELECT filename.name, mlink.fid, mlink.mperm FROM mlink" + " JOIN filename ON filename.fnid=mlink.fnid" + " WHERE mlink.mid=%d", + ckinId + ); + while( db_step(&q4)==SQLITE_ROW ){ + const char *zName = db_column_text(&q4,0); + int zNew = db_column_int(&q4,1); + int mPerm = db_column_int(&q4,2); + if( zNew==0) + printf("D %s\n", zName); + else if( bag_find(&blobs, zNew) ) { + const char *zPerm; + switch( mPerm ){ + case PERM_LNK: zPerm = "120000"; break; + case PERM_EXE: zPerm = "100755"; break; + default: zPerm = "100644"; break; + } + printf("M %s :%d %s\n", zPerm, BLOBMARK(zNew), zName); + } + } + db_finalize(&q4); + db_finalize(&q3); + printf("\n"); + } + db_finalize(&q2); + db_finalize(&q); + bag_clear(&blobs); + manifest_cache_clear(); + + + /* Output tags */ + db_prepare(&q, + "SELECT tagname, rid, strftime('%%s',mtime)" + " FROM tagxref JOIN tag USING(tagid)" + " WHERE tagtype=1 AND tagname GLOB 'sym-*'" + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zTagname = db_column_text(&q, 0); + char *zEncoded = 0; + int rid = db_column_int(&q, 1); + const char *zSecSince1970 = db_column_text(&q, 2); + int i; + if( rid==0 || !bag_find(&vers, rid) ) continue; + zTagname += 4; + zEncoded = mprintf("%s", zTagname); + for(i=0; zEncoded[i]; i++){ + if( !fossil_isalnum(zEncoded[i]) ) zEncoded[i] = '_'; + } + printf("tag %s\n", zEncoded); + printf("from :%d\n", COMMITMARK(rid)); + printf("tagger %s +0000\n", zSecSince1970); + printf("data 0\n"); + fossil_free(zEncoded); + } + db_finalize(&q); + bag_clear(&vers); + + if( markfile_out!=0 ){ + FILE *f; + f = fopen(markfile_out, "w"); + if( f == 0 ){ + fossil_panic("cannot open %s for writing", markfile_out); + } + db_prepare(&q, "SELECT rid FROM oldblob"); + while( db_step(&q)==SQLITE_ROW ){ + fprintf(f, "b%d\n", db_column_int(&q, 0)); + } + db_finalize(&q); + db_prepare(&q, "SELECT rid FROM oldcommit"); + while( db_step(&q)==SQLITE_ROW ){ + fprintf(f, "c%d\n", db_column_int(&q, 0)); + } + db_finalize(&q); + if( ferror(f)!=0 || fclose(f)!=0 ) { + fossil_panic("error while writing %s", markfile_out); + } + } +} Index: src/file.c ================================================================== --- src/file.c +++ src/file.c @@ -13,37 +13,79 @@ ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** -** File utilities +** File utilities. +** +** Functions named file_* are generic functions that always follow symlinks. +** +** Functions named file_wd_* are to be used for files inside working +** directories. They follow symlinks depending on 'allow-symlinks' setting. */ #include "config.h" #include #include #include +#include +#include #include "file.h" /* ** The file status information from the most recent stat() call. +** +** Use _stati64 rather than stat on windows, in order to handle files +** larger than 2GB. +*/ +#if defined(_WIN32) && (defined(__MSVCRT__) || defined(_MSC_VER)) +# define stat _stati64 +#endif +/* +** On Windows S_ISLNK always returns FALSE. */ -static struct stat fileStat; +#if defined(_WIN32) +# define S_ISLNK(x) (0) +#endif static int fileStatValid = 0; +static struct stat fileStat; + +/* +** Fill stat buf with information received from stat() or lstat(). +** lstat() is called on Unix if isWd is TRUE and allow-symlinks setting is on. +** +*/ +static int fossil_stat(const char *zFilename, struct stat *buf, int isWd){ +#if !defined(_WIN32) + if( isWd && g.allowSymlinks ){ + return lstat(zFilename, buf); + }else{ + return stat(zFilename, buf); + } +#else + int rc = 0; + char *zMbcs = fossil_utf8_to_mbcs(zFilename); + rc = stat(zMbcs, buf); + fossil_mbcs_free(zMbcs); + return rc; +#endif +} /* ** Fill in the fileStat variable for the file named zFilename. ** If zFilename==0, then use the previous value of fileStat if ** there is a previous value. ** +** If isWd is TRUE, do lstat() instead of stat() if allow-symlinks is on. +** ** Return the number of errors. No error messages are generated. */ -static int getStat(const char *zFilename){ +static int getStat(const char *zFilename, int isWd){ int rc = 0; if( zFilename==0 ){ if( fileStatValid==0 ) rc = 1; }else{ - if( stat(zFilename, &fileStat)!=0 ){ + if( fossil_stat(zFilename, &fileStat, isWd)!=0 ){ fileStatValid = 0; rc = 1; }else{ fileStatValid = 1; rc = 0; @@ -50,50 +92,166 @@ } } return rc; } - /* ** Return the size of a file in bytes. Return -1 if the file does not ** exist. If zFilename is NULL, return the size of the most recently ** stat-ed file. */ i64 file_size(const char *zFilename){ - return getStat(zFilename) ? -1 : fileStat.st_size; + return getStat(zFilename, 0) ? -1 : fileStat.st_size; +} + +/* +** Same as file_size(), but takes into account symlinks. +*/ +i64 file_wd_size(const char *zFilename){ + return getStat(zFilename, 1) ? -1 : fileStat.st_size; } /* ** Return the modification time for a file. Return -1 if the file ** does not exist. If zFilename is NULL return the size of the most ** recently stat-ed file. */ i64 file_mtime(const char *zFilename){ - return getStat(zFilename) ? -1 : fileStat.st_mtime; + return getStat(zFilename, 0) ? -1 : fileStat.st_mtime; +} + +/* +** Same as file_mtime(), but takes into account symlinks. +*/ +i64 file_wd_mtime(const char *zFilename){ + return getStat(zFilename, 1) ? -1 : fileStat.st_mtime; +} + +/* +** Return TRUE if the named file is an ordinary file or symlink +** and symlinks are allowed. +** Return false for directories, devices, fifos, etc. +*/ +int file_wd_isfile_or_link(const char *zFilename){ + return getStat(zFilename, 1) ? 0 : S_ISREG(fileStat.st_mode) || + S_ISLNK(fileStat.st_mode); } /* ** Return TRUE if the named file is an ordinary file. Return false ** for directories, devices, fifos, symlinks, etc. */ int file_isfile(const char *zFilename){ - return getStat(zFilename) ? 0 : S_ISREG(fileStat.st_mode); + return getStat(zFilename, 0) ? 0 : S_ISREG(fileStat.st_mode); +} + +/* +** Same as file_isfile(), but takes into account symlinks. +*/ +int file_wd_isfile(const char *zFilename){ + return getStat(zFilename, 1) ? 0 : S_ISREG(fileStat.st_mode); +} + +/* +** Create symlink to file on Unix, or plain-text file with +** symlink target if "allow-symlinks" is off or we're on Windows. +** +** Arguments: target file (symlink will point to it), link file +**/ +void symlink_create(const char *zTargetFile, const char *zLinkFile){ +#if !defined(_WIN32) + if( g.allowSymlinks ){ + int i, nName; + char *zName, zBuf[1000]; + + nName = strlen(zLinkFile); + if( nName>=sizeof(zBuf) ){ + zName = mprintf("%s", zLinkFile); + }else{ + zName = zBuf; + memcpy(zName, zLinkFile, nName+1); + } + nName = file_simplify_name(zName, nName); + for(i=1; i =0 ){ + fossil_free(z); + z = mprintf("%s-%s-%d", zBase, zSuffix, cnt++); + } + if( relFlag ){ + Blob x; + file_relative_name(z, &x); + fossil_free(z); + z = blob_str(&x); + } + return z; +} + /* ** Return the tail of a file pathname. The tail is the last component ** of the path. For example, the tail of "/a/b/c.d" is "c.d". */ const char *file_tail(const char *z){ @@ -130,38 +341,53 @@ */ void file_copy(const char *zFrom, const char *zTo){ FILE *in, *out; int got; char zBuf[8192]; - in = fopen(zFrom, "rb"); + in = fossil_fopen(zFrom, "rb"); if( in==0 ) fossil_fatal("cannot open \"%s\" for reading", zFrom); - out = fopen(zTo, "wb"); + 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); } /* -** Set or clear the execute bit on a file. +** Set or clear the execute bit on a file. Return true if a change +** occurred and false if this routine is a no-op. */ -void file_setexe(const char *zFilename, int onoff){ -#ifndef __MINGW32__ +int file_wd_setexe(const char *zFilename, int onoff){ + int rc = 0; +#if !defined(_WIN32) struct stat buf; - if( stat(zFilename, &buf)!=0 ) return; + if( fossil_stat(zFilename, &buf, 1)!=0 || S_ISLNK(buf.st_mode) ) return 0; if( onoff ){ - if( (buf.st_mode & 0111)!=0111 ){ - chmod(zFilename, buf.st_mode | 0111); + int targetMode = (buf.st_mode & 0444)>>2; + if( (buf.st_mode & 0111)!=targetMode ){ + chmod(zFilename, buf.st_mode | targetMode); + rc = 1; } }else{ if( (buf.st_mode & 0111)!=0 ){ chmod(zFilename, buf.st_mode & ~0111); + rc = 1; } } -#endif /* __MINGW32__ */ +#endif /* _WIN32 */ + return rc; +} + +/* +** Delete a file. +*/ +void file_delete(const char *zFilename){ + char *z = fossil_utf8_to_mbcs(zFilename); + unlink(z); + fossil_mbcs_free(z); } /* ** Create the directory named in the argument, if it does not already ** exist. If forceFlag is 1, delete any prior non-directory object @@ -168,18 +394,22 @@ ** with the same name. ** ** Return the number of errors. */ int file_mkdir(const char *zName, int forceFlag){ - int rc = file_isdir(zName); + int rc = file_wd_isdir(zName); if( rc==2 ){ if( !forceFlag ) return 1; - unlink(zName); + file_delete(zName); } if( rc!=1 ){ -#ifdef __MINGW32__ - return mkdir(zName); +#if defined(_WIN32) + int rc; + char *zMbcs = fossil_utf8_to_mbcs(zName); + rc = mkdir(zMbcs); + fossil_mbcs_free(zMbcs); + return rc; #else return mkdir(zName, 0755); #endif } return 0; @@ -197,20 +427,21 @@ ** * Does not contain two or more "/" characters in a row. ** * Contains at least one character */ int file_is_simple_pathname(const char *z){ int i; - if( *z=='/' || *z==0 ) return 0; - if( *z=='.' ){ + char c = 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; } - for(i=0; z[i]; i++){ - if( z[i]=='\\' || z[i]=='*' || z[i]=='[' || z[i]==']' || z[i]=='?' ){ + for(i=0; (c=z[i])!=0; i++){ + if( c=='\\' || c=='*' || c=='[' || c==']' || c=='?' ){ return 0; } - if( z[i]=='/' ){ + if( c=='/' ){ if( z[i+1]=='/' ) return 0; if( z[i+1]=='.' ){ if( z[i+2]=='/' || z[i+2]==0 ) return 0; if( z[i+2]=='.' && (z[i+3]=='/' || z[i+3]==0) ) return 0; } @@ -217,48 +448,143 @@ } } 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){ + int j = *pJ; + int i; + if( j<=0 ) return 0; + for(i=j-1; i>0 && z[i-1]!='/'; i--){} + if( z[i]=='.' && i==j-2 && z[i+1]=='.' ) return 0; + *pJ = i-1; + return 1; +} /* ** Simplify a filename by ** +** * Convert all \ into / on windows ** * removing any trailing and duplicate / ** * removing /./ ** * removing /A/../ ** ** Changes are made in-place. Return the new name length. */ int file_simplify_name(char *z, int n){ int i, j; if( n<0 ) n = strlen(z); -#ifdef __MINGW32__ + + /* On windows convert all \ characters to / */ +#if defined(_WIN32) for(i=0; i 1 && z[n-1]=='/' ){ n--; } - for(i=j=0; i 0 && z[j-1]!='/' ){ j--; } - if( j>0 ){ j--; } + + /* If this is a "/.." directory component then back out the + ** previous term of the directory if it is something other than ".." + ** or "." + */ + if( z[i+1]=='.' && i+2