Index: src/blob.c ================================================================== --- src/blob.c +++ src/blob.c @@ -653,19 +653,47 @@ */ int blob_is_uuid(Blob *pBlob){ return blob_size(pBlob)==UUID_SIZE && validate16(blob_buffer(pBlob), UUID_SIZE); } + +/* +** Return true if the blob contains a valid filename +*/ +int blob_is_filename(Blob *pBlob){ + return file_is_simple_pathname(blob_str(pBlob), 1); +} /* ** Return true if the blob contains a valid 32-bit integer. Store ** the integer value in *pValue. */ int blob_is_int(Blob *pBlob, int *pValue){ const char *z = blob_buffer(pBlob); int i, n, c, v; n = blob_size(pBlob); + v = 0; + for(i=0; i='0' && c<='9'; i++){ + v = v*10 + c - '0'; + } + if( i==n ){ + *pValue = v; + return 1; + }else{ + return 0; + } +} + +/* +** Return true if the blob contains a valid 64-bit integer. Store +** the integer value in *pValue. +*/ +int blob_is_int64(Blob *pBlob, sqlite3_int64 *pValue){ + const char *z = blob_buffer(pBlob); + int i, n, c; + sqlite3_int64 v; + n = blob_size(pBlob); v = 0; for(i=0; i='0' && c<='9'; i++){ v = v*10 + c - '0'; } if( i==n ){ Index: src/content.c ================================================================== --- src/content.c +++ src/content.c @@ -452,10 +452,24 @@ ** Turn dephantomization processing on or off. */ void content_enable_dephantomize(int onoff){ ignoreDephantomizations = !onoff; } + +/* +** Make sure the g.rcvid global variable has been initialized. +*/ +void content_rcvid_init(void){ + if( g.rcvid==0 ){ + db_multi_exec( + "INSERT INTO rcvfrom(uid, mtime, nonce, ipaddr)" + "VALUES(%d, julianday('now'), %Q, %Q)", + g.userUid, g.zNonce, g.zIpAddr + ); + g.rcvid = db_last_insert_rowid(); + } +} /* ** Write content into the database. Return the record ID. If the ** content is already in the database, just return the record ID. ** @@ -530,18 +544,11 @@ markAsUnclustered = 1; } db_finalize(&s1); /* Construct a received-from ID if we do not already have one */ - if( g.rcvid==0 ){ - db_multi_exec( - "INSERT INTO rcvfrom(uid, mtime, nonce, ipaddr)" - "VALUES(%d, julianday('now'), %Q, %Q)", - g.userUid, g.zNonce, g.zIpAddr - ); - g.rcvid = db_last_insert_rowid(); - } + content_rcvid_init(); if( nBlob ){ cmpr = pBlob[0]; }else{ blob_compress(pBlob, &cmpr); Index: src/doc.c ================================================================== --- src/doc.c +++ src/doc.c @@ -522,12 +522,13 @@ } blob_append(cgi_output_blob(), &z[base], i-base); } /* +** WEBPAGE: uv ** WEBPAGE: doc -** URL: /doc?name=CHECKIN/FILE +** URL: /uv/FILE ** URL: /doc/CHECKIN/FILE ** ** CHECKIN can be either tag or SHA1 hash or timestamp identifying a ** particular check, or the name of a branch (meaning the most recent ** check-in on that branch) or one of various magic words: @@ -578,10 +579,12 @@ int rid = 0; /* Artifact of file */ int i; /* Loop counter */ Blob filebody; /* Content of the documentation file */ Blob title; /* Document title */ int nMiss = (-1); /* Failed attempts to find the document */ + int isUV = g.zPath[0]=='u'; /* True for /uv. False for /doc */ + const char *zDfltTitle; static const char *const azSuffix[] = { "index.html", "index.wiki", "index.md" #ifdef FOSSIL_ENABLE_TH1_DOCS , "index.th1" #endif @@ -588,29 +591,38 @@ }; login_check_credentials(); if( !g.perm.Read ){ login_needed(g.anon.Read); return; } blob_init(&title, 0, 0); + zDfltTitle = isUV ? "" : "Documentation"; db_begin_transaction(); while( rid==0 && (++nMiss)<=ArraySize(azSuffix) ){ zName = P("name"); - if( zName==0 || zName[0]==0 ) zName = "tip/index.wiki"; - for(i=0; zName[i] && zName[i]!='/'; i++){} - zCheckin = mprintf("%.*s", i, zName); - if( fossil_strcmp(zCheckin,"ckout")==0 && g.localOpen==0 ){ - zCheckin = "tip"; + if( isUV ){ + i = 0; + }else{ + if( zName==0 || zName[0]==0 ) zName = "tip/index.wiki"; + for(i=0; zName[i] && zName[i]!='/'; i++){} + zCheckin = mprintf("%.*s", i, zName); + if( fossil_strcmp(zCheckin,"ckout")==0 && g.localOpen==0 ){ + zCheckin = "tip"; + } } if( nMiss==ArraySize(azSuffix) ){ zName = "404.md"; }else if( zName[i]==0 ){ assert( nMiss>=0 && nMiss=0 && nMiss0 ){ style_header("%s", blob_str(&title)); }else{ style_header("%s", nMiss>=ArraySize(azSuffix)? - "Not Found" : "Documentation"); + "Not Found" : zDfltTitle); } convert_href_and_output(&tail); style_footer(); }else if( fossil_strcmp(zMime, "text/plain")==0 ){ - style_header("Documentation"); + style_header("%s", zDfltTitle); @
     @ %h(blob_str(&filebody))
     @ 
style_footer(); }else if( fossil_strcmp(zMime, "text/html")==0 Index: src/login.c ================================================================== --- src/login.c +++ src/login.c @@ -1095,11 +1095,11 @@ p->RdWiki = p->WrWiki = p->NewWiki = p->ApndWiki = p->Hyperlink = p->Clone = p->NewTkt = p->Password = p->RdAddr = p->TktFmt = p->Attach = p->ApndTkt = p->ModWiki = p->ModTkt = p->Delete = - p->Private = 1; + p->WrUnver = p->Private = 1; /* Fall thru into Read/Write */ case 'i': p->Read = p->Write = 1; break; case 'o': p->Read = 1; break; case 'z': p->Zip = 1; break; @@ -1122,10 +1122,11 @@ case 'c': p->ApndTkt = 1; break; case 'q': p->ModTkt = 1; break; case 't': p->TktFmt = 1; break; case 'b': p->Attach = 1; break; case 'x': p->Private = 1; break; + case 'y': p->WrUnver = 1; break; /* The "u" privileges is a little different. It recursively ** inherits all privileges of the user named "reader" */ case 'u': { if( (flags & LOGIN_IGNORE_UV)==0 ){ Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -91,10 +91,11 @@ char Attach; /* b: add attachments */ char TktFmt; /* t: create new ticket report formats */ char RdAddr; /* e: read email addresses or other private data */ char Zip; /* z: download zipped artifact via /zip URL */ char Private; /* x: can send and receive private content */ + char WrUnver; /* y: can push unversioned content */ }; #ifdef FOSSIL_ENABLE_TCL /* ** All Tcl related context information is in this structure. This structure Index: src/main.mk ================================================================== --- src/main.mk +++ src/main.mk @@ -117,10 +117,11 @@ $(SRCDIR)/timeline.c \ $(SRCDIR)/tkt.c \ $(SRCDIR)/tktsetup.c \ $(SRCDIR)/undo.c \ $(SRCDIR)/unicode.c \ + $(SRCDIR)/unversioned.c \ $(SRCDIR)/update.c \ $(SRCDIR)/url.c \ $(SRCDIR)/user.c \ $(SRCDIR)/utf8.c \ $(SRCDIR)/util.c \ @@ -290,10 +291,11 @@ $(OBJDIR)/timeline_.c \ $(OBJDIR)/tkt_.c \ $(OBJDIR)/tktsetup_.c \ $(OBJDIR)/undo_.c \ $(OBJDIR)/unicode_.c \ + $(OBJDIR)/unversioned_.c \ $(OBJDIR)/update_.c \ $(OBJDIR)/url_.c \ $(OBJDIR)/user_.c \ $(OBJDIR)/utf8_.c \ $(OBJDIR)/util_.c \ @@ -412,10 +414,11 @@ $(OBJDIR)/timeline.o \ $(OBJDIR)/tkt.o \ $(OBJDIR)/tktsetup.o \ $(OBJDIR)/undo.o \ $(OBJDIR)/unicode.o \ + $(OBJDIR)/unversioned.o \ $(OBJDIR)/update.o \ $(OBJDIR)/url.o \ $(OBJDIR)/user.o \ $(OBJDIR)/utf8.o \ $(OBJDIR)/util.o \ @@ -688,10 +691,11 @@ $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h \ $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h \ $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h \ $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h \ $(OBJDIR)/unicode_.c:$(OBJDIR)/unicode.h \ + $(OBJDIR)/unversioned_.c:$(OBJDIR)/unversioned.h \ $(OBJDIR)/update_.c:$(OBJDIR)/update.h \ $(OBJDIR)/url_.c:$(OBJDIR)/url.h \ $(OBJDIR)/user_.c:$(OBJDIR)/user.h \ $(OBJDIR)/utf8_.c:$(OBJDIR)/utf8.h \ $(OBJDIR)/util_.c:$(OBJDIR)/util.h \ @@ -1549,10 +1553,18 @@ $(OBJDIR)/unicode.o: $(OBJDIR)/unicode_.c $(OBJDIR)/unicode.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/unicode.o -c $(OBJDIR)/unicode_.c $(OBJDIR)/unicode.h: $(OBJDIR)/headers + +$(OBJDIR)/unversioned_.c: $(SRCDIR)/unversioned.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/unversioned.c >$@ + +$(OBJDIR)/unversioned.o: $(OBJDIR)/unversioned_.c $(OBJDIR)/unversioned.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/unversioned.o -c $(OBJDIR)/unversioned_.c + +$(OBJDIR)/unversioned.h: $(OBJDIR)/headers $(OBJDIR)/update_.c: $(SRCDIR)/update.c $(OBJDIR)/translate $(OBJDIR)/translate $(SRCDIR)/update.c >$@ $(OBJDIR)/update.o: $(OBJDIR)/update_.c $(OBJDIR)/update.h $(SRCDIR)/config.h Index: src/makemake.tcl ================================================================== --- src/makemake.tcl +++ src/makemake.tcl @@ -123,10 +123,11 @@ timeline tkt tktsetup undo unicode + unversioned update url user utf8 util Index: src/setup.c ================================================================== --- src/setup.c +++ src/setup.c @@ -294,10 +294,12 @@ @ user developer @ w @ Write-Tkt: Edit tickets @ x @ Private: Push and/or pull private branches + @ y + @ Write-Unver: Push unversioned files @ z @ Zip download: Download a ZIP archive or tarball @ } @@ -714,10 +716,13 @@ @ onchange="updateCapabilityString()" /> @ Ticket Report%s(B('t'))
@
+ @
@ @ @ Index: src/sitemap.c ================================================================== --- src/sitemap.c +++ src/sitemap.c @@ -101,10 +101,13 @@ @
  • %z(href("%R/timeline?y=t"))Recent activity
  • @
  • %z(href("%R/attachlist"))List of Attachments
  • @ @ } + if( g.perm.Read ){ + @
  • %z(href("%R/uvlist"))Unversioned Files + } if( srchFlags ){ @
  • %z(href("%R/search"))Full-Text Search
  • } @
  • %z(href("%R/login"))Login/Logout/Change Password
  • if( g.perm.Read ){ Index: src/stat.c ================================================================== --- src/stat.c +++ src/stat.c @@ -120,10 +120,31 @@ b = 1; } a = t/fsize; @ %d(a):%d(b) @ + } + if( db_table_exists("repository","unversioned") ){ + Stmt q; + char zStored[100]; + db_prepare(&q, + "SELECT count(*), sum(sz), sum(length(content))" + " FROM unversioned" + " WHERE length(hash)>1" + ); + if( db_step(&q)==SQLITE_ROW && (n = db_column_int(&q,0))>0 ){ + sqlite3_int64 iSz, iStored; + iSz = db_column_int64(&q,1); + iStored = db_column_int64(&q,2); + approxSizeName(sizeof(zBuf), zBuf, iSz); + approxSizeName(sizeof(zStored), zStored, iStored); + @ Unversioned Files: + @ %z(href("%R/uvlist"))%d(n) files, + @ total size %s(zBuf) uncompressed, %s(zStored) compressed + @ + } + db_finalize(&q); } @ Number Of Check-ins: n = db_int(0, "SELECT count(*) FROM event WHERE type='ci' /*scan*/"); @ %d(n) @ Index: src/sync.c ================================================================== --- src/sync.c +++ src/sync.c @@ -113,11 +113,15 @@ ** 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 void process_sync_args(unsigned *pConfigFlags, unsigned *pSyncFlags){ +static void process_sync_args( + unsigned *pConfigFlags, /* Write configuration flags here */ + unsigned *pSyncFlags, /* Write sync flags here */ + int uvOnly /* Special handling flags for UV sync */ +){ const char *zUrl = 0; const char *zHttpAuth = 0; unsigned configSync = 0; unsigned urlFlags = URL_REMEMBER | URL_PROMPT_PW; int urlOptional = 0; @@ -126,25 +130,33 @@ urlFlags = 0; } zHttpAuth = find_option("httpauth","B",1); if( find_option("once",0,0)!=0 ) urlFlags &= ~URL_REMEMBER; if( (*pSyncFlags) & SYNC_FROMPARENT ) urlFlags &= ~URL_REMEMBER; + if( !uvOnly ){ + if( find_option("private",0,0)!=0 ){ + *pSyncFlags |= SYNC_PRIVATE; + } + /* The --verily option to sync, push, and pull forces extra igot cards + ** to be exchanged. This can overcome malfunctions in the sync protocol. + */ + if( find_option("verily",0,0)!=0 ){ + *pSyncFlags |= SYNC_RESYNC; + } + } + if( find_option("uv",0,0)!=0 ){ + *pSyncFlags |= SYNC_UNVERSIONED; + } if( find_option("private",0,0)!=0 ){ *pSyncFlags |= SYNC_PRIVATE; } if( find_option("verbose","v",0)!=0 ){ *pSyncFlags |= SYNC_VERBOSE; } - /* The --verily option to sync, push, and pull forces extra igot cards - ** to be exchanged. This can overcome malfunctions in the sync protocol. - */ - if( find_option("verily",0,0)!=0 ){ - *pSyncFlags |= SYNC_RESYNC; - } url_proxy_options(); clone_ssh_find_options(); - db_find_and_open_repository(0, 0); + if( !uvOnly ) db_find_and_open_repository(0, 0); db_open_config(0, 0); if( g.argc==2 ){ if( db_get_boolean("auto-shun",1) ) configSync = CONFIGSET_SHUN; }else if( g.argc==3 ){ zUrl = g.argv[2]; @@ -209,11 +221,11 @@ unsigned configFlags = 0; unsigned syncFlags = SYNC_PULL; if( find_option("from-parent-project",0,0)!=0 ){ syncFlags |= SYNC_FROMPARENT; } - process_sync_args(&configFlags, &syncFlags); + process_sync_args(&configFlags, &syncFlags, 0); /* We should be done with options.. */ verify_all_options(); client_sync(syncFlags, configFlags, 0); @@ -251,11 +263,11 @@ ** See also: clone, config push, pull, remote-url, sync */ void push_cmd(void){ unsigned configFlags = 0; unsigned syncFlags = SYNC_PUSH; - process_sync_args(&configFlags, &syncFlags); + process_sync_args(&configFlags, &syncFlags, 0); /* We should be done with options.. */ verify_all_options(); if( db_get_boolean("dont-push",0) ){ @@ -296,11 +308,11 @@ ** See also: clone, pull, push, remote-url */ void sync_cmd(void){ unsigned configFlags = 0; unsigned syncFlags = SYNC_PUSH|SYNC_PULL; - process_sync_args(&configFlags, &syncFlags); + process_sync_args(&configFlags, &syncFlags, 0); /* We should be done with options.. */ verify_all_options(); if( db_get_boolean("dont-push",0) ) syncFlags &= ~SYNC_PUSH; @@ -307,10 +319,21 @@ client_sync(syncFlags, configFlags, 0); if( (syncFlags & SYNC_PUSH)==0 ){ fossil_warning("pull only: the 'dont-push' option is set"); } } + +/* +** Handle the "fossil unversioned sync" command. +*/ +void sync_unversioned(void){ + unsigned configFlags = 0; + unsigned syncFlags = SYNC_UNVERSIONED; + process_sync_args(&configFlags, &syncFlags, 1); + verify_all_options(); + client_sync(syncFlags, 0, 0); +} /* ** COMMAND: remote-url ** ** Usage: %fossil remote-url ?URL|off? ADDED src/unversioned.c Index: src/unversioned.c ================================================================== --- /dev/null +++ src/unversioned.c @@ -0,0 +1,421 @@ +/* +** Copyright (c) 2016 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code used to implement unversioned file interfaces. +*/ +#include "config.h" +#include +#if defined(FOSSIL_ENABLE_MINIZ) +# define MINIZ_HEADER_FILE_ONLY +# include "miniz.c" +#else +# include +#endif +#include "unversioned.h" +#include + +/* +** SQL code to implement the tables needed by the unversioned. +*/ +static const char zUnversionedInit[] = +@ CREATE TABLE IF NOT EXISTS "%w".unversioned( +@ name TEXT PRIMARY KEY, -- Name of the uv file +@ rcvid INTEGER, -- Where received from +@ mtime DATETIME, -- timestamp. Seconds since 1970. +@ hash TEXT, -- Content hash. NULL if a delete marker +@ sz INTEGER, -- size of content after decompression +@ encoding INT, -- 0: plaintext. 1: zlib compressed +@ content BLOB -- content of the file. NULL if oversized +@ ) WITHOUT ROWID; +; + +/* +** Make sure the unversioned table exists in the repository. +*/ +void unversioned_schema(void){ + if( !db_table_exists("repository", "unversioned") ){ + db_multi_exec(zUnversionedInit /*works-like:"%w"*/, db_name("repository")); + } +} + +/* +** Return a string which is the hash of the unversioned content. +** This is the hash used by repositories to compare content before +** exchanging a catalog. So all repositories must compute this hash +** in exactly the same way. +** +** If debugFlag is set, force the value to be recomputed and write +** the text of the hashed string to stdout. +*/ +const char *unversioned_content_hash(int debugFlag){ + const char *zHash = debugFlag ? 0 : db_get("uv-hash", 0); + if( zHash==0 ){ + Stmt q; + db_prepare(&q, + "SELECT printf('%%s %%s %%s\n',name,datetime(mtime,'unixepoch'),hash)" + " FROM unversioned" + " WHERE hash IS NOT NULL" + " ORDER BY name" + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *z = db_column_text(&q, 0); + if( debugFlag ) fossil_print("%s", z); + sha1sum_step_text(z,-1); + } + db_finalize(&q); + db_set("uv-hash", sha1sum_finish(0), 0); + zHash = db_get("uv-hash",0); + } + return zHash; +} + +/* +** Initialize pContent to be the content of an unversioned file zName. +** +** Return 0 on success. Return 1 if zName is not found. +*/ +int unversioned_content(const char *zName, Blob *pContent){ + Stmt q; + int rc = 1; + blob_init(pContent, 0, 0); + db_prepare(&q, "SELECT encoding, content FROM unversioned WHERE name=%Q", zName); + if( db_step(&q)==SQLITE_ROW ){ + db_column_blob(&q, 1, pContent); + if( db_column_int(&q, 0)==1 ){ + blob_uncompress(pContent, pContent); + } + rc = 0; + } + db_finalize(&q); + return rc; +} + +/* +** Check the status of unversioned file zName. Return an integer status +** code as follows: +** +** 0: zName does not exist in the unversioned table. +** 1: zName exists and should be replaced by mtime/zHash. +** 2: zName exists and is the same as zHash but has a older mtime +** 3: zName exists and is identical to mtime/zHash in all respects. +** 4: zName exists and is the same as zHash but has a newer mtime. +** 5: zName exists and should override mtime/zHash. +*/ +int unversioned_status(const char *zName, sqlite3_int64 mtime, const char *zHash){ + int iStatus = 0; + Stmt q; + db_prepare(&q, "SELECT mtime, hash FROM unversioned WHERE name=%Q", zName); + if( db_step(&q)==SQLITE_ROW ){ + const char *zLocalHash = db_column_text(&q, 1); + int hashCmp; + sqlite3_int64 iLocalMtime = db_column_int64(&q, 0); + int mtimeCmp = iLocalMtime=3 ? g.argv[2] : "x"; + nCmd = (int)strlen(zCmd); + if( zMtime==0 ){ + mtime = time(0); + }else{ + mtime = db_int(0, "SELECT strftime('%%s',%Q)", zMtime); + if( mtime<=0 ) fossil_fatal("bad timestamp: %Q", zMtime); + } + if( memcmp(zCmd, "add", nCmd)==0 ){ + const char *zIn; + const char *zAs; + Blob file; + Blob hash; + Blob compressed; + Stmt ins; + int i; + + zAs = find_option("as",0,1); + if( zAs && g.argc!=4 ) usage("add DISKFILE --as UVFILE"); + verify_all_options(); + db_begin_transaction(); + content_rcvid_init(); + db_prepare(&ins, + "REPLACE INTO unversioned(name,rcvid,mtime,hash,sz,encoding,content)" + " VALUES(:name,:rcvid,:mtime,:hash,:sz,:encoding,:content)" + ); + for(i=3; i1 && zCmd[1]=='i'); + verify_all_options(); + if( !longFlag ){ + if( allFlag ){ + db_prepare(&q, "SELECT name FROM unversioned ORDER BY name"); + }else{ + db_prepare(&q, "SELECT name FROM unversioned WHERE hash IS NOT NULL" + " ORDER BY name"); + } + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%s\n", db_column_text(&q,0)); + } + }else{ + db_prepare(&q, + "SELECT hash, datetime(mtime,'unixepoch'), sz, length(content), name" + " FROM unversioned" + " ORDER BY name;" + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zHash = db_column_text(&q, 0); + const char *zNoContent = ""; + if( zHash==0 ){ + if( !allFlag ) continue; + zHash = "(deleted)"; + }else if( db_column_type(&q,3)==SQLITE_NULL ){ + zNoContent = " (no content)"; + } + fossil_print("%12.12s %s %8d %8d %s%s\n", + zHash, + db_column_text(&q,1), + db_column_int(&q,2), + db_column_int(&q,3), + db_column_text(&q,4), + zNoContent + ); + } + } + db_finalize(&q); + }else if( memcmp(zCmd, "revert", nCmd)==0 ){ + fossil_fatal("not yet implemented..."); + }else if( memcmp(zCmd, "rm", nCmd)==0 ){ + int i; + verify_all_options(); + db_begin_transaction(); + for(i=3; i + @ + @ + @ + @ + } + if( isDeleted ){ + sqlite3_snprintf(sizeof(zSzName), zSzName, "Deleted"); + fullSize = 0; + }else{ + approxSizeName(sizeof(zSzName), zSzName, fullSize); + iTotalSz += fullSize; + cnt++; + } + @ + @ + @ + @ + @ + fossil_free(zAge); + } + db_finalize(&q); + if( n ){ + approxSizeName(sizeof(zSzName), zSzName, iTotalSz); + @ + @ + @
    Name + @ Age + @ Size + @
    %h(zName) %s(zAge) %s(zSzName)
    Total over %d(cnt) files%s(zSzName)
    + output_table_sorting_javascript("uvtab","tKk",1); + }else{ + @ No unversioned files on this server. + } + style_footer(); +} Index: src/xfer.c ================================================================== --- src/xfer.c +++ src/xfer.c @@ -2,11 +2,11 @@ ** Copyright (c) 2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) - +** ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: @@ -282,10 +282,126 @@ Th_AppendToList(pzUuidList, pnUuidList, blob_str(&pXfer->aToken[1]), blob_size(&pXfer->aToken[1])); remote_has(rid); blob_reset(&content); } + +/* +** The aToken[0..nToken-1] blob array is a parse of a "uvfile" line +** message. This routine finishes parsing that message and adds the +** unversioned file to the "unversioned" table. +** +** The file line is in one of the following two forms: +** +** uvfile NAME MTIME HASH SIZE FLAGS +** uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT +** +** If the 0x0001 bit of FLAGS is set, that means the file has been +** deleted, SIZE is zero, the HASH is "-", and the "\n CONTENT" is omitted. +** +** SIZE is the number of bytes of CONTENT. The CONTENT is uncompressed. +** HASH is the SHA1 hash of CONTENT. +** +** If the 0x0004 bit of FLAGS is set, that means the CONTENT is omitted. +** The sender might have omitted the content because it is too big to +** transmit, or because it is unchanged and this record exists purely +** to update the MTIME. +*/ +static void xfer_accept_unversioned_file(Xfer *pXfer, int isWriter){ + sqlite3_int64 mtime; /* The MTIME */ + Blob *pHash; /* The HASH value */ + int sz; /* The SIZE */ + int flags; /* The FLAGS */ + Blob content; /* The CONTENT */ + Blob hash; /* Hash computed from CONTENT to compare with HASH */ + Blob x; /* Compressed content */ + Stmt q; /* SQL statements for comparison and insert */ + int isDelete; /* HASH is "-" indicating this is a delete */ + int nullContent; /* True of CONTENT is NULL */ + int iStatus; /* Result from unversioned_status() */ + + pHash = &pXfer->aToken[3]; + if( pXfer->nToken==5 + || !blob_is_filename(&pXfer->aToken[1]) + || !blob_is_int64(&pXfer->aToken[2], &mtime) + || (!blob_eq(pHash,"-") && !blob_is_uuid(pHash)) + || !blob_is_int(&pXfer->aToken[4], &sz) + || !blob_is_int(&pXfer->aToken[5], &flags) + ){ + blob_appendf(&pXfer->err, "malformed uvfile line"); + return; + } + blob_init(&content, 0, 0); + blob_init(&hash, 0, 0); + blob_init(&x, 0, 0); + if( sz>0 && (flags & 0x0005)==0 ){ + blob_extract(pXfer->pIn, sz, &content); + nullContent = 0; + sha1sum_blob(&content, &hash); + if( blob_compare(&hash, pHash)!=0 ){ + blob_appendf(&pXfer->err, "in uvfile line, HASH does not match CONTENT"); + goto end_accept_unversioned_file; + } + }else{ + nullContent = 1; + } + + /* The isWriter flag must be true in order to land the new file */ + if( !isWriter ) goto end_accept_unversioned_file; + + /* Check to see if current content really should be overwritten. Ideally, + ** a uvfile card should never have been sent unless the overwrite should + ** occur. But do not trust the sender. Double-check. + */ + iStatus = unversioned_status(blob_str(&pXfer->aToken[1]), mtime, + blob_str(pHash)); + if( iStatus>=3 ) goto end_accept_unversioned_file; + + /* Store the content */ + isDelete = blob_eq(pHash, "-"); + if( isDelete ){ + db_prepare(&q, + "UPDATE unversioned" + " SET rcvid=:rcvid, mtime=:mtime, hash=NULL," + " sz=0, encoding=0, content=NULL" + " WHERE name=:name" + ); + db_bind_int(&q, ":rcvid", g.rcvid); + }else if( iStatus==4 ){ + db_prepare(&q, "UPDATE unversioned SET mtime=:mtime WHERE name=:name"); + }else{ + db_prepare(&q, + "REPLACE INTO unversioned(name,rcvid,mtime,hash,sz,encoding,content)" + " VALUES(:name,:rcvid,:mtime,:hash,:sz,:encoding,:content)" + ); + db_bind_int(&q, ":rcvid", g.rcvid); + db_bind_text(&q, ":hash", blob_str(pHash)); + db_bind_int(&q, ":sz", blob_size(&content)); + if( !nullContent ){ + blob_compress(&content, &x); + if( blob_size(&x) < 0.8*blob_size(&content) ){ + db_bind_blob(&q, ":content", &x); + db_bind_int(&q, ":encoding", 1); + }else{ + db_bind_blob(&q, ":content", &content); + db_bind_int(&q, ":encoding", 0); + } + }else{ + db_bind_int(&q, ":encoding", 0); + } + } + db_bind_text(&q, ":name", blob_str(&pXfer->aToken[1])); + db_bind_int64(&q, ":mtime", mtime); + db_step(&q); + db_finalize(&q); + db_unset("uv-hash", 0); + +end_accept_unversioned_file: + blob_reset(&x); + blob_reset(&content); + blob_reset(&hash); +} /* ** 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 @@ -524,10 +640,71 @@ blob_reset(&fullContent); } } db_reset(&q1); } + +/* +** Send the unversioned file identified by zName by generating the +** appropriate "uvfile" card. +** +** uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT +** +** If the noContent flag is set, omit the CONTENT and set the 0x0004 +** flag in FLAGS. +*/ +static void send_unversioned_file( + Xfer *pXfer, /* Transfer context */ + const char *zName, /* Name of unversioned file to be sent */ + int noContent /* True to omit the content */ +){ + Stmt q1; + + if( blob_size(pXfer->pOut)>=pXfer->mxSend ) noContent = 1; + if( noContent ){ + db_prepare(&q1, + "SELECT mtime, hash, encoding, sz FROM unversioned WHERE name=%Q", + zName + ); + }else{ + db_prepare(&q1, + "SELECT mtime, hash, encoding, sz, content FROM unversioned" + " WHERE name=%Q", + zName + ); + } + if( db_step(&q1)==SQLITE_ROW ){ + sqlite3_int64 mtime = db_column_int64(&q1, 0); + const char *zHash = db_column_text(&q1, 1); + if( blob_size(pXfer->pOut)>=pXfer->mxSend ){ + /* If we have already reached the send size limit, send a (short) + ** uvigot card rather than a uvfile card. This only happens on the + ** server side. The uvigot card will provoke the client to resend + ** another uvgimme on the next cycle. */ + blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n", + zName, mtime, zHash, db_column_int(&q1,3)); + }else{ + blob_appendf(pXfer->pOut, "uvfile %s %lld", zName, mtime); + if( zHash==0 ){ + blob_append(pXfer->pOut, " - 0 1\n", -1); + }else if( noContent ){ + blob_appendf(pXfer->pOut, " %s %d 4\n", zHash, db_column_int(&q1,3)); + }else{ + Blob content; + blob_init(&content, 0, 0); + db_column_blob(&q1, 4, &content); + if( db_column_int(&q1, 2) ){ + blob_uncompress(&content, &content); + } + blob_appendf(pXfer->pOut, " %s %d 0\n", zHash, blob_size(&content)); + blob_append(pXfer->pOut, blob_buffer(&content), blob_size(&content)); + blob_reset(&content); + } + } + } + db_finalize(&q1); +} /* ** Send a gimme message for every phantom. ** ** Except: do not request shunned artifacts. And do not request @@ -593,11 +770,13 @@ Stmt q; int rc = -1; char *zLogin = blob_terminate(pLogin); defossilize(zLogin); - if( fossil_strcmp(zLogin, "nobody")==0 || fossil_strcmp(zLogin,"anonymous")==0 ){ + if( fossil_strcmp(zLogin, "nobody")==0 + || fossil_strcmp(zLogin,"anonymous")==0 + ){ return 0; /* Anybody is allowed to sync as "nobody" or "anonymous" */ } if( fossil_strcmp(P("REMOTE_USER"), zLogin)==0 && db_get_boolean("remote_user_ok",0) ){ return 0; /* Accept Basic Authorization */ @@ -833,10 +1012,40 @@ blob_size(&content), blob_str(&content)); blob_reset(&content); } } + +/* +** pXfer is a "pragma uv-hash HASH" card. +** +** If HASH is different from the unversioned content hash on this server, +** then send a bunch of uvigot cards, one for each entry unversioned file +** on this server. +*/ +static void send_unversioned_catalog(Xfer *pXfer){ + unversioned_schema(); + if( !blob_eq(&pXfer->aToken[2], unversioned_content_hash(0)) ){ + int nUvIgot = 0; + Stmt uvq; + db_prepare(&uvq, + "SELECT name, mtime, hash, sz FROM unversioned" + ); + while( db_step(&uvq)==SQLITE_ROW ){ + const char *zName = db_column_text(&uvq,0); + sqlite3_int64 mtime = db_column_int64(&uvq,1); + const char *zHash = db_column_text(&uvq,2); + int sz = db_column_int(&uvq,3); + nUvIgot++; + if( zHash==0 ){ sz = 0; zHash = "-"; } + blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n", + zName, mtime, zHash, sz); + } + db_finalize(&uvq); + } +} + /* ** 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){ @@ -1026,10 +1235,24 @@ @ error %T(blob_str(&xfer.err)) nErr++; break; } }else + + /* uvfile NAME MTIME HASH SIZE FLAGS \n CONTENT + ** + ** Accept 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(); + @ error %T(blob_str(&xfer.err)) + nErr++; + break; + } + }else /* gimme UUID ** ** Client is requesting a file. Send it. */ @@ -1043,10 +1266,21 @@ if( rid ){ send_file(&xfer, rid, &xfer.aToken[1], deltaFlag); } } }else + + /* uvgimme NAME + ** + ** Client is requesting an unversioned file. Send it. + */ + if( blob_eq(&xfer.aToken[0], "uvgimme") + && xfer.nToken==2 + && blob_is_filename(&xfer.aToken[1]) + ){ + send_unversioned_file(&xfer, blob_str(&xfer.aToken[1]), 0); + }else /* igot UUID ?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. @@ -1225,11 +1459,10 @@ blob_reset(&content); blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR); }else - /* cookie TEXT ** ** A cookie contains a arbitrary-length argument that is server-defined. ** The argument must be encoded so as not to contain any whitespace. ** The server can optionally send a cookie to the client. The client @@ -1269,10 +1502,11 @@ ** 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. @@ -1283,17 +1517,36 @@ server_private_xfer_not_authorized(); }else{ xfer.syncPrivate = 1; } } + /* pragma send-catalog ** ** Send igot cards for all known artifacts. */ if( blob_eq(&xfer.aToken[1], "send-catalog") ){ xfer.resync = 0x7fffffff; } + + /* pragma uv-hash HASH + ** + ** The client wants to make sure that unversioned files are all synced. + ** If the HASH does not match, send a complete catalog of + ** "uvigot" cards. + */ + if( blob_eq(&xfer.aToken[1], "uv-hash") + && blob_is_uuid(&xfer.aToken[2]) + ){ + if( g.perm.Read && g.perm.WrUnver ){ + @ pragma uv-push-ok + send_unversioned_catalog(&xfer); + }else if( g.perm.Read ){ + @ pragma uv-pull-only + send_unversioned_catalog(&xfer); + } + } }else /* Unknown message */ { @@ -1391,17 +1644,18 @@ #if INTERFACE /* ** Flag options for controlling client_sync() */ -#define SYNC_PUSH 0x0001 -#define SYNC_PULL 0x0002 -#define SYNC_CLONE 0x0004 -#define SYNC_PRIVATE 0x0008 -#define SYNC_VERBOSE 0x0010 -#define SYNC_RESYNC 0x0020 -#define SYNC_FROMPARENT 0x0080 +#define SYNC_PUSH 0x0001 +#define SYNC_PULL 0x0002 +#define SYNC_CLONE 0x0004 +#define SYNC_PRIVATE 0x0008 +#define SYNC_VERBOSE 0x0010 +#define SYNC_RESYNC 0x0020 +#define SYNC_UNVERSIONED 0x0040 +#define SYNC_FROMPARENT 0x0080 #endif /* ** Floating-point absolute value */ @@ -1424,11 +1678,11 @@ ){ int go = 1; /* Loop until zero */ int nCardSent = 0; /* Number of cards sent */ int nCardRcvd = 0; /* Number of cards received */ int nCycle = 0; /* Number of round trips to the server */ - int size; /* Size of a config value */ + int size; /* Size of a config value or uvfile */ int origConfigRcvMask; /* Original value of configRcvMask */ int nFileRecv; /* Number of files received */ int mxPhantomReq = 200; /* Max number of phantoms to request per comm */ const char *zCookie; /* Server cookie */ i64 nSent, nRcvd; /* Bytes sent and received (after compression) */ @@ -1445,13 +1699,19 @@ int nRoundtrip= 0; /* Number of HTTP requests */ int nArtifactSent = 0; /* Total artifacts sent */ int nArtifactRcvd = 0; /* Total artifacts received */ const char *zOpType = 0;/* Push, Pull, Sync, Clone */ double rSkew = 0.0; /* Maximum time skew */ + int uvHashSent = 0; /* The "pragma uv-hash" message has been sent */ + int uvStatus = 0; /* 0: no I/O. 1: pull-only 2: push-and-pull */ + int uvDoPush = 0; /* Generate uvfile messages to send to server */ + int nUvGimmeSent = 0; /* Number of uvgimme cards sent on this cycle */ + int nUvFileRcvd = 0; /* Number of uvfile cards received on this cycle */ + sqlite3_int64 mtime; /* Modification time on a UV file */ if( db_get_boolean("dont-push", 0) ) syncFlags &= ~SYNC_PUSH; - if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE))==0 + if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE|SYNC_UNVERSIONED))==0 && configRcvMask==0 && configSendMask==0 ) return 0; if( syncFlags & SYNC_FROMPARENT ){ configRcvMask = 0; configSendMask = 0; syncFlags &= ~(SYNC_PUSH); @@ -1485,10 +1745,24 @@ /* 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); } + + /* When syncing unversioned files, create a TEMP table in which to store + ** the names of files that do not need to be sent from client to server. + */ + if( syncFlags & SYNC_UNVERSIONED ){ + db_multi_exec( + "CREATE TEMP TABLE uv_tosend(" + " name TEXT PRIMARY KEY," + " mtimeOnly BOOLEAN" + ") WITHOUT ROWID;" + "INSERT INTO uv_toSend(name,mtimeOnly)" + " SELECT name, 0 FROM unversioned WHERE hash IS NOT NULL;" + ); + } /* ** Always begin with a clone, pull, or push message */ if( syncFlags & SYNC_CLONE ){ @@ -1526,11 +1800,11 @@ db_multi_exec( "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);" ); manifest_crosslink_begin(); - /* Send make the most recently received cookie. Let the server + /* Send back the most recently received cookie. 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); @@ -1570,10 +1844,23 @@ configure_prepare_to_receive(overwrite); } 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. + */ + if( (syncFlags & SYNC_UNVERSIONED)!=0 + && ((syncFlags & SYNC_CLONE)==0 || nCycle>0) + && !uvHashSent + ){ + blob_appendf(&send, "pragma uv-hash %s\n", unversioned_content_hash(0)); + nCardSent++; + uvHashSent = 1; + } /* Send configuration parameters being pushed */ if( configSendMask ){ if( zOpType==0 ) zOpType = "Push"; if( configSendMask & CONFIGSET_OLDFORMAT ){ @@ -1587,10 +1874,31 @@ }else{ nCardSent += configure_send_group(xfer.pOut, configSendMask, 0); } configSendMask = 0; } + + /* Send unversioned files present here on the client but missing or + ** obsolete on the server. + */ + if( uvDoPush ){ + Stmt uvq; + int rc = SQLITE_OK; + assert( (syncFlags & SYNC_UNVERSIONED)!=0 ); + assert( uvStatus==2 ); + db_prepare(&uvq, "SELECT name, mtimeOnly FROM uv_tosend"); + while( (rc = db_step(&uvq))==SQLITE_ROW ){ + const char *zName = db_column_text(&uvq, 0); + send_unversioned_file(&xfer, zName, db_column_int(&uvq,1)); + nCardSent++; + nArtifactSent++; + db_multi_exec("DELETE FROM uv_tosend WHERE name=%Q", zName); + if( blob_size(xfer.pOut)>xfer.mxSend ) break; + } + db_finalize(&uvq); + if( rc==SQLITE_DONE ) uvDoPush = 0; + } /* Append randomness to the end of the message. This makes all ** messages unique so that that the login-card nonce will always ** be unique. */ @@ -1647,10 +1955,12 @@ if( syncFlags & SYNC_PUSH ){ blob_appendf(&send, "push %s %s\n", zSCode, zPCode); nCardSent++; } go = 0; + nUvGimmeSent = 0; + nUvFileRcvd = 0; /* Process the reply that came back from the server */ while( blob_line(&recv, &xfer.line) ){ if( blob_buffer(&xfer.line)[0]=='#' ){ const char *zLine = blob_buffer(&xfer.line); @@ -1659,11 +1969,13 @@ double rDiff; sqlite3_snprintf(sizeof(zTime), zTime, "%.19s", &zLine[12]); rDiff = db_double(9e99, "SELECT julianday('%q') - %.17g", zTime, rArrivalTime); if( rDiff>9e98 || rDiff<-9e98 ) rDiff = 0.0; - if( rDiff*24.0*3600.0 >= -(blob_size(&recv)/5000.0 + 20) ) rDiff = 0.0; + if( rDiff*24.0*3600.0 >= -(blob_size(&recv)/5000.0 + 20) ){ + rDiff = 0.0; + } if( fossil_fabs(rDiff)>fossil_fabs(rSkew) ) rSkew = rDiff; } nCardRcvd++; continue; } @@ -1695,10 +2007,20 @@ */ 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 client. + */ + if( blob_eq(&xfer.aToken[0], "uvfile") ){ + xfer_accept_unversioned_file(&xfer, 1); + nArtifactRcvd++; + nUvFileRcvd++; + }else /* gimme UUID ** ** 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 @@ -1742,10 +2064,63 @@ if( rid ) newPhantom = 1; } remote_has(rid); }else + /* uvigot NAME MTIME HASH SIZE + ** + ** Server announces that it has a particular unversioned file. The + ** server will only send this card if the client had previously sent + ** a "pragma uv-hash" card with a hash that does not match. + ** + ** If the identified file needs to be transferred, then setup for the + ** transfer. Generate a "uvgimme" card in the reply if the server + ** version is newer than the client. Generate a "uvfile" card if + ** the client version is newer than the server. If HASH is "-" + ** (indicating that the file has been deleted) and MTIME is newer, + ** then do the deletion. + */ + if( xfer.nToken==5 + && blob_eq(&xfer.aToken[0], "uvigot") + && blob_is_filename(&xfer.aToken[1]) + && blob_is_int64(&xfer.aToken[2], &mtime) + && blob_is_int(&xfer.aToken[4], &size) + && (blob_eq(&xfer.aToken[3],"-") || blob_is_uuid(&xfer.aToken[3])) + ){ + const char *zName = blob_str(&xfer.aToken[1]); + const char *zHash = blob_str(&xfer.aToken[3]); + int iStatus; + if( uvStatus==0 ) uvStatus = 2; + iStatus = unversioned_status(zName, mtime, zHash); + if( iStatus<=1 ){ + if( zHash[0]!='-' ){ + blob_appendf(xfer.pOut, "uvgimme %s\n", zName); + nCardSent++; + nUvGimmeSent++; + }else if( iStatus==1 ){ + db_multi_exec( + "UPDATE unversioned" + " SET mtime=%lld, hash=NULL, sz=0, encoding=0, content=NULL" + " WHERE name=%Q", mtime, zName + ); + db_unset("uv-hash", 0); + } + }else if( iStatus==2 ){ + db_multi_exec( + "UPDATE unversioned SET mtime=%lld WHERE name=%Q", mtime, zName + ); + db_unset("uv-hash", 0); + } + if( iStatus<=3 ){ + db_multi_exec("DELETE FROM uv_tosend WHERE name=%Q", zName); + }else if( iStatus==4 ){ + db_multi_exec("UPDATE uv_tosend SET mtimeOnly=1 WHERE name=%Q",zName); + }else if( iStatus==5 ){ + db_multi_exec("REPLACE INTO uv_tosend(name,mtimeOnly) VALUES(%Q,0)", + zName); + } + }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. @@ -1828,11 +2203,12 @@ ** to the next cycle. */ if( blob_eq(&xfer.aToken[0],"message") && xfer.nToken==2 ){ char *zMsg = blob_terminate(&xfer.aToken[1]); defossilize(zMsg); - if( (syncFlags & SYNC_PUSH) && zMsg && sqlite3_strglob("pull only *", zMsg)==0 ){ + if( (syncFlags & SYNC_PUSH) && zMsg + && sqlite3_strglob("pull only *", zMsg)==0 ){ syncFlags &= ~SYNC_PUSH; zMsg = 0; } if( zMsg && zMsg[0] ){ fossil_force_newline(); @@ -1845,10 +2221,22 @@ ** 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 + ** 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". + */ + if( blob_eq(&xfer.aToken[1], "uv-pull-only") ){ + uvStatus = 1; + }else if( blob_eq(&xfer.aToken[1], "uv-push-ok") ){ + uvStatus = 2; + uvDoPush = 1; + } }else /* error MESSAGE ** ** Report an error and abandon the sync session. @@ -1943,13 +2331,17 @@ xfer.nDanglingFile = 0; /* If we have one or more files queued to send, then go ** another round */ - if( xfer.nFileSent+xfer.nDeltaSent>0 ){ + if( xfer.nFileSent+xfer.nDeltaSent>0 || uvDoPush ){ go = 1; } + + /* Continue looping as long as new uvfile cards are being received + ** and uvgimme cards are being sent. */ + if( nUvGimmeSent>0 && nUvFileRcvd>0 ) go = 1; /* If this is a clone, the go at least two rounds */ if( (syncFlags & SYNC_CLONE)!=0 && nCycle==1 ) go = 1; /* Stop the cycle if the server sends a "clone_seqno 0" card and Index: win/Makefile.dmc ================================================================== --- win/Makefile.dmc +++ win/Makefile.dmc @@ -28,13 +28,13 @@ SQLITE_OPTIONS = -DNDEBUG=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS=1 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_OMIT_DEPRECATED -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 SHELL_OPTIONS = -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=fossil_open -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen -SRC = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c foci_.c fshell_.c fusefs_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c piechart_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c sitemap_.c skins_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c wysiwyg_.c xfer_.c xfersetup_.c zip_.c +SRC = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c foci_.c fshell_.c fusefs_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c piechart_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c sitemap_.c skins_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c wysiwyg_.c xfer_.c xfersetup_.c zip_.c -OBJ = $(OBJDIR)\add$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\setup$O $(OBJDIR)\sha1$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\wysiwyg$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O +OBJ = $(OBJDIR)\add$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\setup$O $(OBJDIR)\sha1$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\wysiwyg$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O RC=$(DMDIR)\bin\rcc RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__ @@ -49,11 +49,11 @@ $(OBJDIR)\fossil.res: $B\win\fossil.rc $(RC) $(RCFLAGS) -o$@ $** $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res - +echo add allrepo attach bag bisect blob branch browse builtin bundle cache captcha cgi checkin checkout clearsign clone comformat configure content db delta deltacmd descendants diff diffcmd doc encode event export file finfo foci fshell fusefs glob graph gzip http http_socket http_ssl http_transport import info json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path piechart pivot popen pqueue printf publish purge rebuild regexp report rss schema search setup sha1 shun sitemap skins sqlcmd stash stat statrep style sync tag tar th_main timeline tkt tktsetup undo unicode update url user utf8 util verify vfile wiki wikiformat winfile winhttp wysiwyg xfer xfersetup zip shell sqlite3 th th_lang > $@ + +echo add allrepo attach bag bisect blob branch browse builtin bundle cache captcha cgi checkin checkout clearsign clone comformat configure content db delta deltacmd descendants diff diffcmd doc encode event export file finfo foci fshell fusefs glob graph gzip http http_socket http_ssl http_transport import info json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path piechart pivot popen pqueue printf publish purge rebuild regexp report rss schema search setup sha1 shun sitemap skins sqlcmd stash stat statrep style sync tag tar th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp wysiwyg xfer xfersetup zip shell sqlite3 th th_lang > $@ +echo fossil >> $@ +echo fossil >> $@ +echo $(LIBS) >> $@ +echo. >> $@ +echo fossil >> $@ @@ -752,10 +752,16 @@ $(OBJDIR)\unicode$O : unicode_.c unicode.h $(TCC) -o$@ -c unicode_.c unicode_.c : $(SRCDIR)\unicode.c +translate$E $** > $@ + +$(OBJDIR)\unversioned$O : unversioned_.c unversioned.h + $(TCC) -o$@ -c unversioned_.c + +unversioned_.c : $(SRCDIR)\unversioned.c + +translate$E $** > $@ $(OBJDIR)\update$O : update_.c update.h $(TCC) -o$@ -c update_.c update_.c : $(SRCDIR)\update.c @@ -844,7 +850,7 @@ zip_.c : $(SRCDIR)\zip.c +translate$E $** > $@ headers: makeheaders$E page_index.h builtin_data.h VERSION.h - +makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h foci_.c:foci.h fshell_.c:fshell.h fusefs_.c:fusefs.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h piechart_.c:piechart.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h wysiwyg_.c:wysiwyg.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h + +makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h foci_.c:foci.h fshell_.c:fshell.h fusefs_.c:fusefs.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h piechart_.c:piechart.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h wysiwyg_.c:wysiwyg.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h @copy /Y nul: headers Index: win/Makefile.mingw ================================================================== --- win/Makefile.mingw +++ win/Makefile.mingw @@ -526,10 +526,11 @@ $(SRCDIR)/timeline.c \ $(SRCDIR)/tkt.c \ $(SRCDIR)/tktsetup.c \ $(SRCDIR)/undo.c \ $(SRCDIR)/unicode.c \ + $(SRCDIR)/unversioned.c \ $(SRCDIR)/update.c \ $(SRCDIR)/url.c \ $(SRCDIR)/user.c \ $(SRCDIR)/utf8.c \ $(SRCDIR)/util.c \ @@ -699,10 +700,11 @@ $(OBJDIR)/timeline_.c \ $(OBJDIR)/tkt_.c \ $(OBJDIR)/tktsetup_.c \ $(OBJDIR)/undo_.c \ $(OBJDIR)/unicode_.c \ + $(OBJDIR)/unversioned_.c \ $(OBJDIR)/update_.c \ $(OBJDIR)/url_.c \ $(OBJDIR)/user_.c \ $(OBJDIR)/utf8_.c \ $(OBJDIR)/util_.c \ @@ -821,10 +823,11 @@ $(OBJDIR)/timeline.o \ $(OBJDIR)/tkt.o \ $(OBJDIR)/tktsetup.o \ $(OBJDIR)/undo.o \ $(OBJDIR)/unicode.o \ + $(OBJDIR)/unversioned.o \ $(OBJDIR)/update.o \ $(OBJDIR)/url.o \ $(OBJDIR)/user.o \ $(OBJDIR)/utf8.o \ $(OBJDIR)/util.o \ @@ -1154,10 +1157,11 @@ $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h \ $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h \ $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h \ $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h \ $(OBJDIR)/unicode_.c:$(OBJDIR)/unicode.h \ + $(OBJDIR)/unversioned_.c:$(OBJDIR)/unversioned.h \ $(OBJDIR)/update_.c:$(OBJDIR)/update.h \ $(OBJDIR)/url_.c:$(OBJDIR)/url.h \ $(OBJDIR)/user_.c:$(OBJDIR)/user.h \ $(OBJDIR)/utf8_.c:$(OBJDIR)/utf8.h \ $(OBJDIR)/util_.c:$(OBJDIR)/util.h \ @@ -2017,10 +2021,18 @@ $(OBJDIR)/unicode.o: $(OBJDIR)/unicode_.c $(OBJDIR)/unicode.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/unicode.o -c $(OBJDIR)/unicode_.c $(OBJDIR)/unicode.h: $(OBJDIR)/headers + +$(OBJDIR)/unversioned_.c: $(SRCDIR)/unversioned.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/unversioned.c >$@ + +$(OBJDIR)/unversioned.o: $(OBJDIR)/unversioned_.c $(OBJDIR)/unversioned.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/unversioned.o -c $(OBJDIR)/unversioned_.c + +$(OBJDIR)/unversioned.h: $(OBJDIR)/headers $(OBJDIR)/update_.c: $(SRCDIR)/update.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/update.c >$@ $(OBJDIR)/update.o: $(OBJDIR)/update_.c $(OBJDIR)/update.h $(SRCDIR)/config.h Index: win/Makefile.msc ================================================================== --- win/Makefile.msc +++ win/Makefile.msc @@ -445,10 +445,11 @@ timeline_.c \ tkt_.c \ tktsetup_.c \ undo_.c \ unicode_.c \ + unversioned_.c \ update_.c \ url_.c \ user_.c \ utf8_.c \ util_.c \ @@ -622,10 +623,11 @@ $(OX)\timeline$O \ $(OX)\tkt$O \ $(OX)\tktsetup$O \ $(OX)\undo$O \ $(OX)\unicode$O \ + $(OX)\unversioned$O \ $(OX)\update$O \ $(OX)\url$O \ $(OX)\user$O \ $(OX)\utf8$O \ $(OX)\util$O \ @@ -803,10 +805,11 @@ echo $(OX)\timeline.obj >> $@ echo $(OX)\tkt.obj >> $@ echo $(OX)\tktsetup.obj >> $@ echo $(OX)\undo.obj >> $@ echo $(OX)\unicode.obj >> $@ + echo $(OX)\unversioned.obj >> $@ echo $(OX)\update.obj >> $@ echo $(OX)\url.obj >> $@ echo $(OX)\user.obj >> $@ echo $(OX)\utf8.obj >> $@ echo $(OX)\util.obj >> $@ @@ -1558,10 +1561,16 @@ $(OX)\unicode$O : unicode_.c unicode.h $(TCC) /Fo$@ -c unicode_.c unicode_.c : $(SRCDIR)\unicode.c translate$E $** > $@ + +$(OX)\unversioned$O : unversioned_.c unversioned.h + $(TCC) /Fo$@ -c unversioned_.c + +unversioned_.c : $(SRCDIR)\unversioned.c + translate$E $** > $@ $(OX)\update$O : update_.c update.h $(TCC) /Fo$@ -c update_.c update_.c : $(SRCDIR)\update.c @@ -1758,10 +1767,11 @@ timeline_.c:timeline.h \ tkt_.c:tkt.h \ tktsetup_.c:tktsetup.h \ undo_.c:undo.h \ unicode_.c:unicode.h \ + unversioned_.c:unversioned.h \ update_.c:update.h \ url_.c:url.h \ user_.c:user.h \ utf8_.c:utf8.h \ util_.c:util.h \ Index: www/sync.wiki ================================================================== --- www/sync.wiki +++ www/sync.wiki @@ -167,18 +167,21 @@

    Privileges are cumulative. There can be multiple successful login cards. The session privileges are the bit-wise OR of the privileges of each individual login.

    -

    3.3 File and Compressed File Cards

    +

    3.3 File Cards

    -

    Artifacts are transferred using either "file" cards, or "cfile" cards. -(The name "file" card comes from the fact that most artifacts correspond to -files, and "cfile" is just a compressed file.) +

    Artifacts are transferred using either "file" cards, or "cfile" +or "uvfile" cards. +The name "file" card comes from the fact that most artifacts correspond to +files that are under version control. +The "cfile" name is an abbreviation for "compressed file". +The "uvfile" name is an abbreviation for "unversioned file".

    -

    3.3.1 File Cards

    +

    3.3.1 Ordinary File Cards

    For sync protocols, artifacts are transferred using "file" cards. File cards come in two different formats depending on whether the artifact is sent directly or as a delta from some other artifact.

    @@ -186,11 +189,11 @@
    file artifact-id size \n content
    file artifact-id delta-artifact-id size \n content
    -

    File cards are different from all other cards in that they +

    File cards are different from most other cards in that they are followed by in-line "payload" data. The content of the artifact or the artifact delta consists of the first size bytes of the x-fossil content that immediately follow the newline that terminates the file card. Only file and cfile cards have this characteristic.

    @@ -257,10 +260,44 @@

    The private card has no arguments and must directly precede a file card that contains the private content.

    +

    3.3.4 Unversioned File Cards

    + +

    Unversioned content is sent in both directions (client to server and +server to client) using "uvfile" cards in the following format: + +

    +uvfile name mtime hash size flags \n content +
    + +

    The name field is the name of the unversioned file. The +mtime is the last modification time of the file in seconds +since 1970. The hash field is the SHA1 hash of the content +for the unversioned file, or "-" for deleted content. +The size field is the (uncompressed) size of the content +in bytes. The flags field is an integer which is interpreted +as an array of bits. The 0x0004 bit of flags indicates that +the content is to be omitted. The content might be omitted if +it is too large to transmit, or if the send merely wants to update the +modification time of the file without changing the files content. +The content is the (uncompressed) content of the file. + +

    The receiver should only accept the uvfile card if the hash and +size match the content and if the mtime is newer than any existing +instance of the same file held by the receiver. The sender will not +normally transmit a uvfile card unless all these constraints are true, +but the receiver should double-check. + +

    A server should only accept uvfile cards if the login user has +the "y" write-unversioned permission. + +

    Servers send uvfile cards in response to uvgimme cards received from +the client. Clients send uvfile cards when they determine that the server +needs the content based on uvigot cards previously received from the server. +

    3.4 Push and Pull Cards

    Among the first cards in a client-to-server message are the push and pull cards. The push card tells the server that the client is pushing content. The pull card tells the server @@ -359,10 +396,42 @@

    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. +

    3.6.1 Unversioned Igot Cards

    + +

    Zero or more "uvigot" cards are sent from client to server when +synchronizing unversioned content. The format of a uvigot card is +as follows: + +

    +uvigot name mtime hash size +
    + +

    The name argument is the name of an unversioned file. +The mtime is the last modification time of the unversioned file +in seconds since 1970. +The hash is the SHA1 hash of the unversioned file content, or +"-" if the file has been deleted. +The size is the uncompressed size of the file in bytes. + +

    When the server sees a "pragma uv-hash" card for which the hash +does not match, it sends uvigot cards for every unversioned file that it +holds. The client will use this information to figure out which +unversioned files need to be synchronized. +The server might also send a uvigot card when it receives a uvgimme card +but its reply message size is already oversized and hence unable to hold +the usual uvfile reply. + +

    When a client receives a "uvigot" card, it checks to see if the the +file needs to be transfered from client to server or from server to client. +If a client-to-server transmission is needed, the client schedules that +transfer to occur on a subsequent HTTP request. If a server-to-client +transfer is needed, then the client sends a "uvgimme" card back to the +server to request the file content. +

    3.7 Gimme Cards

    A gimme card is sent from either client to server or from server to client. The gimme card asks the receiver to send a particular artifact back to the sender. The format of a gimme card is this:

    @@ -374,10 +443,26 @@

    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.

    +

    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 +as follows: + +

    +uvgimme name +
    + +

    The name is the name of the unversioned file found on the +server that the client would like to have. When a server sees a +uvgimme card, it normally responses with a uvfile card, though it might +also send anoter uvigot card if the HTTP reply is already oversized. +

    3.8 Cookie Cards

    A cookie card can be used by a server to record a small amount of state information on a client. The server sends a cookie to the client. The client sends the same cookie back to the server on @@ -539,10 +624,49 @@

    The send-catalog pragma instructs the server to transmit igot cards for every known artifact. This can help the client and server to get back in synchronization after a prior protocol error. The "--verily" option to the [/help?cmd=sync|fossil sync] command causes the send-catalog pragma to be transmitted.

    + +
  • uv-hash HASH +

    The uv-hash pragma is sent from client to server to provoke a +synchronization of unversioned content. The HASH is a SHA1 +hash of the names, modification times, and individual hashes of all +unversioned files on the client. If the unversioned content hash +from the client does not match the unversioned content hash on the +server, then the server will reply with either a "pragma uv-push-ok" +or "pragma uv-pull-only" card followed by one "uvigot" card for +each unversioned file currently held on the server. The collection +of "uvigot" cards sent in response to a "uv-hash" pragma is called +the "unversioned catalog". The client will used the unversioned +catalog to figure out which files (if any) need to be synchronized +between client and server and send appropriate "uvfile" or "uvgimme" +cards on the next HTTP request.

    + +

    If a client sends a uv-hash pragma and does not receive back +either a uv-pull-only or uv-push-ok pragma, that means that the +content on the server exactly matches the content on the client and +no further synchronization is required. + +

  • uv-pull-only +

    A server sends the uv-pull-only pragma to the client in response +to a uv-hash pragma with a mismatched content hash argument. This +pragma indicates that there are differences in unversioned content +between the client and server but that content can only be transfered +from server to client. The server is unwilling to accept content from +the client because the client login lacks the "write-unversioned" +permission.

    + +
  • uv-push-ok +

    A server sends the uv-push-ok pragma to the client in response +to a uv-hash pragma with a mismatched content hash argument. This +pragma indicates that there are differences in unversioned content +between the client and server and that content can only be transfered +in either direction. The server is willing to accept content from +the client because the client login has the "write-unversioned" +permission.

    +

    3.12 Comment Cards

    Any card that begins with "#" (ASCII 0x23) is a comment card and @@ -713,10 +837,52 @@ The first three steps of a pull are combined with the first five steps of a push. Steps (4) through (7) of a pull are combined with steps (5) through (8) of a push. And steps (8) through (10) of a pull are combined with step (9) of a push.

    +

    5.4 Unversioned File Sync

    + +

    "Unversioned files" are files held in the repository +where only the most recent version of the file is kept rather than +the entire change history. Unversioned files are intended to be +used to store ephemeral content, such as compiled binaries of the +most recent release. + +

    Unversioned files are identified by name and timestamp (mtime). +Only the most recent version of each file (the version with +the largest mtime value) is retained. + +

    Unversioned files are synchronized using the +[/help?cmd=unversioned|fossil unversioned sync] command. + +

    A schematic of an unversioned file synchronization is as follows: + +

      +
    1. The client sends a "pragma uv-hash" card to the server. The argument + to the uv-hash pragma is a hash of all filesnames, mtimes, and + content hashes for the unversioned files held by the client. +
      +
    2. If the unversion content hash from the client matches the unversioned + content hash on the server, then nothing needs to be done and the + server no-ops. But if the hashes are different, then the server + replies with either a uv-pull-only or a uv-push-ok pragma followed by + uvigot cards for all unversioned files held on the server. +
      +
    3. The client examines the uvigot cards received from the server and + determines which unversioned files need to be exchanged in order + to bring the client and server into synchronization. The client + then sends appropriate "uvgimme" or "uvfile" cards back to the + server. +
      +
    4. The server updates its unversioned file store with received "uvfile" + cards and answers "uvgimme" cards with "uvfile" cards in its reply. +
    + +

    The last two steps might be repeated multiple +times if there is more unversioned content to be transferred than will +fit comfortably in a single HTTP request. +

    6.0 Summary

    Here are the key points of the synchronization protocol:

      @@ -734,13 +900,16 @@
    1. clone_seqno sequence-number
    2. file artifact-id size \n content
    3. file artifact-id delta-artifact-id size \n content
    4. cfile artifact-id size \n content
    5. cfile artifact-id delta-artifact-id size \n content +
    6. uvfile name mtime hash size flags \n content
    7. private
    8. igot artifact-id ?flag? +
    9. uvigot name mtime hash size
    10. gimme artifact-id +
    11. uvgimme name
    12. cookie cookie-text
    13. reqconfig parameter-name
    14. config parameter-name size \n content
    15. pragma name value...
    16. error error-message