Check-in [02a584f7f5]
Not logged in
Overview

SHA1 Hash:02a584f7f5e58808cc8260b2914345d8a72ed430
Date: 2009-08-26 18:25:48
User: drh
Comment:Add the --private option to the "fossil commit" command. This option creates a private branch which is never pushed.
Timelines: ancestors | descendants | both | trunk
Other Links: files | ZIP archive | manifest

Tags And Properties
Changes
[hide diffs]

Modified src/checkin.c from [e04164b91d] to [8b03f30dd7].

@@ -260,10 +260,17 @@
     "\n"
     "# Enter comments on this check-in.  Lines beginning with # are ignored.\n"
     "# The check-in comment follows wiki formatting rules.\n"
     "#\n"
   );
+  if( g.markPrivate ){
+    blob_append(&text,
+      "# PRIVATE BRANCH: This check-in will be private and will not sync to\n"
+      "# repositories.\n"
+      "#\n", -1
+    );
+  }
   status_report(&text, "# ");
   zEditor = db_get("editor", 0);
   if( zEditor==0 ){
     zEditor = getenv("VISUAL");
   }
@@ -385,17 +392,21 @@
 ** entries in the new branch when shown in the web timeline interface.
 **
 ** A check-in is not permitted to fork unless the --force or -f
 ** option appears.  A check-in is not allowed against a closed check-in.
 **
+** The --private option creates a private check-in that is never synced.
+** Children of private check-ins are automatically private.
+**
 ** Options:
 **
 **    --comment|-m COMMENT-TEXT
 **    --branch NEW-BRANCH-NAME
 **    --bgcolor COLOR
 **    --nosign
 **    --force|-f
