Diff
Not logged in

Differences From:

File src/db.c part of check-in [b2e55c0d4d] - Add the /wiki and /bwiki web pages. Currently renders content from the check-out as readonly. by drh on 2007-09-01 21:11:33. Also file src/db.c part of check-in [bbcb6326c9] - Pulled in the navbar and timeline changes. by aku on 2007-09-17 00:58:51. [view]

To:

File src/db.c part of check-in [371dd6574c] - Fix the revert command so that it works from subdirectories. Other minor comment and help-text changes. by drh on 2007-12-04 01:26:21. Also file src/db.c part of check-in [d0305b305a] - Merged mainline into my branch to get the newest application. by aku on 2007-12-05 08:07:46. [view]

@@ -34,12 +34,14 @@
 **         root of the local copy of the source tree.
 **
 */
 #include "config.h"
+#ifndef __MINGW32__
+#  include <pwd.h>
+#endif
 #include <sqlite3.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <pwd.h>
 #include <unistd.h>
 #include "db.h"
 
 #if INTERFACE
@@ -71,25 +73,53 @@
     fprintf(stderr, "%s: %s\n", g.argv[0], z);
   }
   db_force_rollback();
   exit(1);
-  exit(1);
 }
 
 static int nBegin = 0;      /* Nesting depth of BEGIN */
 static int doRollback = 0;  /* True to force a rollback */
+static int nCommitHook = 0; /* Number of commit hooks */
+static struct sCommitHook {
+  int (*xHook)(void);  /* Functions to call at db_end_transaction() */
+  int sequence;        /* Call functions in sequence order */
+} aHook[5];
+
+/*
+** This routine is called by the SQLite commit-hook mechanism
+** just prior to each omit.  All this routine does is verify
+** that nBegin really is zero.  That insures that transactions
+** cannot commit by any means other than by calling db_end_transaction()
+** below.
+**
+** This is just a safety and sanity check.
+*/
+static int db_verify_at_commit(void *notUsed){
+  if( nBegin ){
+    fossil_panic("illegal commit attempt");
+    return 1;
+  }
+  return 0;
+}
 
 /*
 ** Begin and end a nested transaction
 */
 void db_begin_transaction(void){
-  if( nBegin==0 ) db_multi_exec("BEGIN");
+  if( nBegin==0 ){
+    db_multi_exec("BEGIN");
+    sqlite3_commit_hook(g.db, db_verify_at_commit, 0);
+  }
   nBegin++;
 }
 void db_end_transaction(int rollbackFlag){
   if( rollbackFlag ) doRollback = 1;
   nBegin--;
   if( nBegin==0 ){
+    int i;
+    for(i=0; doRollback==0 && i<nCommitHook; i++){
+      doRollback |= aHook[i].xHook();
+    }
     db_multi_exec(doRollback ? "ROLLBACK" : "COMMIT");
     doRollback = 0;
   }
 }
@@ -97,8 +127,37 @@
   if( nBegin ){
     sqlite3_exec(g.db, "ROLLBACK", 0, 0, 0);
   }
   nBegin = 0;
