Index: src/clone.c
==================================================================
--- src/clone.c
+++ src/clone.c
@@ -35,18 +35,21 @@
 ** admin user. This can be overridden using the -A|--admin-user
 ** parameter.
 **
 ** Options:
 **
-**    --admin-user|-A USERNAME
+**    --admin-user|-A USERNAME    Make USERNAME the administrator
+**    --private                   Also clone private branches 
 **
 */
 void clone_cmd(void){
   char *zPassword;
   const char *zDefaultUser;   /* Optional name of the default user */
   int nErr = 0;
+  int bPrivate;               /* Also clone private branches */
 
+  bPrivate = find_option("private",0,0)!=0;
   url_proxy_options();
   if( g.argc < 4 ){
     usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
   }
   db_open_config(0);
@@ -94,11 +97,11 @@
       "REPLACE INTO config(name,value)"
       " VALUES('server-code', lower(hex(randomblob(20))));"
     );
     url_enable_proxy(0);
     g.xlinkClusterOnly = 1;
-    nErr = client_sync(0,0,1,CONFIGSET_ALL,0);
+    nErr = client_sync(0,0,1,bPrivate,CONFIGSET_ALL,0);
     g.xlinkClusterOnly = 0;
     verify_cancel();
     db_end_transaction(0);
     db_close(1);
     if( nErr ){

Index: src/configure.c
==================================================================
--- src/configure.c
+++ src/configure.c
@@ -464,13 +464,13 @@
     url_parse(zServer);
     if( g.urlPasswd==0 && zPw ) g.urlPasswd = mprintf("%s", zPw);
     user_select();
     url_enable_proxy("via proxy: ");
     if( strncmp(zMethod, "push", n)==0 ){
-      client_sync(0,0,0,0,mask);
+      client_sync(0,0,0,0,0,mask);
     }else{
-      client_sync(0,0,0,mask,0);
+      client_sync(0,0,0,0,mask,0);
     }
   }else
   if( strncmp(zMethod, "reset", n)==0 ){
     int mask, i;
     char *zBackup;

Index: src/http_transport.c
==================================================================
--- src/http_transport.c
+++ src/http_transport.c
@@ -302,11 +302,11 @@
   if( g.urlIsSsh ){
     fprintf(sshOut, "\n\n");
   }else if( g.urlIsFile ){
     char *zCmd;
     fclose(transport.pFile);
-    zCmd = mprintf("\"%s\" http \"%s\" \"%s\" \"%s\" 127.0.0.1",
+    zCmd = mprintf("\"%s\" http \"%s\" \"%s\" \"%s\" 127.0.0.1 --localauth",
        fossil_nameofexe(), g.urlName, transport.zOutFile, transport.zInFile
     );
     fossil_system(zCmd);
     free(zCmd);
     transport.pFile = fopen(transport.zInFile, "rb");

Index: src/login.c
==================================================================
--- src/login.c
+++ src/login.c
@@ -374,11 +374,11 @@
    && db_get_int("localauth",0)==0
    && P("HTTPS")==0
   ){
     uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
     g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
-    zCap = "s";
+    zCap = "sx";
     g.noPswd = 1;
     sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "localhost");
   }
 
   /* Check the login cookie to see if it matches a known valid user.
@@ -535,10 +535,11 @@
       case 'w':   g.okWrTkt = g.okRdTkt = g.okNewTkt = 
                   g.okApndTkt = 1;                              break;
       case 'c':   g.okApndTkt = 1;                              break;
       case 't':   g.okTktFmt = 1;                               break;
       case 'b':   g.okAttach = 1;                               break;
+      case 'x':   g.okPrivate = 1;                              break;
 
       /* The "u" privileges is a little different.  It recursively 
       ** inherits all privileges of the user named "reader" */
       case 'u': {
         if( zUser==0 ){

Index: src/main.c
==================================================================
--- src/main.c
+++ src/main.c
@@ -134,10 +134,11 @@
   int okWrTkt;            /* w: make changes to tickets via web */
   int okAttach;           /* b: add attachments */
   int okTktFmt;           /* t: create new ticket report formats */
   int okRdAddr;           /* e: read email addresses or other private data */
   int okZip;              /* z: download zipped artifact via /zip URL */
+  int okPrivate;          /* x: can send and receive private content */
 
   /* For defense against Cross-site Request Forgery attacks */
   char zCsrfToken[12];    /* Value of the anti-CSRF token */
   int okCsrf;             /* Anti-CSRF token is present and valid */
 

Index: src/rebuild.c
==================================================================
--- src/rebuild.c
+++ src/rebuild.c
@@ -540,19 +540,20 @@
   }
 }
 
 /*
 ** COMMAND: scrub
-** %fossil scrub [--verily] [--force] [REPOSITORY]
+** %fossil scrub [--verily] [--force] [--private] [REPOSITORY]
 **
 ** The command removes sensitive information (such as passwords) from a
 ** repository so that the respository can be sent to an untrusted reader.
 **
 ** By default, only passwords are removed.  However, if the --verily option
 ** is added, then private branches, concealed email addresses, IP
 ** addresses of correspondents, and similar privacy-sensitive fields
-** are also purged.
+** are also purged.  If the --private option is used, then only private
+** branches are removed and all other information is left intact.
 **
 ** This command permanently deletes the scrubbed information.  The effects
 ** of this command are irreversible.  Use with caution.
 **
 ** The user is prompted to confirm the scrub unless the --force option
@@ -559,10 +560,11 @@
 ** is used.
 */
 void scrub_cmd(void){
   int bVerily = find_option("verily",0,0)!=0;
   int bForce = find_option("force", "f", 0)!=0;
+  int privateOnly = find_option("private",0,0)!=0;
   int bNeedRebuild = 0;
   if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
   if( g.argc==2 ){
     db_must_be_within_tree();
   }else{
@@ -569,30 +571,37 @@
     db_open_repository(g.argv[2]);
   }
   if( !bForce ){
     Blob ans;
     blob_zero(&ans);
-    prompt_user("Scrubbing the repository will permanently remove user\n"
-                "passwords and other information. Changes cannot be undone.\n"
-                "Continue (y/N)? ", &ans);
+    prompt_user("Scrubbing the repository will permanently information.\n"
+                "Changes cannot be undone.  Continue (y/N)? ", &ans);
     if( blob_str(&ans)[0]!='y' ){
       fossil_exit(1);
     }
   }
   db_begin_transaction();
-  db_multi_exec(
-    "UPDATE user SET pw='';"
-    "DELETE FROM config WHERE name GLOB 'last-sync-*';"
-  );
-  if( bVerily ){
+  if( privateOnly || bVerily ){
     bNeedRebuild = db_exists("SELECT 1 FROM private");
     db_multi_exec(
-      "DELETE FROM concealed;"
-      "UPDATE rcvfrom SET ipaddr='unknown';"
-      "UPDATE user SET photo=NULL, info='';"
-      "INSERT INTO shun SELECT uuid FROM blob WHERE rid IN private;"
+      "DELETE FROM blob WHERE rid IN private;"
+      "DELETE FROM delta WHERE rid IN private;"
+      "DELETE FROM private;"
+    );
+  }
+  if( !privateOnly ){
+    db_multi_exec(
+      "UPDATE user SET pw='';"
+      "DELETE FROM config WHERE name GLOB 'last-sync-*';"
     );
+    if( bVerily ){
+      db_multi_exec(
+        "DELETE FROM concealed;"
+        "UPDATE rcvfrom SET ipaddr='unknown';"
+        "UPDATE user SET photo=NULL, info='';"
+      );
+    }
   }
   if( !bNeedRebuild ){
     db_end_transaction(0);
     db_multi_exec("VACUUM;");
   }else{

Index: src/setup.c
==================================================================
--- src/setup.c
+++ src/setup.c
@@ -122,11 +122,10 @@
   @   <th class="usetupListCon"  style="text-align: left;">Contact&nbsp;Info</th>
   @ </tr>
   db_prepare(&s, "SELECT uid, login, cap, info FROM user ORDER BY login");
   while( db_step(&s)==SQLITE_ROW ){
     const char *zCap = db_column_text(&s, 2);
-    if( strstr(zCap, "s") ) zCap = "s";
     @ <tr>
     @ <td class="usetupListUser" style="text-align: right;padding-right: 20px;white-space:nowrap;">
     if( g.okAdmin && (zCap[0]!='s' || g.okSetup) ){
       @ <a href="setup_uedit?id=%d(db_column_int(&s,0))">
     }
@@ -188,10 +187,12 @@
      @ <tr><td valign="top"><b>v</b></td>
      @   <td><i>Developer:</i> Inherit privileges of
      @   user <tt>developer</tt></td></tr>
      @ <tr><td valign="top"><b>w</b></td>
      @   <td><i>Write-Tkt:</i> Edit tickets</td></tr>
+     @ <tr><td valign="top"><b>x</b></td>
+     @   <td><i>Private:</i> Push and/or pull private branches</td></tr>
      @ <tr><td valign="top"><b>z</b></td>
      @   <td><i>Zip download:</i> Download a baseline via the
      @   <tt>/zip</tt> URL even without 
      @    check<span class="capability">o</span>ut
      @    and <span class="capability">h</span>istory permissions</td></tr>
@@ -243,11 +244,11 @@
 */
 void user_edit(void){
   const char *zId, *zLogin, *zInfo, *zCap, *zPw;
   char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap;
   char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae;
-  char *oat, *oau, *oav, *oab, *oaz;
+  char *oat, *oau, *oav, *oab, *oax, *oaz;
   const char *inherit[128];
   int doWrite;
   int uid;
   int higherUser = 0;  /* True if user being edited is SETUP and the */
                        /* user doing the editing is ADMIN.  Disallow editing */
@@ -300,10 +301,11 @@
     int ah = P("ah")!=0;
     int ag = P("ag")!=0;
     int at = P("at")!=0;
     int au = P("au")!=0;
     int av = P("av")!=0;
+    int ax = P("ax")!=0;
     int az = P("az")!=0;
     if( aa ){ zCap[i++] = 'a'; }
     if( ab ){ zCap[i++] = 'b'; }
     if( ac ){ zCap[i++] = 'c'; }
     if( ad ){ zCap[i++] = 'd'; }
@@ -322,10 +324,11 @@
     if( as ){ zCap[i++] = 's'; }
     if( at ){ zCap[i++] = 't'; }
     if( au ){ zCap[i++] = 'u'; }
     if( av ){ zCap[i++] = 'v'; }
     if( aw ){ zCap[i++] = 'w'; }
+    if( ax ){ zCap[i++] = 'x'; }
     if( az ){ zCap[i++] = 'z'; }
 
     zCap[i] = 0;
     zPw = P("pw");
     zLogin = P("login");
@@ -360,11 +363,11 @@
   zLogin = "";
   zInfo = "";
   zCap = "";
   zPw = "";
   oaa = oab = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam =
-        oan = oao = oap = oar = oas = oat = oau = oav = oaw = oaz = "";
+        oan = oao = oap = oar = oas = oat = oau = oav = oaw = oax = oaz = "";
   if( uid ){
     zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
     zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
     zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
     zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid);
@@ -387,10 +390,11 @@
     if( strchr(zCap, 's') ) oas = " checked=\"checked\"";
     if( strchr(zCap, 't') ) oat = " checked=\"checked\"";
     if( strchr(zCap, 'u') ) oau = " checked=\"checked\"";
     if( strchr(zCap, 'v') ) oav = " checked=\"checked\"";
     if( strchr(zCap, 'w') ) oaw = " checked=\"checked\"";
+    if( strchr(zCap, 'x') ) oax = " checked=\"checked\"";
     if( strchr(zCap, 'z') ) oaz = " checked=\"checked\"";
   }
 
   /* figure out inherited permissions */
   memset(inherit, 0, sizeof(inherit));
@@ -484,10 +488,11 @@
   @    <input type="checkbox" name="ar"%s(oar) />%s(B('r'))Read Ticket<br />
   @    <input type="checkbox" name="an"%s(oan) />%s(B('n'))New Ticket<br />
   @    <input type="checkbox" name="ac"%s(oac) />%s(B('c'))Append Ticket<br />
   @    <input type="checkbox" name="aw"%s(oaw) />%s(B('w'))Write Ticket<br />
   @    <input type="checkbox" name="at"%s(oat) />%s(B('t'))Ticket Report<br />
+  @    <input type="checkbox" name="ax"%s(oax) />%s(B('x'))Private<br />
   @    <input type="checkbox" name="az"%s(oaz) />%s(B('z'))Download Zip
   @   </td>
   @ </tr>
   @ <tr>
   @   <td align="right">Password:</td>

Index: src/sync.c
==================================================================
--- src/sync.c
+++ src/sync.c
@@ -78,11 +78,11 @@
     configSync = CONFIGSET_SHUN;
   }
 #endif
   printf("Autosync:  %s\n", g.urlCanonical);
   url_enable_proxy("via proxy: ");
-  rc = client_sync((flags & AUTOSYNC_PUSH)!=0, 1, 0, configSync, 0);
+  rc = client_sync((flags & AUTOSYNC_PUSH)!=0, 1, 0, 0, configSync, 0);
   if( rc ) fossil_warning("Autosync failed");
   return rc;
 }
 
 /*
@@ -89,16 +89,17 @@
 ** This routine processes the command-line argument for push, pull,
 ** and sync.  If a command-line argument is given, that is the URL
 ** of a server to sync against.  If no argument is given, use the
 ** most recently synced URL.  Remember the current URL for next time.
 */
-static int process_sync_args(void){
+static void process_sync_args(int *pConfigSync, int *pPrivate){
   const char *zUrl = 0;
   const char *zPw = 0;
   int configSync = 0;
   int urlOptional = find_option("autourl",0,0)!=0;
   g.dontKeepUrl = find_option("once",0,0)!=0;
+  *pPrivate = find_option("private",0,0)!=0;
   url_proxy_options();
   db_find_and_open_repository(0, 0);
   db_open_config(0);
   if( g.argc==2 ){
     zUrl = db_get("last-sync-url", 0);
@@ -126,11 +127,11 @@
   user_select();
   if( g.argc==2 ){
     printf("Server:    %s\n", g.urlCanonical);
   }
   url_enable_proxy("via proxy: ");
-  return configSync;
+  *pConfigSync = configSync;
 }
 
 /*
 ** COMMAND: pull
 **
@@ -145,16 +146,21 @@
 **
 ** The URL specified normally becomes the new "remote-url" used for
 ** subsequent push, pull, and sync operations.  However, the "--once"
 ** command-line option makes the URL a one-time-use URL that is not
 ** saved.
+**
+** Use the --private option to pull private branches from the
+** remote repository.
 **
 ** See also: clone, push, sync, remote-url
 */
 void pull_cmd(void){
-  int syncFlags = process_sync_args();
-  client_sync(0,1,0,syncFlags,0);
+  int syncFlags;
+  int bPrivate;
+  process_sync_args(&syncFlags, &bPrivate);
+  client_sync(0,1,0,bPrivate,syncFlags,0);
 }
 
 /*
 ** COMMAND: push
 **
@@ -169,16 +175,21 @@
 **
 ** The URL specified normally becomes the new "remote-url" used for
 ** subsequent push, pull, and sync operations.  However, the "--once"
 ** command-line option makes the URL a one-time-use URL that is not
 ** saved.
+**
+** Use the --private option to push private branches to the
+** remote repository.
 **
 ** See also: clone, pull, sync, remote-url
 */
 void push_cmd(void){
-  process_sync_args();
-  client_sync(1,0,0,0,0);
+  int syncFlags;
+  int bPrivate;
+  process_sync_args(&syncFlags, &bPrivate);
+  client_sync(1,0,0,bPrivate,0,0);
 }
 
 
 /*
 ** COMMAND: sync
@@ -199,16 +210,21 @@
 **
 ** The URL specified normally becomes the new "remote-url" used for
 ** subsequent push, pull, and sync operations.  However, the "--once"
 ** command-line option makes the URL a one-time-use URL that is not
 ** saved.
+**
+** Use the --private option to sync private branches with the
+** remote repository.
 **
 ** See also:  clone, push, pull, remote-url
 */
 void sync_cmd(void){
-  int syncFlags = process_sync_args();
-  client_sync(1,1,0,syncFlags,0);
+  int syncFlags;
+  int bPrivate;
+  process_sync_args(&syncFlags, &bPrivate);
+  client_sync(1,1,0,bPrivate,syncFlags,0);
 }
 
 /*
 ** COMMAND: remote-url
 **

Index: src/tag.c
==================================================================
--- src/tag.c
+++ src/tag.c
@@ -204,10 +204,16 @@
       break;
     }
     case TAG_USER: {
       zCol = "euser";
       break;
+    }
+    case TAG_PRIVATE: {
+      db_multi_exec(
+        "INSERT OR IGNORE INTO private(rid) VALUES(%d);",
+        rid
+      );
     }
   }
   if( zCol ){
     db_multi_exec("UPDATE event SET %s=%Q WHERE objid=%d", zCol, zValue, rid);
     if( tagid==TAG_COMMENT ){

Index: src/xfer.c
==================================================================
--- src/xfer.c
+++ src/xfer.c
@@ -38,10 +38,12 @@
   int nDeltaSent;     /* Number of deltas sent */
   int nFileRcvd;      /* Number of files received */
   int nDeltaRcvd;     /* Number of deltas received */
   int nDanglingFile;  /* Number of dangling deltas received */
   int mxSend;         /* Stop sending "file" with pOut reaches this size */
+  u8 syncPrivate;     /* True to enable syncing private content */
+  u8 nextIsPrivate;   /* If true, next "file" received is a private */
 };
 
 
 /*
 ** The input blob contains a UUID.  Convert it into a record ID.
@@ -49,11 +51,11 @@
 ** phantomize is true.
 **
 ** Compare to uuid_to_rid().  This routine takes a blob argument
 ** and does less error checking.
 */
-static int rid_from_uuid(Blob *pUuid, int phantomize){
+static int rid_from_uuid(Blob *pUuid, int phantomize, int isPrivate){
   static Stmt q;
   int rid;
 
   db_static_prepare(&q, "SELECT rid FROM blob WHERE uuid=:uuid");
   db_bind_str(&q, ":uuid", pUuid);
@@ -62,11 +64,11 @@
   }else{
     rid = 0;
   }
   db_reset(&q);
   if( rid==0 && phantomize ){
-    rid = content_new(blob_str(pUuid), 0);
+    rid = content_new(blob_str(pUuid), isPrivate);
   }
   return rid;
 }
 
 /*
@@ -106,11 +108,14 @@
 static void xfer_accept_file(Xfer *pXfer, int cloneFlag){
   int n;
   int rid;
   int srcid = 0;
   Blob content, hash;
+  int isPriv;
   
+  isPriv = pXfer->nextIsPrivate;
+  pXfer->nextIsPrivate = 0;
   if( pXfer->nToken<3 
    || pXfer->nToken>4
    || !blob_is_uuid(&pXfer->aToken[1])
    || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &n)
    || n<0
@@ -123,32 +128,38 @@
   blob_zero(&hash);
   blob_extract(pXfer->pIn, n, &content);
   if( !cloneFlag && uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
     /* Ignore files that have been shunned */
     return;
+  }
+  if( isPriv && !g.okPrivate ){
+    /* Do not accept private files if not authorized */
+    return;
   }
   if( cloneFlag ){
     if( pXfer->nToken==4 ){
-      srcid = rid_from_uuid(&pXfer->aToken[2], 1);
+      srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
       pXfer->nDeltaRcvd++;
     }else{
       srcid = 0;
       pXfer->nFileRcvd++;
     }
-    rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid, 0, 0);
+    rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid,
+                         0, isPriv);
     remote_has(rid);
     blob_reset(&content);
     return;
   }
   if( pXfer->nToken==4 ){
     Blob src, next;
-    srcid = rid_from_uuid(&pXfer->aToken[2], 1);
+    srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
     if( content_get(srcid, &src)==0 ){
-      rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid, 0, 0);
+      rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid,
+                           0, isPriv);
       pXfer->nDanglingFile++;
       db_multi_exec("DELETE FROM phantom WHERE rid=%d", rid);
-      content_make_public(rid);
+      if( !isPriv ) content_make_public(rid);
       return;
     }
     pXfer->nDeltaRcvd++;
     blob_delta_apply(&src, &content, &next);
     blob_reset(&src);
