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