Index: src/mkversion.c ================================================================== --- src/mkversion.c +++ src/mkversion.c @@ -8,10 +8,11 @@ ** Note that the manifest.uuid and manifest files are generated by Fossil. */ #include #include #include +#include static FILE *open_for_reading(const char *zFilename){ FILE *f = fopen(zFilename, "r"); if( f==0 ){ fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename); @@ -46,14 +47,28 @@ *z = 0; printf("#define MANIFEST_UUID \"%s\"\n",b); printf("#define MANIFEST_VERSION \"[%10.10s]\"\n",b); m = open_for_reading(argv[2]); while(b == fgets(b, sizeof(b)-1,m)){ - if(0 == strncmp("D ",b,2)){ - printf("#define MANIFEST_DATE \"%.10s %.8s\"\n",b+2,b+13); - printf("#define MANIFEST_YEAR \"%.4s\"\n",b+2); + if(0 == strncmp("D ",b,2)){ + int k, n; + char zDateNum[30]; + printf("#define MANIFEST_DATE \"%.10s %.8s\"\n",b+2,b+13); + printf("#define MANIFEST_YEAR \"%.4s\"\n",b+2); + n = 0; + for(k=0; k<10; k++){ + if( isdigit(b[k+2]) ) zDateNum[n++] = b[k+2]; + } + zDateNum[n] = 0; + printf("#define MANIFEST_NUMERIC_DATE %s\n", zDateNum); + n = 0; + for(k=0; k<8; k++){ + if( isdigit(b[k+13]) ) zDateNum[n++] = b[k+13]; } + zDateNum[n] = 0; + printf("#define MANIFEST_NUMERIC_TIME %s\n", zDateNum); + } } fclose(m); v = open_for_reading(argv[3]); if( fgets(b, sizeof(b)-1,v)==0 ){ fprintf(stderr, "malformed VERSION file: %s\n", argv[3]); Index: src/schema.c ================================================================== --- src/schema.c +++ src/schema.c @@ -158,10 +158,16 @@ @ ); @ @ -- Artifacts that should not be pushed are stored in the "private" @ -- table. Private artifacts are omitted from the "unclustered" and @ -- "unsent" tables. +@ -- +@ -- A phantom artifact (that is, an artifact with BLOB.SIZE<0 - an artifact +@ -- for which we do not know the content) might also be marked as private. +@ -- This comes about when an artifact is named in a manifest or tag but +@ -- the content of that artifact is held privately by some other peer +@ -- repository. @ -- @ CREATE TABLE private(rid INTEGER PRIMARY KEY); @ @ -- An entry in this table describes a database query that generates a @ -- table of tickets. Index: src/sync.c ================================================================== --- src/sync.c +++ src/sync.c @@ -61,22 +61,10 @@ g.url.flags |= URL_PROMPT_PW; url_prompt_for_password(); } g.zHttpAuth = get_httpauth(); url_remember(); -#if 0 /* Disabled for now */ - if( (flags & AUTOSYNC_PULL)!=0 && db_get_boolean("auto-shun",1) ){ - /* When doing an automatic pull, also automatically pull shuns from - ** the server if pull_shuns is enabled. - ** - ** TODO: What happens if the shun list gets really big? - ** Maybe the shunning list should only be pulled on every 10th - ** autosync, or something? - */ - configSync = CONFIGSET_SHUN; - } -#endif if( find_option("verbose","v",0)!=0 ) flags |= SYNC_VERBOSE; fossil_print("Autosync: %s\n", g.url.canonical); url_enable_proxy("via proxy: "); rc = client_sync(flags, configSync, 0, 0); return rc; @@ -163,11 +151,11 @@ url_proxy_options(); clone_ssh_find_options(); if( !uvOnly ) db_find_and_open_repository(0, 0); db_open_config(0, 1); if( g.argc==2 ){ - if( db_get_boolean("auto-shun",1) ) configSync = CONFIGSET_SHUN; + if( db_get_boolean("auto-shun",0) ) configSync = CONFIGSET_SHUN; }else if( g.argc==3 ){ zUrl = g.argv[2]; } if( ((*pSyncFlags) & (SYNC_PUSH|SYNC_PULL))==(SYNC_PUSH|SYNC_PULL) && db_get_boolean("uv-sync",0) Index: src/xfer.c ================================================================== --- src/xfer.c +++ src/xfer.c @@ -49,11 +49,13 @@ int nDanglingFile; /* Number of dangling deltas received */ int mxSend; /* Stop sending "file" when pOut reaches this size */ int resync; /* Send igot cards for all holdings */ u8 syncPrivate; /* True to enable syncing private content */ u8 nextIsPrivate; /* If true, next "file" received is a private */ - u32 clientVersion; /* Version of the client software */ + u32 remoteVersion; /* Version of fossil running on the other side */ + u32 remoteDate; /* Date for specific client software edition */ + u32 remoteTime; /* Time of date correspoding on remoteDate */ time_t maxTime; /* Time when this transfer should be finished */ }; /* @@ -524,20 +526,19 @@ 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( 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); if( blob_size(&uuid)==0 ){ return; } - if( blob_size(&uuid)>HNAME_LEN_SHA1 && pXfer->clientVersion<20000 ){ + if( blob_size(&uuid)>HNAME_LEN_SHA1 && pXfer->remoteVersion<20000 ){ xfer_cannot_send_sha3_error(pXfer); return; } if( pUuid ){ if( blob_compare(pUuid, &uuid)!=0 ){ @@ -548,10 +549,21 @@ pUuid = &uuid; } if( uuid_is_shunned(blob_str(pUuid)) ){ blob_reset(&uuid); return; + } + if( isPriv && pXfer->syncPrivate==0 ){ + if( pXfer->remoteDate>=20200413 ){ + /* If the artifact is private and we are not doing a private sync, + ** at least tell the other side that the artifact exists and is + ** known to be private. But only do this for newer clients since + ** older ones will throw an error if they get a private igot card + ** and private syncing is disallowed */ + blob_appendf(pXfer->pOut, "igot %b 1\n", pUuid); + } + return; } if( (pXfer->maxTime != -1 && time(NULL) >= pXfer->maxTime) || pXfer->mxSend<=blob_size(pXfer->pOut) ){ const char *zFormat = isPriv ? "igot %b 1\n" : "igot %b\n"; blob_appendf(pXfer->pOut, zFormat /*works-like:"%b"*/, pUuid); @@ -626,11 +638,11 @@ szC = db_column_bytes(&q1, 2); zContent = db_column_raw(&q1, 2); srcIsPrivate = db_column_int(&q1, 3); zDelta = db_column_text(&q1, 4); if( isPrivate ) blob_append(pXfer->pOut, "private\n", -1); - if( pXfer->clientVersion<20000 && db_column_bytes(&q1,0)!=HNAME_LEN_SHA1 ){ + if( pXfer->remoteVersion<20000 && db_column_bytes(&q1,0)!=HNAME_LEN_SHA1 ){ xfer_cannot_send_sha3_error(pXfer); db_reset(&q1); return; } blob_appendf(pXfer->pOut, "cfile %s ", zUuid); @@ -690,11 +702,11 @@ ); } if( db_step(&q1)==SQLITE_ROW ){ sqlite3_int64 mtime = db_column_int64(&q1, 0); const char *zHash = db_column_text(&q1, 1); - if( pXfer->clientVersion<20000 && db_column_bytes(&q1,1)>HNAME_LEN_SHA1 ){ + if( pXfer->remoteVersion<20000 && db_column_bytes(&q1,1)>HNAME_LEN_SHA1 ){ xfer_cannot_send_sha3_error(pXfer); db_reset(&q1); return; } if( blob_size(pXfer->pOut)>=pXfer->mxSend ){ @@ -956,30 +968,48 @@ } /* ** Send an igot message for every entry in unclustered table. ** Return the number of cards sent. +** +** Except: +** * Do not send igot cards for shunned artifacts +** * Do not send igot cards for phantoms +** * Do not send igot cards for private artifacts +** * Do not send igot cards for any artifact that is in the +** ONREMOTE table, if that table exists. +** +** If the pXfer->resync flag is set, that means we are doing a "--verily" +** sync and all artifacts that don't meet the restrictions above should +** be sent. */ static int send_unclustered(Xfer *pXfer){ Stmt q; int cnt = 0; + const char *zExtra; + if( db_table_exists("temp","onremote") ){ + zExtra = " AND NOT EXISTS(SELECT 1 FROM onremote WHERE rid=blob.rid)"; + }else{ + zExtra = ""; + } if( pXfer->resync ){ db_prepare(&q, "SELECT uuid, rid FROM blob" " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)" - " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)" + " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)%s" " AND blob.rid<=%d" " ORDER BY blob.rid DESC", - pXfer->resync + zExtra /*safe-for-%s*/, pXfer->resync ); }else{ db_prepare(&q, "SELECT uuid FROM unclustered JOIN blob USING(rid) /*scan*/" " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)" - " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)" + " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)%s", + zExtra /*safe-for-%s*/ ); } while( db_step(&q)==SQLITE_ROW ){ blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0)); cnt++; @@ -1191,11 +1221,11 @@ xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken)); /* file HASH SIZE \n CONTENT ** file HASH DELTASRC SIZE \n CONTENT ** - ** Accept a file from the client. + ** Server accepts a file from the client. */ if( blob_eq(&xfer.aToken[0], "file") ){ if( !isPush ){ cgi_reset_content(); @ error not\sauthorized\sto\swrite @@ -1212,11 +1242,11 @@ }else /* cfile HASH USIZE CSIZE \n CONTENT ** cfile HASH DELTASRC USIZE CSIZE \n CONTENT ** - ** Accept a file from the client. + ** Server accepts a compressed file from the client. */ if( blob_eq(&xfer.aToken[0], "cfile") ){ if( !isPush ){ cgi_reset_content(); @ error not\sauthorized\sto\swrite @@ -1232,11 +1262,11 @@ } }else /* uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT ** - ** Accept an unversioned file from the client. + ** Server accepts an unversioned file from the client. */ if( blob_eq(&xfer.aToken[0], "uvfile") ){ xfer_accept_unversioned_file(&xfer, g.perm.WrUnver); if( blob_size(&xfer.err) ){ cgi_reset_content(); @@ -1246,11 +1276,11 @@ } }else /* gimme HASH ** - ** Client is requesting a file. Send it. + ** Client is requesting a file from the server. Send it. */ if( blob_eq(&xfer.aToken[0], "gimme") && xfer.nToken==2 && blob_is_hname(&xfer.aToken[1]) ){ @@ -1263,11 +1293,11 @@ } }else /* uvgimme NAME ** - ** Client is requesting an unversioned file. Send it. + ** Client is requesting an unversioned file from the server. Send it. */ if( blob_eq(&xfer.aToken[0], "uvgimme") && xfer.nToken==2 && blob_is_filename(&xfer.aToken[1]) ){ @@ -1275,24 +1305,38 @@ }else /* igot HASH ?ISPRIVATE? ** ** Client announces that it has a particular file. If the ISPRIVATE - ** argument exists and is non-zero, then the file is a private file. + ** argument exists and is "1", then the file is a private file. */ if( xfer.nToken>=2 && blob_eq(&xfer.aToken[0], "igot") && blob_is_hname(&xfer.aToken[1]) ){ if( isPush ){ + int rid = 0; if( xfer.nToken==2 || blob_eq(&xfer.aToken[2],"1")==0 ){ - rid_from_uuid(&xfer.aToken[1], 1, 0); + /* Client says the artifact is public */ + rid = rid_from_uuid(&xfer.aToken[1], 1, 0); }else if( g.perm.Private ){ - rid_from_uuid(&xfer.aToken[1], 1, 1); + /* Client says the artifact is private and the client has + ** permission to push private content. Create a new phantom + ** artifact that is marked private. */ + rid = rid_from_uuid(&xfer.aToken[1], 1, 1); }else{ - server_private_xfer_not_authorized(); + /* Client says the artifact is private and the client is unable + ** or unwilling to send us the artifact. If we already hold the + ** artifact here on the server as a phantom, make sure that + ** phantom is marked as private so that we don't keep asking about + ** it in subsequent sync requests. */ + rid = rid_from_uuid(&xfer.aToken[1], 0, 1); + if( rid>0 ){ + db_multi_exec("INSERT OR IGNORE INTO private(rid) VALUES(%d)",rid); + } } + if( rid ) remote_has(rid); } }else /* pull SERVERCODE PROJECTCODE @@ -1388,11 +1432,12 @@ @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x")) }else /* login USER NONCE SIGNATURE ** - ** Check for a valid login. This has to happen before anything else. + ** The client has sent login credentials to the server. + ** Validate the login. This has to happen before anything else. ** The client can send multiple logins. Permissions are cumulative. */ if( blob_eq(&xfer.aToken[0], "login") && xfer.nToken==4 ){ @@ -1410,11 +1455,11 @@ } }else /* reqconfig NAME ** - ** Request a configuration value + ** Client is requesting a configuration value from the server */ if( blob_eq(&xfer.aToken[0], "reqconfig") && xfer.nToken==2 ){ if( g.perm.Read ){ @@ -1429,12 +1474,12 @@ } }else /* config NAME SIZE \n CONTENT ** - ** Receive a configuration value from the client. This is only - ** permitted for high-privilege users. + ** Client has sent a configuration value to the server. + ** This is only permitted for high-privilege users. */ if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3 && blob_is_int(&xfer.aToken[2], &size) ){ const char *zName = blob_str(&xfer.aToken[1]); Blob content; @@ -1474,11 +1519,11 @@ }else /* private ** - ** This card indicates that the next "file" or "cfile" will contain + ** The client card indicates that the next "file" or "cfile" will contain ** private content. */ if( blob_eq(&xfer.aToken[0], "private") ){ if( !g.perm.Private ){ server_private_xfer_not_authorized(); @@ -1495,10 +1540,12 @@ ** ignored. */ if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){ /* pragma send-private + ** + ** The client is requesting private artifacts. ** ** If the user has the "x" privilege (which must be set explicitly - ** it is not automatic with "a" or "s") then this pragma causes ** private information to be pulled in addition to public records. */ @@ -1511,22 +1558,32 @@ } } /* pragma send-catalog ** - ** Send igot cards for all known artifacts. + ** The client wants to see igot cards for all known artifacts. + ** This is used as part of "sync --verily" to help ensure that + ** no artifacts have been missed on prior syncs. */ if( blob_eq(&xfer.aToken[1], "send-catalog") ){ xfer.resync = 0x7fffffff; } - /* pragma client-version VERSION + /* pragma client-version VERSION ?DATE? ?TIME? ** - ** Let the server know what version of Fossil is running on the client. + ** The client announces to the server what version of Fossil it + ** is running. The DATE and TIME are a pure numeric ISO8601 time + ** for the specific check-in of the client. */ if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "client-version") ){ - xfer.clientVersion = atoi(blob_str(&xfer.aToken[2])); + xfer.remoteVersion = atoi(blob_str(&xfer.aToken[2])); + if( xfer.nToken>=5 ){ + xfer.remoteDate = atoi(blob_str(&xfer.aToken[3])); + xfer.remoteTime = atoi(blob_str(&xfer.aToken[4])); + @ pragma server-version %d(RELEASE_VERSION_NUMBER) \ + @ %d(MANIFEST_NUMERIC_DATE) %d(MANIFEST_NUMERIC_TIME) + } } /* pragma uv-hash HASH ** ** The client wants to make sure that unversioned files are all synced. @@ -1550,11 +1607,11 @@ /* pragma ci-lock CHECKIN-HASH CLIENT-ID ** ** The client wants to make non-branch commit against the check-in ** identified by CHECKIN-HASH. The server will remember this and - ** subsequent ci-lock request from different clients will generate + ** subsequent ci-lock requests from different clients will generate ** a ci-lock-fail pragma in the reply. */ if( blob_eq(&xfer.aToken[1], "ci-lock") && xfer.nToken==4 && blob_is_hname(&xfer.aToken[2]) @@ -1808,11 +1865,11 @@ memset(&xfer, 0, sizeof(xfer)); xfer.pIn = &recv; xfer.pOut = &send; xfer.mxSend = db_get_int("max-upload", 250000); xfer.maxTime = -1; - xfer.clientVersion = RELEASE_VERSION_NUMBER; + xfer.remoteVersion = RELEASE_VERSION_NUMBER; if( syncFlags & SYNC_PRIVATE ){ g.perm.Private = 1; xfer.syncPrivate = 1; } @@ -1856,13 +1913,16 @@ " SELECT name, 0 FROM unversioned WHERE hash IS NOT NULL;" ); } /* - ** Always begin with a clone, pull, or push message + ** The request from the client always begin with a clone, pull, + ** or push message. */ - blob_appendf(&send, "pragma client-version %d\n", RELEASE_VERSION_NUMBER); + blob_appendf(&send, "pragma client-version %d %d %d\n", + RELEASE_VERSION_NUMBER, MANIFEST_NUMERIC_DATE, + MANIFEST_NUMERIC_TIME); if( syncFlags & SYNC_CLONE ){ blob_appendf(&send, "clone 3 %d\n", cloneSeqno); syncFlags &= ~(SYNC_PUSH|SYNC_PULL); nCardSent++; /* TBD: Request all transferable configuration values */ @@ -1898,20 +1958,19 @@ "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);" ); manifest_crosslink_begin(); - /* Send back the most recently received cookie. Let the server - ** figure out if this is a cookie that it cares about. + /* Client sends the most recently received cookie back to the server. + ** Let the server figure out if this is a cookie that it cares about. */ zCookie = db_get("cookie", 0); if( zCookie ){ blob_appendf(&send, "cookie %s\n", zCookie); } - /* Generate gimme cards for phantoms and leaf cards - ** for all leaves. + /* Client sends gimme cards for phantoms */ if( (syncFlags & SYNC_PULL)!=0 || ((syncFlags & SYNC_CLONE)!=0 && cloneSeqno==1) ){ request_phantoms(&xfer, mxPhantomReq); @@ -1920,11 +1979,11 @@ send_unsent(&xfer); nCardSent += send_unclustered(&xfer); if( syncFlags & SYNC_PRIVATE ) send_private(&xfer); } - /* Send configuration parameter requests. On a clone, delay sending + /* Client sends configuration parameter requests. On a clone, delay sending ** this until the second cycle since the login card might fail on ** the first cycle. */ if( configRcvMask && ((syncFlags & SYNC_CLONE)==0 || nCycle>0) ){ const char *zName; @@ -1937,13 +1996,13 @@ } origConfigRcvMask = configRcvMask; configRcvMask = 0; } - /* Send a request to sync unversioned files. On a clone, delay sending - ** this until the second cycle since the login card might fail on - ** the first cycle. + /* Client sends a request to sync unversioned files. + ** On a clone, delay sending this until the second cycle since + ** the login card might fail on the first cycle. */ if( (syncFlags & SYNC_UNVERSIONED)!=0 && ((syncFlags & SYNC_CLONE)==0 || nCycle>0) && !uvHashSent ){ @@ -1950,11 +2009,12 @@ blob_appendf(&send, "pragma uv-hash %s\n", unversioned_content_hash(0)); nCardSent++; uvHashSent = 1; } - /* Send configuration parameters being pushed */ + /* On a "fossil config push", the client send configuration parameters + ** being pushed up to the server */ if( configSendMask ){ if( zOpType==0 ) zOpType = "Push"; nCardSent += configure_send_group(xfer.pOut, configSendMask, 0); configSendMask = 0; } @@ -2010,11 +2070,11 @@ zCkinLock = 0; }else if( zClientId ){ blob_appendf(&send, "pragma ci-unlock %s\n", zClientId); } - /* Append randomness to the end of the message. This makes all + /* Append randomness to the end of the uplink message. This makes all ** messages unique so that that the login-card nonce will always ** be unique. */ zRandomness = db_text(0, "SELECT hex(randomblob(20))"); blob_appendf(&send, "# %s\n", zRandomness); @@ -2055,11 +2115,13 @@ xfer.nGimmeSent = 0; xfer.nIGotSent = 0; lastPctDone = -1; blob_reset(&send); - blob_appendf(&send, "pragma client-version %d\n", RELEASE_VERSION_NUMBER); + blob_appendf(&send, "pragma client-version %d %d %d\n", + RELEASE_VERSION_NUMBER, MANIFEST_NUMERIC_DATE, + MANIFEST_NUMERIC_TIME); rArrivalTime = db_double(0.0, "SELECT julianday('now')"); /* Send the send-private pragma if we are trying to sync private data */ if( syncFlags & SYNC_PRIVATE ){ blob_append(&send, "pragma send-private\n", -1); @@ -2112,30 +2174,30 @@ } /* file HASH SIZE \n CONTENT ** file HASH DELTASRC SIZE \n CONTENT ** - ** Receive a file transmitted from the server. + ** Client receives a file transmitted from the server. */ if( blob_eq(&xfer.aToken[0],"file") ){ xfer_accept_file(&xfer, (syncFlags & SYNC_CLONE)!=0, 0, 0); nArtifactRcvd++; }else /* cfile HASH USIZE CSIZE \n CONTENT ** cfile HASH DELTASRC USIZE CSIZE \n CONTENT ** - ** Receive a compressed file transmitted from the server. + ** Client receives a compressed file transmitted from the server. */ if( blob_eq(&xfer.aToken[0],"cfile") ){ xfer_accept_compressed_file(&xfer, 0, 0); nArtifactRcvd++; }else /* uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT ** - ** Accept an unversioned file from the server. + ** Client accepts an unversioned file from the server. */ if( blob_eq(&xfer.aToken[0], "uvfile") ){ xfer_accept_unversioned_file(&xfer, 1); nArtifactRcvd++; nUvFileRcvd++; @@ -2145,13 +2207,14 @@ } }else /* gimme HASH ** - ** Server is requesting a file. If the file is a manifest, assume - ** that the server will also want to know all of the content files - ** associated with the manifest and send those too. + ** Client receives an artifact request from the server. + ** If the file is a manifest, assume that the server will also want + ** to know all of the content artifacts associated with the manifest + ** and send those too. */ if( blob_eq(&xfer.aToken[0], "gimme") && xfer.nToken==2 && blob_is_hname(&xfer.aToken[1]) ){ @@ -2182,10 +2245,11 @@ rid = rid_from_uuid(&xfer.aToken[1], 0, 0); if( rid>0 ){ if( !isPriv ) content_make_public(rid); }else if( isPriv && !g.perm.Private ){ /* ignore private files */ + db_multi_exec("INSERT OR IGNORE INTO private VALUES(%d)", rid); }else if( (syncFlags & (SYNC_PULL|SYNC_CLONE))!=0 ){ rid = content_new(blob_str(&xfer.aToken[1]), isPriv); if( rid ) newPhantom = 1; } remote_has(rid); @@ -2264,11 +2328,11 @@ }else /* push SERVERCODE PRODUCTCODE ** ** Should only happen in response to a clone. This message tells - ** the client what product to use for the new database. + ** the client what product code to use for the new database. */ if( blob_eq(&xfer.aToken[0],"push") && xfer.nToken==3 && (syncFlags & SYNC_CLONE)!=0 && blob_is_hname(&xfer.aToken[2]) @@ -2281,11 +2345,11 @@ nCardSent++; }else /* config NAME SIZE \n CONTENT ** - ** Receive a configuration value from the server. + ** Client receive a configuration value from the server. ** ** The received configuration setting is silently ignored if it was ** not requested by a prior "reqconfig" sent from client to server. */ if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3 @@ -2303,11 +2367,11 @@ }else /* cookie TEXT ** - ** The server might include a cookie in its reply. The client + ** The client reserves a cookie from the server. The client ** should remember this cookie and send it back to the server ** in its next query. ** ** Each cookie received overwrites the prior cookie from the ** same server. @@ -2317,12 +2381,12 @@ }else /* private ** - ** This card indicates that the next "file" or "cfile" will contain - ** private content. + ** The server tells the client that the next "file" or "cfile" will + ** contain private content. */ if( blob_eq(&xfer.aToken[0], "private") ){ xfer.nextIsPrivate = 1; }else @@ -2338,11 +2402,12 @@ blob_is_int(&xfer.aToken[1], &cloneSeqno); }else /* message MESSAGE ** - ** Print a message. Similar to "error" but does not stop processing. + ** A message is received from the server. Print it. + ** Similar to "error" but does not stop processing. ** ** If the "login failed" message is seen, clear the sync password prior ** to the next cycle. */ if( blob_eq(&xfer.aToken[0],"message") && xfer.nToken==2 ){ @@ -2364,11 +2429,27 @@ ** The server can send pragmas to try to convey meta-information to ** the client. These are informational only. Unknown pragmas are ** silently ignored. */ if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){ - /* If the server is unwill to accept new unversioned content (because + /* pragma server-version VERSION ?DATE? ?TIME? + ** + ** The servger announces to the server what version of Fossil it + ** is running. The DATE and TIME are a pure numeric ISO8601 time + ** for the specific check-in of the client. + */ + if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "client-version") ){ + xfer.remoteVersion = atoi(blob_str(&xfer.aToken[2])); + if( xfer.nToken>=5 ){ + xfer.remoteDate = atoi(blob_str(&xfer.aToken[3])); + xfer.remoteTime = atoi(blob_str(&xfer.aToken[4])); + } + } + + /* pragma uv-pull-only + ** + ** If the server is unwill to accept new unversioned content (because ** this client lacks the necessary permissions) then it sends a ** "uv-pull-only" pragma so that the client will know not to waste ** bandwidth trying to upload unversioned content. If the server ** does accept new unversioned content, it sends "uv-push-ok". */ @@ -2407,11 +2488,12 @@ } }else /* error MESSAGE ** - ** Report an error and abandon the sync session. + ** The server is reporting an error. The client will abandon + ** the sync session. ** ** Except, when cloning we will sometimes get an error on the ** first message exchange because the project-code is unknown ** and so the login card on the request was invalid. The project-code ** is returned in the reply before the error card, so second and Index: www/sync.wiki ================================================================== --- www/sync.wiki +++ www/sync.wiki @@ -421,10 +421,13 @@ using a gimme card in either the reply or in the next message.

If the second argument exists and is "1", then the artifact identified by the first argument is private on the sender and should be ignored unless a "--private" [/help?cmd=sync|sync] is occurring. + +

The name "igot" comes from the English slang expression "I got" meaning +"I have".

3.6.1 Unversioned Igot Cards

Zero or more "uvigot" cards are sent from server to client when synchronizing unversioned content. The format of a uvigot card is @@ -469,10 +472,14 @@

The argument to the gimme card is the ID of the artifact that the sender wants. The receiver will typically respond to a gimme card by sending a file card in its reply or in the next message.

+

The "gimme" name means "give me". The imperative "give me" is +pronounced as if it were a single word "gimme" in some dialects of +English (including the dialect spoken by the original author of Fossil). +

3.7.1 Unversioned Gimme Cards

Sync synchronizing unversioned content, the client may send "uvgimme" cards to the server. A uvgimme card requests that the server send unversioned content to the client. The format of a uvgimme card is