@@ -159,17 +170,17 @@
   }
   sha1sum_blob(&content, &hash);
   if( !blob_eq_str(&pXfer->aToken[1], blob_str(&hash), -1) ){
     blob_appendf(&pXfer->err, "content does not match sha1 hash");
   }
-  rid = content_put_ex(&content, blob_str(&hash), 0, 0, 0);
+  rid = content_put_ex(&content, blob_str(&hash), 0, 0, isPriv);
   blob_reset(&hash);
   if( rid==0 ){
     blob_appendf(&pXfer->err, "%s", g.zErrMsg);
     blob_reset(&content);
   }else{
-    content_make_public(rid);
+    if( !isPriv ) content_make_public(rid);
     manifest_crosslink(rid, &content);
   }
   assert( blob_is_reset(&content) );
   remote_has(rid);
 }
@@ -201,11 +212,14 @@
   int szC;   /* CSIZE */
   int szU;   /* USIZE */
   int rid;
   int srcid = 0;
   Blob content;
+  int isPriv;
   
+  isPriv = pXfer->nextIsPrivate;
+  pXfer->nextIsPrivate = 0;
   if( pXfer->nToken<4 
    || pXfer->nToken>5
    || !blob_is_uuid(&pXfer->aToken[1])
    || !blob_is_int(&pXfer->aToken[pXfer->nToken-2], &szU)
    || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &szC)
