Fossil SCM

Begin work on the "fossil mirror" command.

drh 2019-03-14 17:16 UTC trunk
Commit dbc1c62a995ae7616015ca2e40252bfed1df914443ec357f732a530fde204287
1 file changed +439 -35
+439 -35
--- src/export.c
+++ src/export.c
@@ -28,23 +28,17 @@
2828
const char *zTrunkName; /* Name of trunk branch */
2929
} gexport;
3030
3131
#if INTERFACE
3232
/*
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.
4135
*/
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 */
4640
};
4741
#endif
4842
4943
/*
5044
** Output a "committer" record for the given user.
@@ -297,18 +291,18 @@
297291
}
298292
return zMark;
299293
}
300294
301295
/*
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.
310304
*/
311305
int parse_mark(char *line, struct mark_t *mark){
312306
char *cur_tok;
313307
char type_;
314308
cur_tok = strtok(line, " \t");
@@ -361,23 +355,24 @@
361355
insert_commit_xref(mark->rid, mark->name, mark->uuid);
362356
return 0;
363357
}
364358
365359
/*
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.
379374
*/
380375
int import_marks(FILE* f, Bag *blobs, Bag *vers, unsigned int *unused_mark){
381376
char line[101];
382377
while(fgets(line, sizeof(line), f)){
383378
struct mark_t mark;
@@ -507,11 +502,12 @@
507502
verify_all_options();
508503
if( g.argc!=2 && g.argc!=3 ){ usage("--git ?REPOSITORY?"); }
509504
510505
db_multi_exec("CREATE TEMPORARY TABLE oldblob(rid INTEGER PRIMARY KEY)");
511506
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)");
513509
db_multi_exec("CREATE INDEX xmark_trid ON xmark(trid)");
514510
if( markfile_in!=0 ){
515511
Stmt qb,qc;
516512
FILE *f;
517513
int rid;
@@ -754,11 +750,11 @@
754750
** );
755751
**
756752
** This table contains all check-ins of the repository in topological
757753
** order. "Topological order" means that every parent check-in comes
758754
** 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
760756
** are timewarps. In as much as Git hates timewarps, we have to compute
761757
** a correct topological order when doing an export.
762758
**
763759
** Since mtime is a usually already nearly in topological order, the
764760
** algorithm is to start with mtime, then make adjustments as necessary
@@ -835,5 +831,413 @@
835831
int n;
836832
db_find_and_open_repository(0, 0);
837833
n = topological_sort_checkins(1);
838834
fossil_print("%d reorderings required\n", n);
839835
}
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 <%[email protected]> %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
+}
8401244
--- src/export.c
+++ src/export.c
@@ -28,23 +28,17 @@
28 const char *zTrunkName; /* Name of trunk branch */
29 } gexport;
30
31 #if INTERFACE
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.
41 */
42 struct mark_t{
43 char *name;
44 int rid;
45 char uuid[65];
46 };
47 #endif
48
49 /*
50 ** Output a "committer" record for the given user.
@@ -297,18 +291,18 @@
297 }
298 return zMark;
299 }
300
301 /*
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.
310 */
311 int parse_mark(char *line, struct mark_t *mark){
312 char *cur_tok;
313 char type_;
314 cur_tok = strtok(line, " \t");
@@ -361,23 +355,24 @@
361 insert_commit_xref(mark->rid, mark->name, mark->uuid);
362 return 0;
363 }
364
365 /*
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.
 
379 */
380 int import_marks(FILE* f, Bag *blobs, Bag *vers, unsigned int *unused_mark){
381 char line[101];
382 while(fgets(line, sizeof(line), f)){
383 struct mark_t mark;
@@ -507,11 +502,12 @@
507 verify_all_options();
508 if( g.argc!=2 && g.argc!=3 ){ usage("--git ?REPOSITORY?"); }
509
510 db_multi_exec("CREATE TEMPORARY TABLE oldblob(rid INTEGER PRIMARY KEY)");
511 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)");
 
513 db_multi_exec("CREATE INDEX xmark_trid ON xmark(trid)");
514 if( markfile_in!=0 ){
515 Stmt qb,qc;
516 FILE *f;
517 int rid;
@@ -754,11 +750,11 @@
754 ** );
755 **
756 ** This table contains all check-ins of the repository in topological
757 ** order. "Topological order" means that every parent check-in comes
758 ** before all of its children. Topological order is *almost* the same
759 ** thing as "ORDER BY event.mtime". Differences only arrise when there
760 ** are timewarps. In as much as Git hates timewarps, we have to compute
761 ** a correct topological order when doing an export.
762 **
763 ** Since mtime is a usually already nearly in topological order, the
764 ** algorithm is to start with mtime, then make adjustments as necessary
@@ -835,5 +831,413 @@
835 int n;
836 db_find_and_open_repository(0, 0);
837 n = topological_sort_checkins(1);
838 fossil_print("%d reorderings required\n", n);
839 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
840
--- src/export.c
+++ src/export.c
@@ -28,23 +28,17 @@
28 const char *zTrunkName; /* Name of trunk branch */
29 } gexport;
30
31 #if INTERFACE
32 /*
33 ** Each line in a git-fast-export "marK" file is an instance of
34 ** this object.
 
 
 
 
 
 
35 */
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 */
40 };
41 #endif
42
43 /*
44 ** Output a "committer" record for the given user.
@@ -297,18 +291,18 @@
291 }
292 return zMark;
293 }
294
295 /*
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.
304 */
305 int parse_mark(char *line, struct mark_t *mark){
306 char *cur_tok;
307 char type_;
308 cur_tok = strtok(line, " \t");
@@ -361,23 +355,24 @@
355 insert_commit_xref(mark->rid, mark->name, mark->uuid);
356 return 0;
357 }
358
359 /*
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.
374 */
375 int import_marks(FILE* f, Bag *blobs, Bag *vers, unsigned int *unused_mark){
376 char line[101];
377 while(fgets(line, sizeof(line), f)){
378 struct mark_t mark;
@@ -507,11 +502,12 @@
502 verify_all_options();
503 if( g.argc!=2 && g.argc!=3 ){ usage("--git ?REPOSITORY?"); }
504
505 db_multi_exec("CREATE TEMPORARY TABLE oldblob(rid INTEGER PRIMARY KEY)");
506 db_multi_exec("CREATE TEMPORARY TABLE oldcommit(rid INTEGER PRIMARY KEY)");
507 db_multi_exec("CREATE TEMP TABLE xmark(tname TEXT UNIQUE, trid INT,"
508 " tuuid TEXT)");
509 db_multi_exec("CREATE INDEX xmark_trid ON xmark(trid)");
510 if( markfile_in!=0 ){
511 Stmt qb,qc;
512 FILE *f;
513 int rid;
@@ -754,11 +750,11 @@
750 ** );
751 **
752 ** This table contains all check-ins of the repository in topological
753 ** order. "Topological order" means that every parent check-in comes
754 ** before all of its children. Topological order is *almost* the same
755 ** thing as "ORDER BY event.mtime". Differences only arise when there
756 ** are timewarps. In as much as Git hates timewarps, we have to compute
757 ** a correct topological order when doing an export.
758 **
759 ** Since mtime is a usually already nearly in topological order, the
760 ** algorithm is to start with mtime, then make adjustments as necessary
@@ -835,5 +831,413 @@
831 int n;
832 db_find_and_open_repository(0, 0);
833 n = topological_sort_checkins(1);
834 fossil_print("%d reorderings required\n", n);
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 <%[email protected]> %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 }
1244

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button