+}
+
+/*
+** Install a commit hook.  Hooks are installed in sequence order.
+** It is an error to install the same commit hook more than once.
+**
+** Each commit hook is called (in order of accending sequence) at
+** each commit operation.  If any commit hook returns non-zero,
+** the subsequence commit hooks are omitted and the transaction
+** rolls back rather than commit.  It is the responsibility of the
+** hooks themselves to issue any error messages.
+*/
+void db_commit_hook(int (*x)(void), int sequence){
+  int i;
+  assert( nCommitHook < sizeof(aHook)/sizeof(aHook[1]) );
+  for(i=0; i<nCommitHook; i++){
+    assert( x!=aHook[i].xHook );
+    if( aHook[i].sequence>sequence ){
+      int s = sequence;
+      int (*xS)(void) = x;
+      sequence = aHook[i].sequence;
+      x = aHook[i].xHook;
+      aHook[i].sequence = s;
+      aHook[i].xHook = xS;
+    }
+  }
+  aHook[nCommitHook].sequence = sequence;
+  aHook[nCommitHook].xHook = x;
+  nCommitHook++;
 }
 
 /*
 ** Prepare or reprepare the sqlite3 statement from the raw SQL text.
@@ -164,8 +223,11 @@
   return sqlite3_bind_int(pStmt->pStmt, paramIdx(pStmt, zParamName), iValue);
 }
 int db_bind_int64(Stmt *pStmt, const char *zParamName, i64 iValue){
   return sqlite3_bind_int64(pStmt->pStmt, paramIdx(pStmt, zParamName), iValue);
+}
+int db_bind_double(Stmt *pStmt, const char *zParamName, double rValue){
+  return sqlite3_bind_double(pStmt->pStmt, paramIdx(pStmt, zParamName), rValue);
 }
 int db_bind_text(Stmt *pStmt, const char *zParamName, const char *zValue){
   return sqlite3_bind_text(pStmt->pStmt, paramIdx(pStmt, zParamName), zValue,
                            -1, SQLITE_STATIC);
@@ -257,8 +319,14 @@
   return sqlite3_column_double(pStmt->pStmt, N);
 }
 const char *db_column_text(Stmt *pStmt, int N){
   return (char*)sqlite3_column_text(pStmt->pStmt, N);
+}
+const char *db_column_name(Stmt *pStmt, int N){
+  return (char*)sqlite3_column_name(pStmt->pStmt, N);
+}
+int db_column_count(Stmt *pStmt){
+  return sqlite3_column_count(pStmt->pStmt);
 }
 char *db_column_malloc(Stmt *pStmt, int N){
   return mprintf("%s", db_column_text(pStmt, N));
 }
@@ -486,13 +554,29 @@
 ** it does not already exist.
 */
 void db_open_config(void){
   char *zDbName;
-  const char *zHome = getenv("HOME");
+  const char *zHome;
+#ifdef __MINGW32__
+  zHome = getenv("LOCALAPPDATA");
+  if( zHome==0 ){
+    zHome = getenv("APPDATA");
+    if( zHome==0 ){
+      zHome = getenv("HOMEPATH");
+    }
+  }
+#else
+  zHome = getenv("HOME");
+#endif
   if( zHome==0 ){
     db_err("cannot local home directory");
   }
+#ifdef __MINGW32__
+  /* . filenames give some window systems problems and many apps problems */
+  zDbName = mprintf("%s/_fossil", zHome);
+#else
   zDbName = mprintf("%s/.fossil", zHome);
+#endif
   if( g.configOpen ) return;
   if( file_size(zDbName)<1024*3 ){
     db_init_database(zDbName, zConfigSchema, (char*)0);
   }
@@ -532,13 +616,17 @@
 */
 int db_open_local(void){
   int n;
   char zPwd[2000];
+  char *zPwdConv;
   if( g.localOpen) return 1;
   if( getcwd(zPwd, sizeof(zPwd)-20)==0 ){
     db_err("pwd too big: max %d", sizeof(zPwd)-20);
   }
   n = strlen(zPwd);
+  zPwdConv = mprintf("%/", zPwd);
+  strncpy(zPwd, zPwdConv, 2000-20);
+  free(zPwdConv);
   while( n>0 ){
     if( access(zPwd, W_OK) ) break;
     strcpy(&zPwd[n], "/_FOSSIL_");
     if( isValidLocalDb(zPwd) ){
@@ -649,28 +737,38 @@
 ** repository. This function is shared between 'create_repository_cmd'
 ** ('new') and 'reconstruct_cmd' ('reconstruct'), both of which create
 ** new repositories.
 **
-** The caller determines wheter the function inserts an empty root
-** manifest (zRoot == TRUE), or not (zRoot == FALSE).
+** The makeInitialVersion flag determines whether or not an initial
+** manifest is created.  The makeServerCodes flag determines whether or
+** not server and project codes are invented for this repository.
 */
-
-void db_initial_setup (int zRoot){
+void db_initial_setup (int makeInitialVersion, int makeServerCodes){
   char *zDate;
-  char *zUser;
+  const char *zUser;
   Blob hash;
   Blob manifest;
 
-  db_set("content-schema", CONTENT_SCHEMA);
-  db_set("aux-schema", AUX_SCHEMA);
-  db_set_int("authenticate-localhost", 0);
-  db_multi_exec(
-    "INSERT INTO config(name,value) VALUES('server-code', hex(randomblob(20)));"
-    "INSERT INTO config(name,value) VALUES('project-code',hex(randomblob(20)));"
-  );
-  zUser = db_global_get("default-user", 0);
+  db_set("content-schema", CONTENT_SCHEMA, 0);
+  db_set("aux-schema", AUX_SCHEMA, 0);
+  if( makeServerCodes ){
+    db_multi_exec(
+      "INSERT INTO config(name,value)"
+      " VALUES('server-code', lower(hex(randomblob(20))));"
+      "INSERT INTO config(name,value)"
+      " VALUES('project-code', lower(hex(randomblob(20))));"
+    );
+  }
+  if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
+  if( !db_is_global("safemerge") ) db_set_int("safemerge", 0, 0);
+  if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
+  zUser = db_get("default-user", 0);
   if( zUser==0 ){
+#ifdef __MINGW32__
+    zUser = getenv("USERNAME");
+#else
     zUser = getenv("USER");
+#endif
   }
   if( zUser==0 ){
     zUser = "root";
   }
@@ -685,9 +783,9 @@
      "   VALUES('nobody','','jor','Nobody');"
   );
   user_select();
 
-  if (zRoot){
+  if (makeInitialVersion){
     blob_zero(&manifest);
     blob_appendf(&manifest, "C initial\\sempty\\sbaseline\n");
     zDate = db_text(0, "SELECT datetime('now')");
     zDate[10]='T';
@@ -718,9 +816,9 @@
   db_create_repository(g.argv[2]);
   db_open_repository(g.argv[2]);
   db_open_config();
   db_begin_transaction();
-  db_initial_setup (1);
+  db_initial_setup(1, 1);
   db_end_transaction(0);
   printf("project-id: %s\n", db_get("project-code", 0));
   printf("server-id:  %s\n", db_get("server-code", 0));
   printf("admin-user: %s (no password set yet!)\n", g.zLogin);
@@ -802,30 +900,79 @@
 /*
 ** Get and set values from the CONFIG, GLOBAL_CONFIG and VVAR table in the
 ** repository and local databases.
 */
-char *db_get(const char *zName, const char *zDefault){
-  return db_text((char*)zDefault,
-                 "SELECT value FROM config WHERE name=%Q", zName);
+char *db_get(const char *zName, char *zDefault){
+  char *z = 0;
+  if( g.repositoryOpen ){
+    z = db_text(0, "SELECT value FROM config WHERE name=%Q", zName);
+  }
+  if( z==0 && g.configOpen ){
+    z = db_text(0, "SELECT value FROM global_config WHERE name=%Q", zName);
+  }
+  if( z==0 ){
+    z = zDefault;
+  }
+  return z;
+}
+void db_set(const char *zName, const char *zValue, int globalFlag){
+  db_begin_transaction();
+  db_multi_exec("REPLACE INTO %sconfig(name,value) VALUES(%Q,%Q)",
+                 globalFlag ? "global_" : "", zName, zValue);
+  if( globalFlag && g.repositoryOpen ){
+    db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
+  }
+  db_end_transaction(0);
 }
-void db_set(const char *zName, const char *zValue){
-  db_multi_exec("REPLACE INTO config(name,value) VALUES(%Q,%Q)", zName, zValue);
+int db_is_global(const char *zName){
+  if( g.configOpen ){
+    return db_exists("SELECT 1 FROM global_config WHERE name=%Q", zName);
+  }else{
+    return 0;
+  }
 }
 int db_get_int(const char *zName, int dflt){
-  return db_int(dflt, "SELECT value FROM config WHERE name=%Q", zName);
+  int v;
+  int rc;
+  if( g.repositoryOpen ){
+    Stmt q;
+    db_prepare(&q, "SELECT value FROM config WHERE name=%Q", zName);
+    rc = db_step(&q);
+    if( rc==SQLITE_ROW ){
+      v = db_column_int(&q, 0);
+    }
+    db_finalize(&q);
+  }else{
+    rc = SQLITE_DONE;
+  }
+  if( rc==SQLITE_DONE && g.configOpen ){
+    v = db_int(dflt, "SELECT value FROM global_config WHERE name=%Q", zName);
+  }
+  return v;
 }
-void db_set_int(const char *zName, int value){
-  db_multi_exec("REPLACE INTO config(name,value) VALUES(%Q,%d)", zName, value);
+void db_set_int(const char *zName, int value, int globalFlag){
+  db_begin_transaction();
+  db_multi_exec("REPLACE INTO %sconfig(name,value) VALUES(%Q,%d)",
+                globalFlag ? "global_" : "", zName, value);
+  if( globalFlag && g.repositoryOpen ){
+    db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
+  }
+  db_end_transaction(0);
 }
-char *db_global_get(const char *zName, const char *zDefault){
-  return db_text((char*)zDefault,
-                 "SELECT value FROM global_config WHERE name=%Q", zName);
+int db_get_boolean(const char *zName, int dflt){
+  static const char *azOn[] = { "on", "yes", "true", "1" };
+  static const char *azOff[] = { "off", "no", "false", "0" };
+  int i;
+  char *zVal = db_get(zName, dflt ? "on" : "off");
+  for(i=0; i<sizeof(azOn)/sizeof(azOn[0]); i++){
+    if( strcmp(zVal,azOn[i])==0 ) return 1;
+  }
+  for(i=0; i<sizeof(azOff)/sizeof(azOff[0]); i++){
+    if( strcmp(zVal,azOff[i])==0 ) return 0;
+  }
+  return dflt;
 }
-void db_global_set(const char *zName, const char *zValue){
-  db_multi_exec("REPLACE INTO global_config(name,value)"
-                "VALUES(%Q,%Q)", zName, zValue);
-}
-char *db_lget(const char *zName, const char *zDefault){
+char *db_lget(const char *zName, char *zDefault){
   return db_text((char*)zDefault,
                  "SELECT value FROM vvar WHERE name=%Q", zName);
 }
 void db_lset(const char *zName, const char *zValue){
@@ -848,8 +995,10 @@
 ** See also the "close" command.
 */
 void cmd_open(void){
   Blob path;
+  int vid;
+  static char *azNewArgv[] = { 0, "update", "--latest", 0 };
   if( g.argc!=3 ){
     usage("REPOSITORY-FILENAME");
   }
   if( db_open_local() ){
@@ -859,61 +1008,108 @@
   db_open_repository(blob_str(&path));
   db_init_database("./_FOSSIL_", zLocalSchema, (char*)0);
   db_open_local();
   db_lset("repository", blob_str(&path));
-  db_lset_int("checkout", 1);
+  vid = db_int(0, "SELECT pid FROM plink y"
+                  " WHERE NOT EXISTS(SELECT 1 FROM plink x WHERE x.cid=y.pid)");
+  if( vid==0 ){
+    db_lset_int("checkout", 1);
+  }else{
+    db_lset_int("checkout", vid);
+    g.argv = azNewArgv;
+    g.argc = 3;
+    update_cmd();
+  }
 }
 
 /*
-** COMMAND: config
-**
-** Usage: %fossil config NAME=VALUE ...
-**
-** List or change the global configuration settings.  With no arguments,
-** all settings are listed.  Arguments of simply NAME cause that setting
-** to be displayed.  Arguments of the form NAME=VALUE change the value of
-** a setting.  Arguments of the form NAME= delete a setting.
-**
-** Recognized settings include:
-**
-**   editor        Text editor command used for check-in comments.
-**
-**   clear-sign    Command used to clear-sign manifests at check-in.
-**                 The default is "gpg --clearsign -o ".
+** Print the value of a setting named zName
 */
-void cmd_config(void){
-  db_open_config();
-  if( g.argc>2 ){
-    int i;
-    db_begin_transaction();
-    for(i=2; i<g.argc; i++){
-      char *zName, *zValue;
-      int j;
+static void print_setting(const char *zName){
+  Stmt q;
+  db_prepare(&q,
+     "SELECT '(local)', value FROM config WHERE name=%Q"
+     " UNION ALL "
+     "SELECT '(global)', value FROM global_config WHERE name=%Q",
+     zName, zName
+  );
+  if( db_step(&q)==SQLITE_ROW ){
+    printf("%-20s %-8s %s\n", zName, db_column_text(&q, 0),
+        db_column_text(&q, 1));
+  }else{
+    printf("%-20s\n", zName);
+  }
+  db_finalize(&q);
+}
+
 
-      zName = mprintf("%s", g.argv[i]);
-      for(j=0; zName[j] && zName[j]!='='; j++){}
-      if( zName[j] ){
-        zName[j] = 0;
-        zValue = &zName[j+1];
-        if( zValue[0] ){
-          db_global_set(zName, zValue);
-        }else{
-          db_multi_exec("DELETE FROM global_config WHERE name=%Q", zName);
-        }
-      }
-      zValue = db_global_get(zName, 0);
-      if( zValue ){
-        printf("%s=%s\n", zName, zValue);
-      }else{
-        printf("%s is undefined\n", zName);
-      }
+/*
+** COMMAND: settings
+** %fossil setting ?PROPERTY? ?VALUE? ?-global?
+**
+** With no arguments, list all properties and their values.  With just
+** a property name, show the value of that property.  With a value
+** argument, change the property for the current repository.
+**
+**    autosync         If enabled, automatically pull prior to
+**                     commit or update and automatically push
+**                     after commit or tag or branch creation.
+**
+**    clearsign        Command used to clear-sign manifests at check-in.
+**                     The default is "gpg --clearsign -o ".
+**
+**    editor           Text editor command used for check-in comments.
+**
+**    localauth        If enabled, require that HTTP connections from
+**                     127.0.0.1 be authenticated by password.  If
+**                     false, all HTTP requests from localhost have
+**                     unrestricted access to the repository.
+**
+**    omitsign         When enabled, fossil will not attempt to sign any
+**                     commit with gpg. All commits will be unsigned.
+**
+**    safemerge        If enabled, when commit will cause a fork, the
+**                     commit will not abort with warning. Also update
+**                     will not be allowed if local changes exist.
+**
+**   diff-command      External command to run when performing a diff.
+**                     If undefined, the internal text diff will be used.
+**
+**   gdiff-command     External command to run when performing a graphical
+**                     diff. If undefined, text diff will be used.
+*/
+void setting_cmd(void){
+  static const char *azName[] = {
+    "autosync",
+    "clearsign",
+    "editor",
+    "localauth",
+    "omitsign",
+    "safemerge",
+    "diff-command",
+    "gdiff-command",
+  };
+  int i;
+  int globalFlag = find_option("global","g",0)!=0;
+  db_find_and_open_repository();
+  if( g.argc==2 ){
+    for(i=0; i<sizeof(azName)/sizeof(azName[0]); i++){
+      print_setting(azName[i]);
+    }
+  }else if( g.argc==3 || g.argc==4 ){
+    const char *zName = g.argv[2];
+    int n = strlen(zName);
+    for(i=0; i<sizeof(azName)/sizeof(azName[0]); i++){
+      if( strncmp(azName[i], zName, n)==0 ) break;
+    }
+    if( i>=sizeof(azName)/sizeof(azName[0]) ){
+      fossil_fatal("no such setting: %s", zName);
+    }
+    if( g.argc==4 ){
+      db_set(azName[i], g.argv[3], globalFlag);
+    }else{
+      print_setting(azName[i]);
     }
-    db_end_transaction(0);
   }else{
-    Stmt q;
-    db_prepare(&q, "SELECT name, value FROM global_config ORDER BY name");
-    while( db_step(&q)==SQLITE_ROW ){
-      printf("%s=%s\n", db_column_text(&q, 0), db_column_text(&q, 1));
-    }
-    db_finalize(&q);
+    usage("?PROPERTY? ?VALUE?");
   }
 }