@@ -212,25 +226,30 @@
    || szC<0 || szU<0
    || (pXfer->nToken==5 && !blob_is_uuid(&pXfer->aToken[2]))
   ){
     blob_appendf(&pXfer->err, "malformed cfile line");
     return;
+  }
+  if( isPriv && !g.okPrivate ){
+    /* Do not accept private files if not authorized */
+    return;
   }
   blob_zero(&content);
   blob_extract(pXfer->pIn, szC, &content);
   if( uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
     /* Ignore files that have been shunned */
     return;
   }
   if( pXfer->nToken==5 ){
-    srcid = rid_from_uuid(&pXfer->aToken[2], 1);
+    srcid = rid_from_uuid(&pXfer->aToken[2], 1, isPriv);
     pXfer->nDeltaRcvd++;
   }else{
     srcid = 0;
     pXfer->nFileRcvd++;
   }
-  rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid, szC, 0);
+  rid = content_put_ex(&content, blob_str(&pXfer->aToken[1]), srcid,
+                       szC, isPriv);
   remote_has(rid);
   blob_reset(&content);
 }
 
 /*
@@ -242,10 +261,11 @@
 ** 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 */
+  int isPrivate,          /* True if rid is a private artifact */
   Blob *pContent,         /* The content of the file to send */
   Blob *pUuid             /* The UUID of the file to send */
 ){
   static const char *azQuery[] = {
     "SELECT pid FROM plink x"
@@ -266,22 +286,25 @@
   int srcId = 0;
 
   for(i=0; srcId==0 && i<count(azQuery); i++){
     srcId = db_int(0, azQuery[i], rid);
   }
-  if( srcId>0 && !content_is_private(srcId) && content_get(srcId, &src) ){
+  if( srcId>0
+   && (pXfer->syncPrivate || !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;
     }else if( uuid_is_shunned(zUuid) ){
       size = 0;
     }else{
+      if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
       blob_appendf(pXfer->pOut, "file %b %s %d\n", pUuid, zUuid, size);
       blob_append(pXfer->pOut, blob_buffer(&delta), size);
-      /* blob_appendf(pXfer->pOut, "\n", 1); */
     }
     blob_reset(&delta);
     free(zUuid);
     blob_reset(&src);
   }
@@ -297,27 +320,31 @@
 ** 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 */
+  int isPrivate,          /* True if rid is a private artifact */
   Blob *pUuid             /* The UUID of the file to send */
 ){
   Blob src, delta;
   int size = 0;
   int srcId;
 
   srcId = db_int(0, "SELECT srcid FROM delta WHERE rid=%d", rid);
-  if( srcId>0 && !content_is_private(srcId) ){
+  if( srcId>0
+   && (pXfer->syncPrivate || !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;
     }
     blob_zero(&delta);
     db_blob(&delta, "SELECT content FROM blob WHERE rid=%d", rid);
     blob_uncompress(&delta, &delta);
+    if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
     blob_appendf(pXfer->pOut, "file %b %b %d\n",
                 pUuid, &src, blob_size(&delta));
     blob_append(pXfer->pOut, blob_buffer(&delta), blob_size(&delta));
     size = blob_size(&delta);
     blob_reset(&delta);
@@ -342,12 +369,13 @@
 ** this routine becomes a no-op.
 */
 static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int nativeDelta){
   Blob content, uuid;
   int size = 0;
+  int isPriv = content_is_private(rid);
 
-  if( content_is_private(rid) ) return;
+  if( pXfer->syncPrivate==0 && isPriv ) 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);
@@ -365,38 +393,45 @@
   if( uuid_is_shunned(blob_str(pUuid)) ){
     blob_reset(&uuid);
     return;
   }
   if( pXfer->mxSend<=blob_size(pXfer->pOut) ){
-    blob_appendf(pXfer->pOut, "igot %b\n", pUuid);
+    const char *zFormat = isPriv ? "igot %b 1\n" : "igot %b\n";
+    blob_appendf(pXfer->pOut, zFormat, pUuid);
     pXfer->nIGotSent++;
     blob_reset(&uuid);
     return;
   }
   if( nativeDelta ){
-    size = send_delta_native(pXfer, rid, pUuid);
+    size = send_delta_native(pXfer, rid, isPriv, pUuid);
     if( size ){
       pXfer->nDeltaSent++;
     }
   }
   if( size==0 ){
     content_get(rid, &content);
 
     if( !nativeDelta && blob_size(&content)>100 ){
-      size = send_delta_parent(pXfer, rid, &content, pUuid);
+      size = send_delta_parent(pXfer, rid, isPriv, &content, pUuid);
     }
     if( size==0 ){
       int size = blob_size(&content);
+      if( isPriv ) blob_append(pXfer->pOut, "private\n", -1);
       blob_appendf(pXfer->pOut, "file %b %d\n", pUuid, size);
       blob_append(pXfer->pOut, blob_buffer(&content), size);
       pXfer->nFileSent++;
     }else{
       pXfer->nDeltaSent++;
     }
   }
   remote_has(rid);
   blob_reset(&uuid);
+#if 0
+  if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){
+    blob_appendf(pXfer->pOut, "\n", 1);
+  }
+#endif
 }
 
 /*
 ** Send the file identified by rid as a compressed artifact.  Basically,
 ** send the content exactly as it appears in the BLOB table using 
@@ -407,57 +442,62 @@
   const char *zUuid;
   const char *zDelta;
   int szU;
   int szC;
   int rc;
+  int isPrivate;
   static Stmt q1;
 
+  isPrivate = content_is_private(rid);
+  if( isPrivate && pXfer->syncPrivate==0 ) return;
   db_static_prepare(&q1,
     "SELECT uuid, size, content,"
          "  (SELECT uuid FROM delta, blob"
          "    WHERE delta.rid=:rid AND delta.srcid=blob.rid)"
     " FROM blob"
     " WHERE rid=:rid"
     "   AND size>=0"
     "   AND uuid NOT IN shun"
-    "   AND rid NOT IN private",
-    rid
   );
   db_bind_int(&q1, ":rid", rid);
   rc = db_step(&q1);
   if( rc==SQLITE_ROW ){
     zUuid = db_column_text(&q1, 0);
     szU = db_column_int(&q1, 1);
     szC = db_column_bytes(&q1, 2);
     zContent = db_column_raw(&q1, 2);
     zDelta = db_column_text(&q1, 3);
+    if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1);
     blob_appendf(pXfer->pOut, "cfile %s ", zUuid);
-    if( zDelta ){
+     if( zDelta ){
       blob_appendf(pXfer->pOut, "%s ", zDelta);
       pXfer->nDeltaSent++;
     }else{
       pXfer->nFileSent++;
     }
     blob_appendf(pXfer->pOut, "%d %d\n", szU, szC);
     blob_append(pXfer->pOut, zContent, szC);
-    blob_append(pXfer->pOut, "\n", 1);
+    if( blob_buffer(pXfer->pOut)[blob_size(pXfer->pOut)-1]!='\n' ){
+      blob_appendf(pXfer->pOut, "\n", 1);
+    }
   }
   db_reset(&q1);
 }
 
 /*
 ** 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.
+** Except: do not request shunned artifacts.  And do not request
+** private artifacts if we are not doing a private transfer.
 */
 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)"
+    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid) %s",
+    (pXfer->syncPrivate ? "" :
+         "   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++;
@@ -641,10 +681,27 @@
       content_put(&cluster);
       blob_reset(&cluster);
     }
   }
 }