+**    --private
 **
 */
 void commit_cmd(void){
   int rc;
   int vid, nrid, nvid;
@@ -424,21 +435,34 @@
   noSign = find_option("nosign",0,0)!=0;
   zComment = find_option("comment","m",1);
   forceFlag = find_option("force", "f", 0)!=0;
   zBranch = find_option("branch","b",1);
   zBgColor = find_option("bgcolor",0,1);
+  if( find_option("private",0,0) ){
+    g.markPrivate = 1;
+    if( zBranch==0 ) zBranch = "private";
+    if( zBgColor==0 ) zBgColor = "#fec084";  /* Orange */
+  }
   zDateOvrd = find_option("date-override",0,1);
   zUserOvrd = find_option("user-override",0,1);
   db_must_be_within_tree();
   noSign = db_get_boolean("omitsign", 0)|noSign;
   if( db_get_boolean("clearsign", 1)==0 ){ noSign = 1; }
   verify_all_options();
 
-  /*
-  ** Autosync if requested.
-  */
-  autosync(AUTOSYNC_PULL);
+  /* Get the ID of the parent manifest artifact */
+  vid = db_lget_int("checkout", 0);
+  if( content_is_private(vid) ){
+    g.markPrivate = 1;
+  }
+
+  /*
+  ** Autosync if autosync is enabled and this is not a private check-in.
+  */
+  if( !g.markPrivate ){
+    autosync(AUTOSYNC_PULL);
+  }
 
   /* There are two ways this command may be executed. If there are
   ** no arguments following the word "commit", then all modified files
   ** in the checked out directory are committed. If one or more arguments
   ** follows "commit", then only those files are committed.
@@ -482,17 +506,15 @@
     if( strlen(blob_str(&unmodified)) ){
       fossil_panic("file %s has not changed", blob_str(&unmodified));
     }
   }
 
-  vid = db_lget_int("checkout", 0);
-
   /*
   ** Do not allow a commit that will cause a fork unless the --force flag
-  ** is used.
-  */
-  if( zBranch==0 && forceFlag==0 && !is_a_leaf(vid) ){
+  ** is used or unless this is a private check-in.
+  */
+  if( zBranch==0 && forceFlag==0 && g.markPrivate==0 && !is_a_leaf(vid) ){
     fossil_fatal("would fork.  \"update\" first or use -f or --force.");
   }
 
   /*
   ** Do not allow a commit against a closed leaf
@@ -558,11 +580,11 @@
   blob_appendf(&manifest, "C %F\n", blob_str(&comment));
   zDate = db_text(0, "SELECT datetime('%q')", zDateOvrd ? zDateOvrd : "now");
   zDate[10] = 'T';
   blob_appendf(&manifest, "D %s\n", zDate);
   db_prepare(&q,
-    "SELECT pathname, uuid, origname"
+    "SELECT pathname, uuid, origname, blob.rid"
     "  FROM vfile JOIN blob ON vfile.mrid=blob.rid"
     " WHERE NOT deleted AND vfile.vid=%d"
     " ORDER BY 1", vid);
   blob_zero(&filename);
   blob_appendf(&filename, "%s/", g.zLocalRoot);
@@ -569,10 +591,11 @@
   nBasename = blob_size(&filename);
   while( db_step(&q)==SQLITE_ROW ){
     const char *zName = db_column_text(&q, 0);
     const char *zUuid = db_column_text(&q, 1);
     const char *zOrig = db_column_text(&q, 2);
+    int frid = db_column_int(&q, 3);
     const char *zPerm;
     blob_append(&filename, zName, -1);
     if( file_isexe(blob_str(&filename)) ){
       zPerm = " x";
     }else{
@@ -583,10 +606,11 @@
       blob_appendf(&manifest, "F %F %s%s\n", zName, zUuid, zPerm);
     }else{
       if( zPerm[0]==0 ){ zPerm = " w"; }
       blob_appendf(&manifest, "F %F %s%s %F\n", zName, zUuid, zPerm, zOrig);
     }
+    if( !g.markPrivate ) content_make_public(frid);
   }
   blob_reset(&filename);
   db_finalize(&q);
   zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
   blob_appendf(&manifest, "P %s", zUuid);
@@ -593,10 +617,11 @@
 
   db_prepare(&q2, "SELECT merge FROM vmerge WHERE id=:id");
   db_bind_int(&q2, ":id", 0);
   while( db_step(&q2)==SQLITE_ROW ){
     int mid = db_column_int(&q2, 0);
+    if( !g.markPrivate && content_is_private(mid) ) continue;
     zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid);
     if( zUuid ){
       blob_appendf(&manifest, " %s", zUuid);
       free(zUuid);
     }
@@ -629,11 +654,11 @@
   }
   blob_appendf(&manifest, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin);
   md5sum_blob(&manifest, &mcksum);
   blob_appendf(&manifest, "Z %b\n", &mcksum);
   zManifestFile = mprintf("%smanifest", g.zLocalRoot);
-  if( !noSign && clearsign(&manifest, &manifest) ){
+  if( !noSign && !g.markPrivate && clearsign(&manifest, &manifest) ){
     Blob ans;
     blob_zero(&ans);
     prompt_user("unable to sign manifest.  continue [y/N]? ", &ans);
     if( blob_str(&ans)[0]!='y' ){
       db_end_transaction(1);
@@ -702,11 +727,13 @@
   undo_reset();
 
   /* Commit */
   db_end_transaction(0);
 
-  autosync(AUTOSYNC_PUSH);
+  if( !g.markPrivate ){
+    autosync(AUTOSYNC_PUSH);
+  }
   if( count_nonbranch_children(vid)>1 ){
     printf("**** warning: a fork has occurred *****\n");
   }
 }
 

Modified src/clone.c from [f0402d199e] to [70ab216ac2].

@@ -58,10 +58,16 @@
       " VALUES('server-code', lower(hex(randomblob(20))));"
       "REPLACE INTO config(name,value)"
       " VALUES('last-sync-url', '%q');",
       g.urlCanonical
     );
+    db_multi_exec(
+       "DELETE FROM blob WHERE rid IN private;"
+       "DELETE FROM delta wHERE rid IN private;"
+       "DELETE FROM private;"
+    );
+    shun_artifacts();
     g.zLogin = db_text(0, "SELECT login FROM user WHERE cap LIKE '%%s%%'");
     if( g.zLogin==0 ){
       db_create_default_users(1);
     }
     printf("Repository cloned into %s\n", g.argv[3]);

Modified src/content.c from [6508de1118] to [ebd984372a].

@@ -450,10 +450,14 @@
     db_exec(&s1);
     rid = db_last_insert_rowid();
     if( !pBlob ){
       db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid);
     }
