Fossil

Check-in [dbc1c62a]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Begin work on the "fossil mirror" command.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | mirror-cmd
Files: files | file ages | folders
SHA3-256: dbc1c62a995ae7616015ca2e40252bfed1df914443ec357f732a530fde204287
User & Date: drh 2019-03-14 17:16:37
Context
2019-03-14
18:20
Progress on the "fossil mirror" command. check-in: 5063eb52 user: drh tags: mirror-cmd
17:16
Begin work on the "fossil mirror" command. check-in: dbc1c62a user: drh tags: mirror-cmd
13:52
When doing a "fossil stash" make sure that the stash has committed before deleting changes from disk, so that the changes can be recovered if something goes wrong. check-in: 60af514d user: drh tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/export.c.

    26     26   */
    27     27   static struct {
    28     28     const char *zTrunkName;     /* Name of trunk branch */
    29     29   } gexport;
    30     30   
    31     31   #if INTERFACE
    32     32   /*
    33         -** struct mark_t
    34         -**   holds information for translating between git commits
    35         -**   and fossil commits.
    36         -**   -git_name: This is the mark name that identifies the commit to git.
    37         -**              It will always begin with a ':'.
    38         -**   -rid: The unique object ID that identifies this commit within the
    39         -**         repository database.
    40         -**   -uuid: The SHA-1/SHA-3 of artifact corresponding to rid.
           33  +** Each line in a git-fast-export "marK" file is an instance of
           34  +** this object.
    41     35   */
    42         -struct mark_t{
    43         -  char *name;
    44         -  int rid;
    45         -  char uuid[65];
           36  +struct mark_t {
           37  +  char *name;       /* Name of the mark.  Also starts with ":" */
           38  +  int rid;          /* Corresponding object in the BLOB table */
           39  +  char uuid[65];    /* The GIT hash name for this object */
    46     40   };
    47     41   #endif
    48     42   
    49     43   /*
    50     44   ** Output a "committer" record for the given user.
    51     45   ** NOTE: the given user name may be an email itself.
    52     46   */
................................................................................
   295    289         return NULL;
   296    290       }
   297    291     }
   298    292     return zMark;
   299    293   }
   300    294   
   301    295   /*
   302         -** parse_mark()
   303         -**   Create a new (mark,rid,uuid) entry in the 'xmark' table given a line
   304         -**   from a marks file.  Return the cross-ref information as a struct mark_t
   305         -**   in *mark.
   306         -**   This function returns -1 in the case that the line is blank, malformed, or
   307         -**   the rid/uuid named in 'line' does not match what is in the repository
   308         -**   database.  Otherwise, 0 is returned.
   309         -**   mark->name is dynamically allocated, and owned by the caller.
          296  +** Parse a single line of the mark file.  Store the result in the mark object.
          297  +**
          298  +** "line" is a single line of input.
          299  +** This function returns -1 in the case that the line is blank, malformed, or
          300  +** the rid/uuid named in 'line' does not match what is in the repository
          301  +** database.  Otherwise, 0 is returned.
          302  +**
          303  +** mark->name is dynamically allocated, and owned by the caller.
   310    304   */
   311    305   int parse_mark(char *line, struct mark_t *mark){
   312    306     char *cur_tok;
   313    307     char type_;
   314    308     cur_tok = strtok(line, " \t");
   315    309     if( !cur_tok || strlen(cur_tok)<2 ){
   316    310       return -1;
................................................................................
   359    353   
   360    354     /* insert a cross-ref into the 'xmark' table */
   361    355     insert_commit_xref(mark->rid, mark->name, mark->uuid);
   362    356     return 0;
   363    357   }
   364    358   
   365    359   /*
   366         -** import_marks()
   367         -**   Import the marks specified in file 'f' into the 'xmark' table.
   368         -**   If 'blobs' is non-null, insert all blob marks into it.
   369         -**   If 'vers' is non-null, insert all commit marks into it.
   370         -**   If 'unused_marks' is non-null, upon return of this function, all values
   371         -**   x >= *unused_marks are free to use as marks, i.e. they do not clash with
   372         -**   any marks appearing in the marks file.
   373         -**   Each line in the file must be at most 100 characters in length.  This
   374         -**   seems like a reasonable maximum for a 40-character uuid, and 1-13
   375         -**   character rid.
   376         -**   The function returns -1 if any of the lines in file 'f' are malformed,
   377         -**   or the rid/uuid information doesn't match what is in the repository
   378         -**   database.  Otherwise, 0 is returned.
          360  +** Import the marks specified in file 'f';
          361  +** If 'blobs' is non-null, insert all blob marks into it.
          362  +** If 'vers' is non-null, insert all commit marks into it.
          363  +** If 'unused_marks' is non-null, upon return of this function, all values
          364  +** x >= *unused_marks are free to use as marks, i.e. they do not clash with
          365  +** any marks appearing in the marks file.
          366  +**
          367  +** Each line in the file must be at most 100 characters in length.  This
          368  +** seems like a reasonable maximum for a 40-character uuid, and 1-13
          369  +** character rid.
          370  +**
          371  +** The function returns -1 if any of the lines in file 'f' are malformed,
          372  +** or the rid/uuid information doesn't match what is in the repository
          373  +** database.  Otherwise, 0 is returned.
   379    374   */
   380    375   int import_marks(FILE* f, Bag *blobs, Bag *vers, unsigned int *unused_mark){
   381    376     char line[101];
   382    377     while(fgets(line, sizeof(line), f)){
   383    378       struct mark_t mark;
   384    379       if( strlen(line)==100 && line[99]!='\n' ){
   385    380         /* line too long */
................................................................................
   505    500   
   506    501     db_find_and_open_repository(0, 2);
   507    502     verify_all_options();
   508    503     if( g.argc!=2 && g.argc!=3 ){ usage("--git ?REPOSITORY?"); }
   509    504   
   510    505     db_multi_exec("CREATE TEMPORARY TABLE oldblob(rid INTEGER PRIMARY KEY)");
   511    506     db_multi_exec("CREATE TEMPORARY TABLE oldcommit(rid INTEGER PRIMARY KEY)");
   512         -  db_multi_exec("CREATE TEMP TABLE xmark(tname TEXT UNIQUE, trid INT, tuuid TEXT)");
          507  +  db_multi_exec("CREATE TEMP TABLE xmark(tname TEXT UNIQUE, trid INT,"
          508  +                " tuuid TEXT)");
   513    509     db_multi_exec("CREATE INDEX xmark_trid ON xmark(trid)");
   514    510     if( markfile_in!=0 ){
   515    511       Stmt qb,qc;
   516    512       FILE *f;
   517    513       int rid;
   518    514   
   519    515       f = fossil_fopen(markfile_in, "r");
................................................................................
   752    748   **        tid INTEGER PRIMARY KEY,   -- Check-in id
   753    749   **        tseq INT                   -- integer total order on check-ins.
   754    750   **     );
   755    751   **
   756    752   ** This table contains all check-ins of the repository in topological
   757    753   ** order.  "Topological order" means that every parent check-in comes
   758    754   ** before all of its children.  Topological order is *almost* the same
   759         -** thing as "ORDER BY event.mtime".  Differences only arrise when there
          755  +** thing as "ORDER BY event.mtime".  Differences only arise when there
   760    756   ** are timewarps.  In as much as Git hates timewarps, we have to compute
   761    757   ** a correct topological order when doing an export.
   762    758   **
   763    759   ** Since mtime is a usually already nearly in topological order, the
   764    760   ** algorithm is to start with mtime, then make adjustments as necessary
   765    761   ** for timewarps.  This is not a great algorithm for the general case,
   766    762   ** but it is very fast for the overwhelmingly common case where there
................................................................................
   833    829   */
   834    830   void test_topological_sort(void){
   835    831     int n;
   836    832     db_find_and_open_repository(0, 0);
   837    833     n = topological_sort_checkins(1);
   838    834     fossil_print("%d reorderings required\n", n);
   839    835   }
          836  +
          837  +/*
          838  +** Transfer a tag over to the mirror.  "rid" is the BLOB.RID value for
          839  +** the record that describes the tag.
          840  +**
          841  +** The Git tag mechanism is very limited compared to Fossil.  Many Fossil
          842  +** tags cannot be exported to Git.  If this tag cannot be exported, then
          843  +** silently ignore it.
          844  +*/
          845  +static void mirror_send_tag(FILE *xCmd, int rid){
          846  +  return;
          847  +}
          848  +
          849  +/*
          850  +** Locate the mark for a UUID.
          851  +**
          852  +** If the mark does not exist and if the bCreate flag is false, then
          853  +** return 0.  If the mark does not exist and the bCreate flag is true,
          854  +** then create the mark.
          855  +*/
          856  +static int mirror_find_mark(const char *zUuid, int bCreate){
          857  +  int iMark;
          858  +  static Stmt sFind, sIns;
          859  +  db_static_prepare(&sFind,
          860  +    "SELECT id FROM mirror.mmark WHERE uuid=:uuid"
          861  +  );
          862  +  db_bind_text(&sFind, ":uuid", zUuid);
          863  +  if( db_step(&sFind)==SQLITE_ROW ){
          864  +    iMark = db_column_int(&sFind, 0);
          865  +    db_reset(&sFind);
          866  +    return iMark;
          867  +  }
          868  +  db_reset(&sFind);
          869  +  if( !bCreate ) return 0;
          870  +  db_static_prepare(&sIns,
          871  +    "INSERT INTO mirror.mmark(uuid) VALUES(:uuid)"
          872  +  );
          873  +  db_bind_text(&sIns, ":uuid", zUuid);
          874  +  db_step(&sIns);
          875  +  db_reset(&sIns);
          876  +  return db_last_insert_rowid();
          877  +}
          878  +
          879  +/*
          880  +** Export a single file named by zUuid.
          881  +*/
          882  +static void mirror_send_file(FILE *xCmd, const char *zUuid){
          883  +  int iMark;
          884  +  int rid;
          885  +  int rc;
          886  +  Blob data;
          887  +  rid = fast_uuid_to_rid(zUuid);
          888  +  if( rid<0 ) fossil_fatal("no rid for %s", zUuid);
          889  +  rc = content_get(rid, &data);
          890  +  if( rc==0 ) fossil_fatal("%s is a phantom", zUuid);
          891  +  iMark = mirror_find_mark(zUuid, 1);
          892  +  fprintf(xCmd, "blob\nmark :%d\ndata %d\n", iMark, blob_size(&data));
          893  +  fwrite(blob_buffer(&data), 1, blob_size(&data), xCmd);
          894  +  fprintf(xCmd, "\n");
          895  +  blob_reset(&data);
          896  +}
          897  +
          898  +/*
          899  +** Transfer a check-in over to the mirror.  "rid" is the BLOB.RID for
          900  +** the check-in to export.
          901  +**
          902  +** If any ancestor of the check-in has not yet been exported, then
          903  +** invoke this routine recursively to export the ancestor first.
          904  +** This can only happen on a timewarp, so deep nesting is unlikely.
          905  +**
          906  +** Before sending the check-in, first make sure all associated files
          907  +** have already been exported, and send "blob" records for any that
          908  +** have not been.  Update the MIRROR.MMARK table so that it holds the
          909  +** marks for the exported files. 
          910  +*/
          911  +static void mirror_send_checkin(
          912  +  FILE *xCmd,           /* Write fast-import text on this pipe */
          913  +  int rid,              /* BLOB.RID for the check-in to export */
          914  +  const char *zUuid,    /* BLOB.UUID for the check-in to export */
          915  +  int *pnLimit          /* Stop when the counter reaches zero */
          916  +){
          917  +  Manifest *pMan;
          918  +  int i;
          919  +  Blob err;
          920  +  Stmt q;
          921  +  char *zBranch;
          922  +  int iMark;
          923  +  Blob sql;
          924  +
          925  +  blob_init(&err, 0, 0);
          926  +  pMan = manifest_get(rid, CFTYPE_MANIFEST, &err);
          927  +  if( pMan==0 ){
          928  +    fossil_fatal("cannot fetch manifest for check-in %d: %s", rid, 
          929  +                 blob_str(&err));
          930  +  }
          931  +
          932  +  /* Check to see if any parent logins have not yet been processed, and
          933  +  ** if so, create them */
          934  +  for(i=0; i<pMan->nParent; i++){
          935  +    int iMark = mirror_find_mark(pMan->azParent[i], 0);
          936  +    if( iMark<=0 ){
          937  +      int prid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q",
          938  +                        pMan->azParent[i]);
          939  +      mirror_send_checkin(xCmd, prid, pMan->azParent[i], pnLimit);
          940  +      if( *pnLimit<=0 ){
          941  +        manifest_destroy(pMan);
          942  +        return;
          943  +      }
          944  +    }
          945  +  }
          946  +
          947  +  /* Make sure all necessary files have been exported */
          948  +  db_prepare(&q,
          949  +    "SELECT uuid FROM files_of_checkin(%Q)"
          950  +    " WHERE uuid NOT IN (SELECT uuid FROM mirror.mmark)",
          951  +    zUuid
          952  +  );
          953  +  while( db_step(&q)==SQLITE_ROW ){
          954  +    const char *zFUuid = db_column_text(&q, 0);
          955  +    mirror_send_file(xCmd, zFUuid);
          956  +  }
          957  +  db_finalize(&q);
          958  +
          959  +  /* Figure out which branch this check-in is a member of */
          960  +  zBranch = db_text(0,
          961  +    "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=%d",
          962  +    TAG_BRANCH, rid
          963  +  );
          964  +  if( fossil_strcmp(zBranch,"trunk")==0 ){
          965  +    fossil_free(zBranch);
          966  +    zBranch = mprintf("master");
          967  +  }
          968  +
          969  +  /* Export the check-in */
          970  +  fprintf(xCmd, "commit refs/head/%s\n", zBranch);
          971  +  fossil_free(zBranch);
          972  +  iMark = mirror_find_mark(zUuid, 1);
          973  +  fprintf(xCmd, "mark :%d\n", iMark);
          974  +  fprintf(xCmd, "committer %s <%s@noemail.net> %lld +0000\n",
          975  +     pMan->zUser, pMan->zUser, 
          976  +     (sqlite3_int64)(pMan->rDate-2440587.5)*86400
          977  +  );
          978  +  fprintf(xCmd, "data %d\n", (int)strlen(pMan->zComment));
          979  +  fprintf(xCmd, "%s\n", pMan->zComment);
          980  +  for(i=0; i<pMan->nParent; i++){
          981  +    int iOther = mirror_find_mark(pMan->azParent[i], 0);
          982  +    if( i==0 ){
          983  +      fprintf(xCmd, "from :%d\n", iOther);
          984  +    }else{
          985  +      fprintf(xCmd, "merge :%d\n", iOther);
          986  +    }
          987  +  }
          988  +  if( pMan->nParent ){
          989  +    db_prepare(&q,
          990  +      "SELECT filename FROM files_of_checkin(%Q)"
          991  +      " EXCEPT SELECT filename FROM files_of_checkin(%Q)",
          992  +      pMan->azParent[0], zUuid
          993  +    );
          994  +    while( db_step(&q)==SQLITE_ROW ){
          995  +      fprintf(xCmd, "D %s\n", db_column_text(&q,0));
          996  +    }
          997  +    db_finalize(&q);
          998  +  }
          999  +  blob_init(&sql, 0, 0);
         1000  +  blob_append_sql(&sql,
         1001  +    "SELECT filename, uuid, perm FROM files_of_checkin(%Q)",
         1002  +    zUuid
         1003  +  );
         1004  +  if( pMan->nParent ){
         1005  +    blob_append_sql(&sql,
         1006  +      " EXCEPT SELECT filename, uuid, perm FROM files_of_checkin(%Q)",
         1007  +      pMan->azParent[0]);
         1008  +  }
         1009  +  db_prepare(&q,
         1010  +     "SELECT x.filename, x.perm, mmark.id FROM (%s) AS x, mirror.mmark"
         1011  +     " WHERE mmark.uuid=x.uuid",
         1012  +     blob_sql_text(&sql)
         1013  +  );
         1014  +  blob_reset(&sql);
         1015  +  while( db_step(&q)==SQLITE_ROW ){
         1016  +    const char *zFilename = db_column_text(&q,0);
         1017  +    const char *zMode = db_column_text(&q,1);
         1018  +    int iMark = db_column_int(&q,2);
         1019  +    const char *zGitMode = "100644";
         1020  +    if( zMode ){
         1021  +      if( strchr(zMode,'x') ) zGitMode = "100755";
         1022  +      if( strchr(zMode,'l') ) zGitMode = "120000";
         1023  +    }
         1024  +    fprintf(xCmd,"M %s :%d %s\n", zGitMode, iMark, zFilename);
         1025  +  }
         1026  +  db_finalize(&q);
         1027  +
         1028  +  /* The check-in is finished, so decrement the counter */
         1029  +  (*pnLimit)--;
         1030  +}
         1031  +
         1032  +/*
         1033  +** COMMAND: mirror
         1034  +**
         1035  +** Usage: %fossil mirror [--git] MIRROR [-R FOSSIL-REPO]
         1036  +**
         1037  +** Create or update another type of repository that is is mirror of
         1038  +** a Fossil repository.
         1039  +**
         1040  +** The current implementation only supports mirrors to Git, and so
         1041  +** the --git option is optional.  The ability to mirror to other version
         1042  +** control systems may be added in the future, in which case an argument
         1043  +** to specify the target version control system will become required.
         1044  +**
         1045  +** The MIRROR argument is the name of the secondary repository.  In the
         1046  +** case of Git, it is the directory that houses the Git repository.
         1047  +** If MIRROR does not previously exist, it is created and initialized to
         1048  +** a copy of the Fossil repository.  If MIRROR does already exist, it is
         1049  +** updated with new check-ins that have been added to the Fossil repository
         1050  +** since the last "fossil mirror" command to that particular repository.
         1051  +**
         1052  +** Implementation notes:
         1053  +**
         1054  +**    *  The git version control system must be installed in order for
         1055  +**       this command to work.  Fossil will invoke various git commands
         1056  +**       to run as subprocesses.
         1057  +**
         1058  +**    *  Fossil creates a directory named ".mirror_state" in the top level of
         1059  +**       the created git repository and stores state information in that
         1060  +**       directory.  Do not attempt to manage any files in that directory.
         1061  +**       Do not change or delete any files in that directory.  Doing so
         1062  +**       may disrupt future calls to the "fossil mirror" command for the
         1063  +**       mirror repository.
         1064  +**
         1065  +** Options:
         1066  +**
         1067  +**    --debug FILE             Write fast-export text to FILE rather than
         1068  +**                             piping it into "git fast-import".
         1069  +**
         1070  +**    --limit N                Add no more than N new check-ins to MIRROR.
         1071  +**                             Useful for debugging
         1072  +*/
         1073  +void mirror_command(void){
         1074  +  const char *zLimit;
         1075  +  int nLimit = 0x7fffffff;
         1076  +  int nTotal = 0;
         1077  +  char *zMirror;
         1078  +  char *z;
         1079  +  char *zInFile;
         1080  +  char *zOutFile;
         1081  +  char *zCmd;
         1082  +  const char *zDebug = 0;
         1083  +  double rEnd;
         1084  +  int rc;
         1085  +  FILE *xCmd;
         1086  +  FILE *pIn, *pOut;
         1087  +  Stmt q;
         1088  +  char zLine[200];
         1089  +
         1090  +  find_option("git", 0, 0);   /* Ignore the --git option for now */
         1091  +  zDebug = find_option("debug",0,1);
         1092  +  db_find_and_open_repository(0, 2);
         1093  +  zLimit = find_option("limit", 0, 1);
         1094  +  if( zLimit ){
         1095  +    nLimit = (unsigned int)atoi(zLimit);
         1096  +    if( nLimit<=0 ) fossil_fatal("--limit must be positive");
         1097  +  }
         1098  +  verify_all_options();
         1099  +  if( g.argc!=3 ){ usage("--git MIRROR"); }
         1100  +  zMirror = g.argv[2];
         1101  +
         1102  +  /* Make sure the GIT repository directory exists */
         1103  +  rc = file_mkdir(zMirror, ExtFILE, 0);
         1104  +  if( rc ) fossil_fatal("cannot create directory \"%s\"", zMirror);
         1105  +
         1106  +  /* Make sure GIT has been initialized */
         1107  +  z = mprintf("%s/.git", zMirror);
         1108  +  if( !file_isdir(z, ExtFILE) ){
         1109  +    zCmd = mprintf("git init '%s'",zMirror);
         1110  +    fossil_print("%s\n", zCmd);
         1111  +    rc = fossil_system(zCmd);
         1112  +    if( rc ){
         1113  +      fossil_fatal("command failed: \"%s\"", zCmd);
         1114  +    }
         1115  +    fossil_free(zCmd);
         1116  +  }
         1117  +  fossil_free(z);
         1118  +  
         1119  +  /* Make sure the .mirror_state subdirectory exists */
         1120  +  z = mprintf("%s/.mirror_state", zMirror);
         1121  +  rc = file_mkdir(z, ExtFILE, 0);
         1122  +  if( rc ) fossil_fatal("cannot create directory \"%s\"", z);
         1123  +  fossil_free(z);
         1124  +
         1125  +  /* Attach the .mirror_state/db database */
         1126  +  db_multi_exec("ATTACH '%q/.mirror_state/db' AS mirror;", zMirror);
         1127  +  db_multi_exec(
         1128  +    "CREATE TABLE IF NOT EXISTS mirror.mconfig(\n"
         1129  +    "  key TEXT PRIMARY KEY,\n"
         1130  +    "  Value ANY\n"
         1131  +    ") WITHOUT ROWID;\n"
         1132  +    "CREATE TABLE IF NOT EXISTS mirror.mmark(\n"
         1133  +    "  id INTEGER PRIMARY KEY,\n"
         1134  +    "  uuid TEXT UNIQUE\n"
         1135  +    ");"
         1136  +    "CREATE TABLE IF NOT EXISTS mirror.mtag(\n"
         1137  +    "  tagname TEXT PRIMARY KEY,\n"
         1138  +    "  cnt INTEGER DEFAULT 1\n"
         1139  +    ") WITHOUT ROWID;"
         1140  +  );
         1141  +
         1142  +  /* See if there is any work to be done.  Exit early if not, before starting
         1143  +  ** the "git fast-import" command. */
         1144  +  if( !db_exists("SELECT 1 FROM event WHERE type IN ('t','ci')"
         1145  +                 " AND mtime>coalesce((SELECT value FROM mconfig"
         1146  +                                        " WHERE key='start'),0.0)")
         1147  +  ){
         1148  +    fossil_print("no changes\n");
         1149  +    return;
         1150  +  }
         1151  +
         1152  +  /* Change to the MIRROR directory so that the Git commands will work */
         1153  +  rc = file_chdir(zMirror, 0);
         1154  +  if( rc ) fossil_fatal("cannot change the working directory to \"%s\"",
         1155  +                        zMirror);
         1156  +
         1157  +  /* Start up the git fast-import command */
         1158  +  if( zDebug ){
         1159  +    if( fossil_strcmp(zDebug,"stdout")==0 ){
         1160  +      xCmd = stdout;
         1161  +    }else{
         1162  +      xCmd = fopen(zDebug, "wb");
         1163  +      if( xCmd==0 ) fossil_fatal("cannot open file \"%s\" for writing", zDebug);
         1164  +    }
         1165  +  }else{
         1166  +    zCmd = mprintf("git fast-import"
         1167  +              " --import-marks-if-exists=.mirror_state/in"
         1168  +              " --export-marks=.mirror_state/out"
         1169  +              " --quiet --done");
         1170  +    fossil_print("%s\n", zCmd);
         1171  +    xCmd = popen(zCmd, "w");
         1172  +    if( zCmd==0 ){
         1173  +      fossil_fatal("cannot start the \"git fast-import\" command");
         1174  +    }
         1175  +    fossil_free(zCmd);
         1176  +  }
         1177  +
         1178  +  /* Run the export */
         1179  +  rEnd = 0.0;
         1180  +  db_multi_exec(
         1181  +    "CREATE TEMP TABLE tomirror(objid INTEGER PRIMARY KEY,type,mtime,uuid);\n"
         1182  +    "INSERT INTO tomirror "
         1183  +    "SELECT objid, type, mtime, blob.uuid FROM event, blob\n"
         1184  +    " WHERE type IN ('ci','t')"
         1185  +    "   AND mtime>coalesce((SELECT value FROM mconfig WHERE key='start'),0.0)"
         1186  +    "   AND blob.rid=event.objid"
         1187  +    "   AND blob.uuid NOT IN (SELECT uuid FROM mirror.mmark);"
         1188  +  );
         1189  +  nTotal = db_int(0, "SELECT count(*) FROM tomirror");
         1190  +  if( nLimit<nTotal ) nTotal = nLimit;
         1191  +  db_prepare(&q,
         1192  +    "SELECT objid, type, mtime, uuid FROM tomirror ORDER BY mtime"
         1193  +  );
         1194  +  while( nLimit && db_step(&q)==SQLITE_ROW ){
         1195  +    double rMTime = db_column_double(&q, 2);
         1196  +    const char *zType = db_column_text(&q, 1);
         1197  +    int rid = db_column_int(&q, 0);
         1198  +    const char *zUuid = db_column_text(&q, 3);
         1199  +    if( rMTime>rEnd ) rEnd = rMTime;
         1200  +    if( zType[0]=='t' ){
         1201  +      mirror_send_tag(xCmd, rid);
         1202  +    }else{
         1203  +      mirror_send_checkin(xCmd, rid, zUuid, &nLimit);
         1204  +      printf("\r%d/%d  ", nTotal-nLimit, nTotal);
         1205  +      fflush(stdout);
         1206  +    }
         1207  +  }
         1208  +  db_finalize(&q);
         1209  +  db_prepare(&q, "REPLACE INTO mirror.mconfig(key,value) VALUES('start',:x)");
         1210  +  db_bind_double(&q, ":x", rEnd);
         1211  +  db_step(&q);
         1212  +  db_finalize(&q);
         1213  +  fprintf(xCmd, "done\n");
         1214  +  if( zDebug ){
         1215  +    if( xCmd!=stdout ) fclose(xCmd);
         1216  +  }else{
         1217  +    pclose(xCmd);
         1218  +  }
         1219  +  fossil_print("%d check-ins added to the mirror\n", nTotal-nLimit);
         1220  +
         1221  +  /* Read the export-marks file.  Transfer the new marks over into
         1222  +  ** the import-marks file.
         1223  +  */
         1224  +  zInFile = mprintf("%s/.mirror_state/in", zMirror);
         1225  +  zOutFile = mprintf("%s/.mirror_state/out", zMirror);
         1226  +  pOut = fopen(zOutFile, "rb");
         1227  +  if( pOut ){
         1228  +    pIn = fopen(zInFile, "ab");
         1229  +    if( pIn==0 ){
         1230  +      fossil_fatal("cannot open %s for appending", zInFile);
         1231  +    }
         1232  +    while( fgets(zLine, sizeof(zLine), pIn) ){
         1233  +      fputs(zLine, pOut);
         1234  +    }
         1235  +    fclose(pOut);
         1236  +    fclose(pIn);
         1237  +    file_delete(zOutFile);
         1238  +  }
         1239  +  fossil_free(zInFile);
         1240  +  fossil_free(zOutFile);
         1241  +
         1242  +  /* Optionally do a "git push" */
         1243  +}