+
+/*
+** Send igot messages for every private artifact
+*/
+static int send_private(Xfer *pXfer){
+  int cnt = 0;
+  Stmt q;
+  if( pXfer->syncPrivate ){
+    db_prepare(&q, "SELECT uuid FROM private JOIN blob USING(rid)");
+    while( db_step(&q)==SQLITE_ROW ){
+      blob_appendf(pXfer->pOut, "igot %s 1\n", db_column_text(&q,0));
+      cnt++;
+    }
+    db_finalize(&q);
+  }
+  return cnt;
+}
 
 /*
 ** Send an igot message for every entry in unclustered table.
 ** Return the number of cards sent.
 */
@@ -652,12 +709,12 @@
   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)"
     "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
+    "   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++;
   }
@@ -704,10 +761,19 @@
                  blob_size(&content), blob_str(&content));
     blob_reset(&content);
   }
 }
 
+
+/*
+** Called when there is an attempt to transfer private content to and
+** from a server without authorization.
+*/
+static void server_private_xfer_not_authorized(void){
+  @ error not\sauthorized\sto\ssync\sprivate\scontent
+}
+
 
 /*
 ** If this variable is set, disable login checks.  Used for debugging
 ** only.
 */
@@ -757,10 +823,11 @@
      "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
   );
   manifest_crosslink_begin();
   while( blob_line(xfer.pIn, &xfer.line) ){
     if( blob_buffer(&xfer.line)[0]=='#' ) continue;
+    if( blob_size(&xfer.line)==0 ) continue;
     xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
 
     /*   file UUID SIZE \n CONTENT
     **   file UUID DELTASRC SIZE \n CONTENT
     **
@@ -811,27 +878,34 @@
      && xfer.nToken==2
      && blob_is_uuid(&xfer.aToken[1])
     ){
       nGimme++;
       if( isPull ){
-        int rid = rid_from_uuid(&xfer.aToken[1], 0);
+        int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
         if( rid ){
           send_file(&xfer, rid, &xfer.aToken[1], deltaFlag);
         }
       }
     }else
 
-    /*   igot UUID
+    /*   igot UUID ?ISPRIVATE?
     **
-    ** Client announces that it has a particular file.
+    ** Client announces that it has a particular file.  If the ISPRIVATE
+    ** argument exists and is non-zero, then the file is a private file.
     */
-    if( xfer.nToken==2
+    if( xfer.nToken>=2
      && blob_eq(&xfer.aToken[0], "igot")
      && blob_is_uuid(&xfer.aToken[1])
     ){
       if( isPush ){
-        rid_from_uuid(&xfer.aToken[1], 1);
+        if( xfer.nToken==2 || blob_eq(&xfer.aToken[2],"1")==0 ){
+          rid_from_uuid(&xfer.aToken[1], 1, 0);
+        }else if( g.okPrivate ){
+          rid_from_uuid(&xfer.aToken[1], 1, 1);
+        }else{
+          server_private_xfer_not_authorized();
+        }
       }
     }else
   
     
     /*    pull  SERVERCODE  PROJECTCODE
@@ -929,11 +1003,11 @@
     */
     if( blob_eq(&xfer.aToken[0], "login")
      && xfer.nToken==4
     ){
       if( disableLogin ){
-        g.okRead = g.okWrite = 1;
+        g.okRead = g.okWrite = g.okPrivate = 1;
       }else{
         if( check_tail_hash(&xfer.aToken[2], xfer.pIn)
          || check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3])
         ){
           cgi_reset_content();
@@ -1029,10 +1103,48 @@
     */
     if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
       /* Process the cookie */
     }else
 
+
+    /*    private
+    **
+    ** This card indicates that the next "file" or "cfile" will contain
+    ** private content.
+    */
+    if( blob_eq(&xfer.aToken[0], "private") ){
+      if( !g.okPrivate ){
+        server_private_xfer_not_authorized();
+      }else{
+        xfer.nextIsPrivate = 1;
+      }
+    }else
+
+
+    /*    pragma NAME VALUE...
+    **
+    ** The client issue pragmas to try to influence the behavior of the
+    ** server.  These are requests only.  Unknown pragmas are silently
+    ** ignored.
+    */
+    if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
+      /*   pragma send-private
+      **
+      ** If the user has the "x" privilege (which must be set explicitly -
+      ** it is not automatic with "a" or "s") then this pragma causes
+      ** private information to be pulled in addition to public records.
+      */
+      if( blob_eq(&xfer.aToken[1], "send-private") ){
+        login_check_credentials();
+        if( !g.okPrivate ){
+          server_private_xfer_not_authorized();
+        }else{
+          xfer.syncPrivate = 1;
+        }
+      }
+    }else
+
     /* Unknown message
     */
     {
       cgi_reset_content();
       @ error bad\scommand:\s%F(blob_str(&xfer.line))
@@ -1049,13 +1161,15 @@
     ** cause the client to create phantoms for all artifacts, which will
     ** in turn make sure that the entire repository is sent efficiently
     ** and expeditiously.
     */
     send_all(&xfer);
+    if( xfer.syncPrivate ) send_private(&xfer);
   }else if( isPull ){
     create_cluster();
     send_unclustered(&xfer);
+    if( xfer.syncPrivate ) send_private(&xfer);
   }
   if( recvConfig ){
     configure_finalize_receive();
   }
   manifest_crosslink_end();
@@ -1120,10 +1234,11 @@
 */
 int client_sync(
   int pushFlag,           /* True to do a push (or a sync) */
   int pullFlag,           /* True to do a pull (or a sync) */
   int cloneFlag,          /* True if this is a clone */
+  int privateFlag,        /* True to exchange private branches */
   int configRcvMask,      /* Receive these configuration items */
   int configSendMask      /* Send these configuration items */
 ){
   int go = 1;             /* Loop until zero */
   int nCardSent = 0;      /* Number of cards sent */
@@ -1155,10 +1270,14 @@
   socket_global_init();
   memset(&xfer, 0, sizeof(xfer));
   xfer.pIn = &recv;
   xfer.pOut = &send;
   xfer.mxSend = db_get_int("max-upload", 250000);
+  if( privateFlag ){
+    g.okPrivate = 1;
+    xfer.syncPrivate = 1;
+  }
 
   assert( pushFlag | pullFlag | cloneFlag | configRcvMask | configSendMask );
   db_begin_transaction();
   db_record_repository_filename(0);
   db_multi_exec(
@@ -1169,10 +1288,14 @@
   blob_zero(&recv);
   blob_zero(&xfer.err);
   blob_zero(&xfer.line);
   origConfigRcvMask = 0;
 
+
+  /* Send the send-private pragma if we are trying to sync private data */
+  if( privateFlag ) blob_append(&send, "pragma send-private\n", -1);
+
   /*
   ** Always begin with a clone, pull, or push message
   */
   if( cloneFlag ){
     blob_appendf(&send, "clone 3 %d\n", cloneSeqno);
@@ -1212,10 +1335,11 @@
       request_phantoms(&xfer, mxPhantomReq);
     }
     if( pushFlag ){
       send_unsent(&xfer);
       nCardSent += send_unclustered(&xfer);
+      if( privateFlag ) send_private(&xfer);
     }
 
     /* Send configuration parameter requests.  On a clone, delay sending
     ** this until the second cycle since the login card might fail on 
     ** the first cycle.
@@ -1275,10 +1399,13 @@
       break;
     }
     lastPctDone = -1;
     blob_reset(&send);
     rArrivalTime = db_double(0.0, "SELECT julianday('now')");
+
+    /* Send the send-private pragma if we are trying to sync private data */
+    if( privateFlag ) blob_append(&send, "pragma send-private\n", -1);
 
     /* Begin constructing the next message (which might never be
     ** sent) by beginning with the pull or push cards
     */
     if( pullFlag ){
@@ -1349,32 +1476,40 @@
       if( blob_eq(&xfer.aToken[0], "gimme")
        && xfer.nToken==2
        && blob_is_uuid(&xfer.aToken[1])
       ){
         if( pushFlag ){
-          int rid = rid_from_uuid(&xfer.aToken[1], 0);
+          int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
           if( rid ) send_file(&xfer, rid, &xfer.aToken[1], 0);
         }
       }else
   
-      /*   igot UUID
+      /*   igot UUID  ?PRIVATEFLAG?
       **
       ** Server announces that it has a particular file.  If this is
       ** not a file that we have and we are pulling, then create a
       ** phantom to cause this file to be requested on the next cycle.
       ** Always remember that the server has this file so that we do
       ** not transmit it by accident.
+      **
+      ** If the PRIVATE argument exists and is 1, then the file is 
+      ** private.  Pretend it does not exists if we are not pulling
+      ** private files.
       */
-      if( xfer.nToken==2
+      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);
+        int rid;
+        int isPriv = xfer.nToken>=3 && blob_eq(&xfer.aToken[2],"1");
+        rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
         if( rid>0 ){
-          content_make_public(rid);
+          if( !isPriv ) content_make_public(rid);
+        }else if( isPriv && !g.okPrivate ){
+          /* ignore private files */
         }else if( pullFlag || cloneFlag ){
-          rid = content_new(blob_str(&xfer.aToken[1]), 0);
+          rid = content_new(blob_str(&xfer.aToken[1]), isPriv);
           if( rid ) newPhantom = 1;
         }
         remote_has(rid);
       }else
     
@@ -1454,10 +1589,21 @@
       ** same server.
       */
       if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
         db_set("cookie", blob_str(&xfer.aToken[1]), 0);
       }else
+
+
+      /*    private
+      **
+      ** This card indicates that the next "file" or "cfile" will contain
+      ** private content.
+      */
+      if( blob_eq(&xfer.aToken[0], "private") ){
+        xfer.nextIsPrivate = 1;
+      }else
+
 
       /*    clone_seqno N
       **
       ** When doing a clone, the server tries to send all of its artifacts
       ** in sequence.  This card indicates the sequence number of the next