+    if( g.markPrivate ){
+      db_multi_exec("INSERT INTO private VALUES(%d)", rid);
+      markAsUnclustered = 0;
+    }
   }
   blob_reset(&cmpr);
 
   /* If the srcId is specified, then the data we just added is
   ** really a delta.  Record this fact in the delta table.
@@ -510,15 +514,19 @@
   db_static_prepare(&s2,
     "INSERT INTO phantom VALUES(:rid)"
   );
   db_bind_int(&s2, ":rid", rid);
   db_exec(&s2);
-  db_static_prepare(&s3,
-    "INSERT INTO unclustered VALUES(:rid)"
-  );
-  db_bind_int(&s3, ":rid", rid);
-  db_exec(&s3);
+  if( g.markPrivate ){
+    db_multi_exec("INSERT INTO private VALUES(%d)", rid);
+  }else{
+    db_static_prepare(&s3,
+      "INSERT INTO unclustered VALUES(:rid)"
+    );
+    db_bind_int(&s3, ":rid", rid);
+    db_exec(&s3);
+  }
   bag_insert(&contentCache.missing, rid);
   db_end_transaction(0);
   return rid;
 }
 
@@ -572,45 +580,84 @@
   rid = atoi(g.argv[2]);
   content_undelta(rid);
 }
 
 /*
+** Return true if the given RID is marked as PRIVATE.
+*/
+int content_is_private(int rid){
+  static Stmt s1;
+  int rc;
+  db_static_prepare(&s1,
+    "SELECT 1 FROM private WHERE rid=:rid"
+  );
+  db_bind_int(&s1, ":rid", rid);
+  rc = db_step(&s1);
+  db_reset(&s1);
+  return rc==SQLITE_ROW;
+}
+
+/*
+** Make sure an artifact is public.
+*/
+void content_make_public(int rid){
+  static Stmt s1;
+  db_static_prepare(&s1,
+    "DELETE FROM private WHERE rid=:rid"
+  );
+  db_bind_int(&s1, ":rid", rid);
+  db_exec(&s1);
+}
+
+/*
 ** Change the storage of rid so that it is a delta of srcid.
 **
 ** If rid is already a delta from some other place then no
 ** conversion occurs and this is a no-op unless force==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 srcid is a delta that depends on rid, then srcid is
 ** converted to undeltaed text.
 **
 ** If either rid or srcid contain less than 50 bytes, or if the
 ** resulting delta does not achieve a compression of at least 25% on
 ** its own the rid is left untouched.
-**
-** NOTE: IMHO the creation of the delta should be defered until after
-** the blob sizes have been checked. Doing it before the check as is
-** done now the code will generate a delta just to immediately throw
-** it away, wasting space and time.
 */
 void content_deltify(int rid, int srcid, int force){
   int s;
   Blob data, src, delta;
   Stmt s1, s2;
   if( srcid==rid ) return;
   if( !force && findSrcid(rid)>0 ) return;
+  if( content_is_private(srcid) && !content_is_private(rid) ){
+    return;
+  }
   s = srcid;
   while( (s = findSrcid(s))>0 ){
     if( s==rid ){
       content_undelta(srcid);
       break;
     }
   }
   content_get(srcid, &src);
+  if( blob_size(&src)<50 ){
+    blob_reset(&src);
+    return;
+  }
   content_get(rid, &data);
+  if( blob_size(&data)<50 ){
+    blob_reset(&src);
+    blob_reset(&data);
+    return;
+  }
   blob_delta_create(&src, &data, &delta);
-  if( blob_size(&src)>=50 && blob_size(&data)>=50 &&
-           blob_size(&delta) < blob_size(&data)*0.75 ){
+  if( blob_size(&delta) < blob_size(&data)*0.75 ){
     blob_compress(&delta, &delta);
     db_prepare(&s1, "UPDATE blob SET content=:data WHERE rid=%d", rid);
     db_prepare(&s2, "REPLACE INTO delta(rid,srcid)VALUES(%d,%d)", rid, srcid);
     db_bind_blob(&s1, ":data", &delta);
     db_begin_transaction();

Modified src/info.c from [4d0388b1d8] to [10f41cb27f].

@@ -1394,10 +1394,11 @@
       Blob cksum;
       blob_appendf(&ctrl, "U %F\n", g.zLogin);
       md5sum_blob(&ctrl, &cksum);
       blob_appendf(&ctrl, "Z %b\n", &cksum);
       db_begin_transaction();
+      g.markPrivate = content_is_private(rid);
       nrid = content_put(&ctrl, 0, 0);
       manifest_crosslink(nrid, &ctrl);
       db_end_transaction(0);
     }
     cgi_redirectf("ci?name=%d", rid);

Modified src/main.c from [0dbd180aa0] to [38aa437f8c].

@@ -81,12 +81,12 @@
   Th_Interp *interp;      /* The TH1 interpreter */
   FILE *httpIn;           /* Accept HTTP input from here */
   FILE *httpOut;          /* Send HTTP output here */
   int xlinkClusterOnly;   /* Set when cloning.  Only process clusters */
   int fTimeFormat;        /* 1 for UTC.  2 for localtime.  0 not yet selected */
-
   int *aCommitFile;       /* Array of files to be committed */
+  int markPrivate;        /* All new artifacts are private if true */
 
   int urlIsFile;          /* True if a "file:" url */
   int urlIsHttps;         /* True if a "https:" url */
   char *urlName;          /* Hostname for http: or filename for file: */
   char *urlHostname;      /* The HOST: parameter on http headers */

Modified src/shun.c from [42fc2023fe] to [95475202fe].

@@ -192,10 +192,12 @@
   db_finalize(&q);
   db_multi_exec(
      "DELETE FROM delta WHERE rid IN toshun;"
      "DELETE FROM blob WHERE rid IN toshun;"
      "DROP TABLE toshun;"
+     "DELETE FROM private "
+     " WHERE NOT EXISTS (SELECT 1 FROM blob WHERE rid=private.rid);"
   );
 }
 
 /*
 ** WEBPAGE: rcvfromlist

Modified src/tag.c from [48169c968e] to [11a50e87e6].

@@ -242,10 +242,11 @@
   }
   rid = name_to_rid(g.argv[3]);
   if( rid==0 ){
     fossil_fatal("no such object: %s", g.argv[3]);
   }
+  g.markPrivate = content_is_private(rid);
   zValue = g.argc==5 ? g.argv[4] : 0;
   db_begin_transaction();
   tag_insert(zTag, tagtype, zValue, -1, 0.0, rid);
   db_end_transaction(0);
 }
@@ -276,10 +277,11 @@
   if( name_to_uuid(&uuid, 9) ){
     fossil_fatal("%s", g.zErrMsg);
     return;
   }
   rid = name_to_rid(blob_str(&uuid));
+  g.markPrivate = content_is_private(rid);
   blob_zero(&ctrl);
 
 #if 0
   if( validate16(zTagname, strlen(zTagname)) ){
     fossil_fatal(

Modified src/xfer.c from [bb547deab3] to [e2586d1f32].

@@ -97,10 +97,13 @@
 ** If DELTASRC exists, then the CONTENT is a delta against the
 ** content of DELTASRC.
 **
 ** If any error occurs, write a message into pErr which has already
 ** be initialized to an empty string.
+**
+** Any artifact successfully received by this routine is considered to
+** be public and is therefore removed from the "private" table.
 */
 static void xfer_accept_file(Xfer *pXfer){
   int n;
   int rid;
   int srcid = 0;
@@ -128,10 +131,11 @@
     srcid = rid_from_uuid(&pXfer->aToken[2], 1);
     if( content_get(srcid, &src)==0 ){
       rid = content_put(&content, blob_str(&pXfer->aToken[1]), srcid);
       pXfer->nDanglingFile++;
       db_multi_exec("DELETE FROM phantom WHERE rid=%d", rid);
+      content_make_public(rid);
       return;
     }
     pXfer->nDeltaRcvd++;
     blob_delta_apply(&src, &content, &content);
     blob_reset(&src);
@@ -145,11 +149,11 @@
   rid = content_put(&content, blob_str(&hash), 0);
   blob_reset(&hash);
   if( rid==0 ){
     blob_appendf(&pXfer->err, "%s", g.zErrMsg);
   }else{
-    /* db_multi_exec("DELETE FROM phantom WHERE rid=%d", rid); */
+    content_make_public(rid);
     manifest_crosslink(rid, &content);
   }
   remote_has(rid);
 }
 
@@ -156,10 +160,12 @@
 /*
 ** Try to send a file as a delta against its parent.
 ** If successful, return the number of bytes in the delta.
 ** If we cannot generate an appropriate delta, then send
 ** nothing and return zero.
+**
+** Never send a delta against a private artifact.
 */
 static int send_delta_parent(
   Xfer *pXfer,            /* The transfer context */
   int rid,                /* record id of the file to send */
   Blob *pContent,         /* The content of the file to send */
@@ -184,11 +190,11 @@
   int srcId = 0;
 
   for(i=0; srcId==0 && i<count(azQuery); i++){
     srcId = db_int(0, azQuery[i], rid);
   }
-  if( srcId>0 && content_get(srcId, &src) ){
+  if( srcId>0 && !content_is_private(srcId) && content_get(srcId, &src) ){
     char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", srcId);
     blob_delta_create(&src, pContent, &delta);
     size = blob_size(&delta);
     if( size>=blob_size(pContent)-50 ){
       size = 0;
@@ -209,10 +215,12 @@
 /*
 ** Try to send a file as a native delta.
 ** If successful, return the number of bytes in the delta.
 ** If we cannot generate an appropriate delta, then send
 ** nothing and return zero.
+**
+** Never send a delta against a private artifact.
 */
 static int send_delta_native(
   Xfer *pXfer,            /* The transfer context */
   int rid,                /* record id of the file to send */
   Blob *pUuid             /* The UUID of the file to send */
@@ -220,11 +228,11 @@
   Blob src, delta;
   int size = 0;
   int srcId;
 
   srcId = db_int(0, "SELECT srcid FROM delta WHERE rid=%d", rid);
-  if( srcId>0 ){
+  if( srcId>0 && !content_is_private(srcId) ){
     blob_zero(&src);
     db_blob(&src, "SELECT uuid FROM blob WHERE rid=%d", srcId);
     if( uuid_is_shunned(blob_str(&src)) ){
       blob_reset(&src);
       return 0;
@@ -250,15 +258,20 @@
 ** The pUuid can be NULL in which case the correct UUID is computed
 ** from the rid.
 **
 ** Try to send the file as a native delta if nativeDelta is true, or
 ** as a parent delta if nativeDelta is false.
+**
+** It should never be the case that rid is a private artifact.  But
+** as a precaution, this routine does check on rid and if it is private
+** this routine becomes a no-op.
 */
 static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int nativeDelta){
   Blob content, uuid;
   int size = 0;
 
+  if( content_is_private(rid) ) 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);
@@ -308,16 +321,20 @@
   blob_reset(&uuid);
 }
 
 /*
 ** Send a gimme message for every phantom.
+**
+** It should not be possible to have a private phantom.  But just to be
+** sure, take care not to send any "gimme" messagse on private artifacts.
 */
 static void request_phantoms(Xfer *pXfer, int maxReq){
   Stmt q;
   db_prepare(&q,
     "SELECT uuid FROM phantom JOIN blob USING(rid)"
     " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
+    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
   );
   while( db_step(&q)==SQLITE_ROW && maxReq-- > 0 ){
     const char *zUuid = db_column_text(&q, 0);
     blob_appendf(pXfer->pOut, "gimme %s\n", zUuid);
     pXfer->nGimmeSent++;
@@ -401,11 +418,11 @@
 ** unsent table, all the right files will still get transferred.
 ** It just might require an extra round trip or two.
 */
 static void send_unsent(Xfer *pXfer){
   Stmt q;
-  db_prepare(&q, "SELECT rid FROM unsent");
+  db_prepare(&q, "SELECT rid FROM unsent EXCEPT SELECT rid FROM private");
   while( db_step(&q)==SQLITE_ROW ){
     int rid = db_column_int(&q, 0);
     send_file(pXfer, rid, 0, 0);
   }
   db_finalize(&q);
@@ -420,10 +437,17 @@
 */
 static void create_cluster(void){
   Blob cluster, cksum;
   Stmt q;
   int nUncl;
+
+  /* We should not ever get any private artifacts in the unclustered table.
+  ** But if we do (because of a bug) now is a good time to delete them. */
+  db_multi_exec(
+    "DELETE FROM unclustered WHERE rid IN (SELECT rid FROM private)"
+  );
+
   nUncl = db_int(0, "SELECT count(*) FROM unclustered"
                     " WHERE NOT EXISTS(SELECT 1 FROM phantom"
                                       " WHERE rid=unclustered.rid)");
   if( nUncl<100 ){
     return;
@@ -455,10 +479,11 @@
   Stmt q;
   int cnt = 0;
   db_prepare(&q,
     "SELECT uuid FROM unclustered JOIN blob USING(rid)"
     " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
+    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
   );
   while( db_step(&q)==SQLITE_ROW ){
     blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
     cnt++;
   }
@@ -472,10 +497,11 @@
 static void send_all(Xfer *pXfer){
   Stmt q;
   db_prepare(&q,
     "SELECT uuid FROM blob "
     " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
+    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
   );
   while( db_step(&q)==SQLITE_ROW ){
     blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
   }
   db_finalize(&q);
@@ -1036,11 +1062,13 @@
       if( xfer.nToken==2
        && blob_eq(&xfer.aToken[0], "igot")
        && blob_is_uuid(&xfer.aToken[1])
       ){
         int rid = rid_from_uuid(&xfer.aToken[1], 0);
-        if( rid==0 && (pullFlag || cloneFlag) ){
+        if( rid>0 ){
+          content_make_public(rid);
+        }else if( pullFlag || cloneFlag ){
           rid = content_new(blob_str(&xfer.aToken[1]));
           if( rid ) newPhantom = 1;
         }
         remote_has(rid);
       }else