Fossil SCM

Add [/help?cmd=import|fossil import --svn], for importing a subversion repository into fossil which was exported using "svnadmin dump".

jan.nijtmans 2015-03-17 10:45 trunk merge
Commit 6a2016098a43280ffca06fa1e7767965348683cd
+844 -14
--- src/import.c
+++ src/import.c
@@ -12,12 +12,12 @@
1212
** Author contact information:
1313
** [email protected]
1414
**
1515
*******************************************************************************
1616
**
17
-** This file contains code used to import the content of a Git
18
-** repository in the git-fast-import format as a new Fossil
17
+** This file contains code used to import the content of a Git/SVN
18
+** repository in the git-fast-import/svn-dump formats as a new Fossil
1919
** repository.
2020
*/
2121
#include "config.h"
2222
#include "import.h"
2323
#include <assert.h>
@@ -716,46 +716,822 @@
716716
malformed_line:
717717
trim_newline(zLine);
718718
fossil_fatal("bad fast-import line: [%s]", zLine);
719719
return;
720720
}
721
+
722
+static struct{
723
+ int rev; /* SVN revision number */
724
+ char *zDate; /* Date/time stamp */
725
+ char *zUser; /* User name */
726
+ char *zComment; /* Comment of a commit */
727
+ const char *zTrunk; /* Name of trunk folder in repo root */
728
+ int lenTrunk; /* String length of zTrunk */
729
+ const char *zBranches; /* Name of branches folder in repo root */
730
+ int lenBranches; /* String length of zBranches */
731
+ const char *zTags; /* Name of tags folder in repo root */
732
+ int lenTags; /* String length of zTags */
733
+ Bag newBranches; /* Branches that were created in this revision */
734
+ int incrFlag; /* Add svn-rev-nn tags on every checkin */
735
+} gsvn;
736
+typedef struct {
737
+ char *zKey;
738
+ char *zVal;
739
+} KeyVal;
740
+typedef struct {
741
+ KeyVal *aHeaders;
742
+ int nHeaders;
743
+ char *pRawProps;
744
+ KeyVal *aProps;
745
+ int nProps;
746
+ Blob content;
747
+ int contentFlag;
748
+} SvnRecord;
749
+
750
+#define svn_find_header(rec, zHeader) \
751
+ svn_find_keyval((rec).aHeaders, (rec).nHeaders, (zHeader))
752
+#define svn_find_prop(rec, zProp) \
753
+ svn_find_keyval((rec).aProps, (rec).nProps, (zProp))
754
+static char *svn_find_keyval(
755
+ KeyVal *aKeyVal,
756
+ int nKeyVal,
757
+ const char *zKey
758
+){
759
+ int i;
760
+ for(i=0; i<nKeyVal; i++){
761
+ if( fossil_strcmp(aKeyVal[i].zKey, zKey)==0 ){
762
+ return aKeyVal[i].zVal;
763
+ }
764
+ }
765
+ return 0;
766
+}
767
+
768
+static void svn_free_rec(SvnRecord *rec){
769
+ int i;
770
+ for(i=0; i<rec->nHeaders; i++){
771
+ fossil_free(rec->aHeaders[i].zKey);
772
+ }
773
+ fossil_free(rec->aHeaders);
774
+ fossil_free(rec->aProps);
775
+ fossil_free(rec->pRawProps);
776
+ blob_reset(&rec->content);
777
+}
778
+
779
+static int svn_read_headers(FILE *pIn, SvnRecord *rec){
780
+ char zLine[1000];
781
+
782
+ rec->aHeaders = 0;
783
+ rec->nHeaders = 0;
784
+ while( fgets(zLine, sizeof(zLine), pIn) ){
785
+ if( zLine[0]!='\n' ) break;
786
+ }
787
+ if( feof(pIn) ) return 0;
788
+ do{
789
+ char *sep;
790
+ if( zLine[0]=='\n' ) break;
791
+ rec->nHeaders += 1;
792
+ rec->aHeaders = fossil_realloc(rec->aHeaders,
793
+ sizeof(rec->aHeaders[0])*rec->nHeaders);
794
+ rec->aHeaders[rec->nHeaders-1].zKey = mprintf("%s", zLine);
795
+ sep = strchr(rec->aHeaders[rec->nHeaders-1].zKey, ':');
796
+ if( !sep ){
797
+ trim_newline(zLine);
798
+ fossil_fatal("bad header line: [%s]", zLine);
799
+ }
800
+ *sep = 0;
801
+ rec->aHeaders[rec->nHeaders-1].zVal = sep+1;
802
+ sep = strchr(rec->aHeaders[rec->nHeaders-1].zVal, '\n');
803
+ *sep = 0;
804
+ while(rec->aHeaders[rec->nHeaders-1].zVal
805
+ && fossil_isspace(*(rec->aHeaders[rec->nHeaders-1].zVal)) )
806
+ {
807
+ rec->aHeaders[rec->nHeaders-1].zVal++;
808
+ }
809
+ }while( fgets(zLine, sizeof(zLine), pIn) );
810
+ if( zLine[0]!='\n' ){
811
+ trim_newline(zLine);
812
+ fossil_fatal("svn-dump data ended unexpectedly");
813
+ }
814
+ return 1;
815
+}
816
+
817
+static void svn_read_props(FILE *pIn, SvnRecord *rec){
818
+ int nRawProps = 0;
819
+ char *pRawProps;
820
+ const char *zLen;
821
+
822
+ rec->pRawProps = 0;
823
+ rec->aProps = 0;
824
+ rec->nProps = 0;
825
+ zLen = svn_find_header(*rec, "Prop-content-length");
826
+ if( zLen ){
827
+ nRawProps = atoi(zLen);
828
+ }
829
+ if( nRawProps ){
830
+ int got;
831
+ char *zLine;
832
+ rec->pRawProps = pRawProps = fossil_malloc( nRawProps );
833
+ got = fread(rec->pRawProps, 1, nRawProps, pIn);
834
+ if( got!=nRawProps ){
835
+ fossil_fatal("short read: got %d of %d bytes", got, nRawProps);
836
+ }
837
+ if( memcmp(&pRawProps[got-10], "PROPS-END\n", 10)!=0 ){
838
+ fossil_fatal("svn-dump data ended unexpectedly");
839
+ }
840
+ zLine = pRawProps;
841
+ while( zLine<(pRawProps+nRawProps-10) ){
842
+ char *eol;
843
+ int propLen;
844
+ if( zLine[0]=='D' ){
845
+ propLen = atoi(&zLine[2]);
846
+ eol = strchr(zLine, '\n');
847
+ zLine = eol+1+propLen+1;
848
+ }else{
849
+ if( zLine[0]!='K' ){
850
+ fossil_fatal("svn-dump data format broken");
851
+ }
852
+ propLen = atoi(&zLine[2]);
853
+ eol = strchr(zLine, '\n');
854
+ zLine = eol+1;
855
+ eol = zLine+propLen;
856
+ if( *eol!='\n' ){
857
+ fossil_fatal("svn-dump data format broken");
858
+ }
859
+ *eol = 0;
860
+ rec->nProps += 1;
861
+ rec->aProps = fossil_realloc(rec->aProps,
862
+ sizeof(rec->aProps[0])*rec->nProps);
863
+ rec->aProps[rec->nProps-1].zKey = zLine;
864
+ zLine = eol+1;
865
+ if( zLine[0]!='V' ){
866
+ fossil_fatal("svn-dump data format broken");
867
+ }
868
+ propLen = atoi(&zLine[2]);
869
+ eol = strchr(zLine, '\n');
870
+ zLine = eol+1;
871
+ eol = zLine+propLen;
872
+ if( *eol!='\n' ){
873
+ fossil_fatal("svn-dump data format broken");
874
+ }
875
+ *eol = 0;
876
+ rec->aProps[rec->nProps-1].zVal = zLine;
877
+ zLine = eol+1;
878
+ }
879
+ }
880
+ }
881
+}
882
+
883
+static int svn_read_rec(FILE *pIn, SvnRecord *rec){
884
+ const char *zLen;
885
+ int nLen = 0;
886
+ if( svn_read_headers(pIn, rec)==0 ) return 0;
887
+ svn_read_props(pIn, rec);
888
+ blob_zero(&rec->content);
889
+ zLen = svn_find_header(*rec, "Text-content-length");
890
+ if( zLen ){
891
+ rec->contentFlag = 1;
892
+ nLen = atoi(zLen);
893
+ blob_read_from_channel(&rec->content, pIn, nLen);
894
+ if( blob_size(&rec->content)!=nLen ){
895
+ fossil_fatal("short read: got %d of %d bytes",
896
+ blob_size(&rec->content), nLen
897
+ );
898
+ }
899
+ }else{
900
+ rec->contentFlag = 0;
901
+ }
902
+ return 1;
903
+}
904
+
905
+/*
906
+** Returns the UUID for the RID, or NULL if not found.
907
+** The returned string is allocated via db_text() and must be
908
+** free()d by the caller.
909
+*/
910
+char * rid_to_uuid(int rid)
911
+{
912
+ return db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
913
+}
914
+
915
+#define SVN_UNKNOWN 0
916
+#define SVN_TRUNK 1
917
+#define SVN_BRANCH 2
918
+#define SVN_TAG 3
919
+
920
+#define MAX_INT_32 (0x7FFFFFFFL)
921
+
922
+static void svn_finish_revision(){
923
+ Blob manifest;
924
+ static Stmt getChanges;
925
+ static Stmt getFiles;
926
+ static Stmt setRid;
927
+ Blob mcksum;
928
+
929
+ blob_zero(&manifest);
930
+ db_static_prepare(&getChanges, "SELECT tid, tname, ttype, tparent"
931
+ " FROM xrevisions, xbranches ON (tbranch=tid)"
932
+ " WHERE trid ISNULL");
933
+ db_static_prepare(&getFiles, "SELECT tpath, tuuid, tperm FROM xfiles"
934
+ " WHERE tbranch=:branch ORDER BY tpath");
935
+ db_prepare(&setRid, "UPDATE xrevisions SET trid=:rid"
936
+ " WHERE trev=%d AND tbranch=:branch", gsvn.rev);
937
+ while( db_step(&getChanges)==SQLITE_ROW ){
938
+ int branchId = db_column_int(&getChanges, 0);
939
+ const char *zBranch = db_column_text(&getChanges, 1);
940
+ int branchType = db_column_int(&getChanges, 2);
941
+ int parentRid = db_column_int(&getChanges, 3);
942
+ int mergeRid = parentRid;
943
+ Manifest *pParentManifest = 0;
944
+ ManifestFile *pParentFile = 0;
945
+ int sameAsParent = 1;
946
+ int parentBranch = 0;
947
+ if( !bag_find(&gsvn.newBranches, branchId) ){
948
+ parentRid = db_int(0, "SELECT trid, max(trev) FROM xrevisions"
949
+ " WHERE trev<%d AND tbranch=%d",
950
+ gsvn.rev, branchId);
951
+ }
952
+ if( parentRid>0 ){
953
+ pParentManifest = manifest_get(parentRid, CFTYPE_MANIFEST, 0);
954
+ pParentFile = manifest_file_next(pParentManifest, 0);
955
+ parentBranch = db_int(0, "SELECT tbranch FROM xrevisions WHERE trid=%d",
956
+ parentRid);
957
+ if( parentBranch!=branchId && branchType!=SVN_TAG ){
958
+ sameAsParent = 0;
959
+ }
960
+ }
961
+ if( mergeRid<MAX_INT_32 ){
962
+ if( gsvn.zComment ){
963
+ blob_appendf(&manifest, "C %F\n", gsvn.zComment);
964
+ }else{
965
+ blob_append(&manifest, "C (no\\scomment)\n", 16);
966
+ }
967
+ blob_appendf(&manifest, "D %s\n", gsvn.zDate);
968
+ db_bind_int(&getFiles, ":branch", branchId);
969
+ while( db_step(&getFiles)==SQLITE_ROW ){
970
+ const char *zFile = db_column_text(&getFiles, 0);
971
+ const char *zUuid = db_column_text(&getFiles, 1);
972
+ const char *zPerm = db_column_text(&getFiles, 2);
973
+ if( zPerm ){
974
+ blob_appendf(&manifest, "F %F %s %s\n", zFile, zUuid, zPerm);
975
+ }else{
976
+ blob_appendf(&manifest, "F %F %s\n", zFile, zUuid);
977
+ }
978
+ if( sameAsParent ){
979
+ if( !pParentFile
980
+ || fossil_strcmp(pParentFile->zName,zFile)!=0
981
+ || fossil_strcmp(pParentFile->zUuid,zUuid)!=0
982
+ || fossil_strcmp(pParentFile->zPerm,zPerm)!=0
983
+ ){
984
+ sameAsParent = 0;
985
+ }else{
986
+ pParentFile = manifest_file_next(pParentManifest, 0);
987
+ }
988
+ }
989
+ }
990
+ if( pParentFile ){
991
+ sameAsParent = 0;
992
+ }
993
+ db_reset(&getFiles);
994
+ if( !sameAsParent ){
995
+ if( parentRid>0 ){
996
+ char *zParentUuid = rid_to_uuid(parentRid);
997
+ if( parentRid==mergeRid || mergeRid==0){
998
+ char *zParentBranch =
999
+ db_text(0, "SELECT tname FROM xbranches WHERE tid=%d",
1000
+ parentBranch
1001
+ );
1002
+ blob_appendf(&manifest, "P %s\n", zParentUuid);
1003
+ blob_appendf(&manifest, "T *branch * %F\n", zBranch);
1004
+ blob_appendf(&manifest, "T *sym-%F *\n", zBranch);
1005
+ if( gsvn.incrFlag ){
1006
+ blob_appendf(&manifest, "T +sym-svn-rev-%d *\n", gsvn.rev);
1007
+ }
1008
+ blob_appendf(&manifest, "T -sym-%F *\n", zParentBranch);
1009
+ fossil_free(zParentBranch);
1010
+ }else{
1011
+ char *zMergeUuid = rid_to_uuid(mergeRid);
1012
+ blob_appendf(&manifest, "P %s %s\n", zParentUuid, zMergeUuid);
1013
+ if( gsvn.incrFlag ){
1014
+ blob_appendf(&manifest, "T +sym-svn-rev-%d *\n", gsvn.rev);
1015
+ }
1016
+ fossil_free(zMergeUuid);
1017
+ }
1018
+ fossil_free(zParentUuid);
1019
+ }else{
1020
+ blob_appendf(&manifest, "T *branch * %F\n", zBranch);
1021
+ blob_appendf(&manifest, "T *sym-%F *\n", zBranch);
1022
+ if( gsvn.incrFlag ){
1023
+ blob_appendf(&manifest, "T +sym-svn-rev-%d *\n", gsvn.rev);
1024
+ }
1025
+ }
1026
+ }else if( branchType==SVN_TAG ){
1027
+ char *zParentUuid = rid_to_uuid(parentRid);
1028
+ blob_reset(&manifest);
1029
+ blob_appendf(&manifest, "D %s\n", gsvn.zDate);
1030
+ blob_appendf(&manifest, "T +sym-%F %s\n", zBranch, zParentUuid);
1031
+ fossil_free(zParentUuid);
1032
+ }
1033
+ }else{
1034
+ char *zParentUuid = rid_to_uuid(parentRid);
1035
+ blob_appendf(&manifest, "D %s\n", gsvn.zDate);
1036
+ if( branchType!=SVN_TAG ){
1037
+ blob_appendf(&manifest, "T +closed %s\n", zParentUuid);
1038
+ }else{
1039
+ blob_appendf(&manifest, "T -sym-%F %s\n", zBranch, zParentUuid);
1040
+ }
1041
+ fossil_free(zParentUuid);
1042
+ }
1043
+ if( gsvn.zUser ){
1044
+ blob_appendf(&manifest, "U %F\n", gsvn.zUser);
1045
+ }else{
1046
+ const char *zUserOvrd = find_option("user-override",0,1);
1047
+ blob_appendf(&manifest, "U %F\n", zUserOvrd ? zUserOvrd : login_name());
1048
+ }
1049
+ md5sum_blob(&manifest, &mcksum);
1050
+ blob_appendf(&manifest, "Z %b\n", &mcksum);
1051
+ blob_reset(&mcksum);
1052
+ if( !sameAsParent ){
1053
+ int rid = content_put(&manifest);
1054
+ db_bind_int(&setRid, ":branch", branchId);
1055
+ db_bind_int(&setRid, ":rid", rid);
1056
+ db_step(&setRid);
1057
+ db_reset(&setRid);
1058
+ }else if( branchType==SVN_TAG ){
1059
+ content_put(&manifest);
1060
+ db_bind_int(&setRid, ":branch", branchId);
1061
+ db_bind_int(&setRid, ":rid", parentRid);
1062
+ db_step(&setRid);
1063
+ db_reset(&setRid);
1064
+ }else if( mergeRid==MAX_INT_32 ){
1065
+ content_put(&manifest);
1066
+ db_multi_exec("DELETE FROM xrevisions WHERE tbranch=%d AND trev=%d",
1067
+ branchId, gsvn.rev);
1068
+ }else{
1069
+ db_multi_exec("DELETE FROM xrevisions WHERE tbranch=%d AND trev=%d",
1070
+ branchId, gsvn.rev);
1071
+ }
1072
+ blob_reset(&manifest);
1073
+ manifest_destroy(pParentManifest);
1074
+ }
1075
+ db_reset(&getChanges);
1076
+ db_finalize(&setRid);
1077
+}
1078
+
1079
+static u64 svn_get_varint(const char **pz){
1080
+ unsigned int v = 0;
1081
+ do{
1082
+ v = (v<<7) | ((*pz)[0]&0x7f);
1083
+ }while( (*pz)++[0]&0x80 );
1084
+ return v;
1085
+}
1086
+
1087
+static void svn_apply_svndiff(Blob *pDiff, Blob *pSrc, Blob *pOut){
1088
+ const char *zDiff = blob_buffer(pDiff);
1089
+ char *zOut;
1090
+ if( blob_size(pDiff)<4 || memcmp(zDiff, "SVN", 4)!=0 ){
1091
+ fossil_fatal("Invalid svndiff0 format");
1092
+ }
1093
+ zDiff += 4;
1094
+ blob_zero(pOut);
1095
+ while( zDiff<(blob_buffer(pDiff)+blob_size(pDiff)) ){
1096
+ u64 lenOut, lenInst, lenData, lenOld;
1097
+ const char *zInst;
1098
+ const char *zData;
1099
+
1100
+ u64 offSrc = svn_get_varint(&zDiff);
1101
+ /*lenSrc =*/ svn_get_varint(&zDiff);
1102
+ lenOut = svn_get_varint(&zDiff);
1103
+ lenInst = svn_get_varint(&zDiff);
1104
+ lenData = svn_get_varint(&zDiff);
1105
+ zInst = zDiff;
1106
+ zData = zInst+lenInst;
1107
+ lenOld = blob_size(pOut);
1108
+ blob_resize(pOut, lenOut+lenOld);
1109
+ zOut = blob_buffer(pOut)+lenOld;
1110
+ while( zDiff<zInst+lenInst ){
1111
+ u64 lenCpy = (*zDiff)&0x3f;
1112
+ const char *zCpy;
1113
+ switch( (*zDiff)&0xC0 ){
1114
+ case 0x00: zCpy = blob_buffer(pSrc)+offSrc; break;
1115
+ case 0x40: zCpy = blob_buffer(pOut); break;
1116
+ case 0x80: zCpy = zData; break;
1117
+ default: fossil_fatal("Invalid svndiff0 instruction");
1118
+ }
1119
+ zDiff++;
1120
+ if( lenCpy==0 ){
1121
+ lenCpy = svn_get_varint(&zDiff);
1122
+ }
1123
+ if( zCpy!=zData ){
1124
+ zCpy += svn_get_varint(&zDiff);
1125
+ }else{
1126
+ zData += lenCpy;
1127
+ }
1128
+ while( lenCpy-- > 0 ){
1129
+ *zOut++ = *zCpy++;
1130
+ }
1131
+ }
1132
+ zDiff += lenData;
1133
+ }
1134
+}
1135
+
1136
+/*
1137
+** Extract the branch or tag that the given path is on. Return the branch ID.
1138
+ */
1139
+static int svn_parse_path(char *zPath, char **zFile, int *type){
1140
+ char *zBranch = 0;
1141
+ int branchId = 0;
1142
+ *type = SVN_UNKNOWN;
1143
+ *zFile = 0;
1144
+ if( gsvn.lenTrunk==0 ){
1145
+ zBranch = "trunk";
1146
+ *zFile = zPath;
1147
+ *type = SVN_TRUNK;
1148
+ }else
1149
+ if( strncmp(zPath, gsvn.zTrunk, gsvn.lenTrunk-1)==0 ){
1150
+ if( zPath[gsvn.lenTrunk-1]=='/' || zPath[gsvn.lenTrunk-1]==0 ){
1151
+ zBranch = "trunk";
1152
+ *zFile = zPath+gsvn.lenTrunk;
1153
+ *type = SVN_TRUNK;
1154
+ }else{
1155
+ zBranch = 0;
1156
+ *type = SVN_UNKNOWN;
1157
+ }
1158
+ }else{
1159
+ if( strncmp(zPath, gsvn.zBranches, gsvn.lenBranches)==0 ){
1160
+ *zFile = zBranch = zPath+gsvn.lenBranches;
1161
+ *type = SVN_BRANCH;
1162
+ }else
1163
+ if( strncmp(zPath, gsvn.zTags, gsvn.lenTags)==0 ){
1164
+ *zFile = zBranch = zPath+gsvn.lenTags;
1165
+ *type = SVN_TAG;
1166
+ }else{ /* Not a branch, tag or trunk */
1167
+ return 0;
1168
+ }
1169
+ while( **zFile && **zFile!='/' ){ (*zFile)++; }
1170
+ if( **zFile ){
1171
+ **zFile = '\0';
1172
+ (*zFile)++;
1173
+ }
1174
+ }
1175
+ if( *type!=SVN_UNKNOWN ){
1176
+ branchId = db_int(0,
1177
+ "SELECT tid FROM xbranches WHERE tname=%Q AND ttype=%d",
1178
+ zBranch, *type);
1179
+ if( branchId==0 ){
1180
+ db_multi_exec("INSERT INTO xbranches (tname, ttype) VALUES(%Q, %d)",
1181
+ zBranch, *type);
1182
+ branchId = db_last_insert_rowid();
1183
+ }
1184
+ }
1185
+ return branchId;
1186
+}
1187
+
1188
+/*
1189
+** Read the svn-dump format from pIn and insert the corresponding
1190
+** content into the database.
1191
+*/
1192
+static void svn_dump_import(FILE *pIn){
1193
+ SvnRecord rec;
1194
+ int ver;
1195
+ char *zTemp;
1196
+ const char *zUuid;
1197
+ Stmt addFile;
1198
+ Stmt delPath;
1199
+ Stmt addRev;
1200
+ Stmt cpyPath;
1201
+ Stmt cpyRoot;
1202
+ Stmt revSrc;
1203
+
1204
+ /* version */
1205
+ if( svn_read_rec(pIn, &rec)
1206
+ && (zTemp = svn_find_header(rec, "SVN-fs-dump-format-version")) ){
1207
+ ver = atoi(zTemp);
1208
+ if( ver!=2 && ver!=3 ){
1209
+ fossil_fatal("Unknown svn-dump format version: %d", ver);
1210
+ }
1211
+ }else{
1212
+ fossil_fatal("Input is not an svn-dump!");
1213
+ }
1214
+ svn_free_rec(&rec);
1215
+ /* UUID */
1216
+ if( !svn_read_rec(pIn, &rec) || !(zUuid = svn_find_header(rec, "UUID")) ){
1217
+ /* Removed the following line since UUID is not actually used
1218
+ fossil_fatal("Missing UUID!"); */
1219
+ }
1220
+ svn_free_rec(&rec);
1221
+
1222
+ /* content */
1223
+ db_prepare(&addFile,
1224
+ "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)"
1225
+ " VALUES(:path, :branch, (SELECT uuid FROM blob WHERE rid=:rid), :perm)"
1226
+ );
1227
+ db_prepare(&delPath,
1228
+ "DELETE FROM xfiles"
1229
+ " WHERE (tpath=:path OR (tpath>:path||'/' AND tpath<:path||'0'))"
1230
+ " AND tbranch=:branch"
1231
+ );
1232
+ db_prepare(&addRev,
1233
+ "INSERT OR IGNORE INTO xrevisions (trev, tbranch) VALUES(:rev, :branch)"
1234
+ );
1235
+ db_prepare(&cpyPath,
1236
+ "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)"
1237
+ " SELECT :path||:sep||substr(filename, length(:srcpath)+2), :branch, uuid, perm"
1238
+ " FROM xfoci"
1239
+ " WHERE checkinID=:rid"
1240
+ " AND filename>:srcpath||'/'"
1241
+ " AND filename<:srcpath||'0'"
1242
+ );
1243
+ db_prepare(&cpyRoot,
1244
+ "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)"
1245
+ " SELECT :path||:sep||filename, :branch, uuid, perm"
1246
+ " FROM xfoci"
1247
+ " WHERE checkinID=:rid"
1248
+ );
1249
+ db_prepare(&revSrc,
1250
+ "UPDATE xrevisions SET tparent=:parent"
1251
+ " WHERE trev=:rev AND tbranch=:branch AND tparent<:parent"
1252
+ );
1253
+ gsvn.rev = -1;
1254
+ bag_init(&gsvn.newBranches);
1255
+ while( svn_read_rec(pIn, &rec) ){
1256
+ if( (zTemp = svn_find_header(rec, "Revision-number")) ){ /* revision node */
1257
+ /* finish previous revision */
1258
+ char *zDate = NULL;
1259
+ if( gsvn.rev>=0 ){
1260
+ svn_finish_revision();
1261
+ fossil_free(gsvn.zUser);
1262
+ fossil_free(gsvn.zComment);
1263
+ fossil_free(gsvn.zDate);
1264
+ bag_clear(&gsvn.newBranches);
1265
+ }
1266
+ /* start new revision */
1267
+ gsvn.rev = atoi(zTemp);
1268
+ gsvn.zUser = mprintf("%s", svn_find_prop(rec, "svn:author"));
1269
+ gsvn.zComment = mprintf("%s", svn_find_prop(rec, "svn:log"));
1270
+ zDate = svn_find_prop(rec, "svn:date");
1271
+ if( zDate ){
1272
+ gsvn.zDate = date_in_standard_format(zDate);
1273
+ }else{
1274
+ gsvn.zDate = date_in_standard_format("now");
1275
+ }
1276
+ db_bind_int(&addRev, ":rev", gsvn.rev);
1277
+ fossil_print("\rImporting SVN revision: %d", gsvn.rev);
1278
+ }else
1279
+ if( (zTemp = svn_find_header(rec, "Node-path")) ){ /* file/dir node */
1280
+ char *zFile;
1281
+ int branchType;
1282
+ int branchId = svn_parse_path(zTemp, &zFile, &branchType);
1283
+ char *zAction = svn_find_header(rec, "Node-action");
1284
+ char *zKind = svn_find_header(rec, "Node-kind");
1285
+ char *zPerm = svn_find_prop(rec, "svn:executable") ? "x" : 0;
1286
+ int deltaFlag = 0;
1287
+ int srcRev = 0;
1288
+ if( branchId==0 ){
1289
+ svn_free_rec(&rec);
1290
+ continue;
1291
+ }
1292
+ if( (zTemp = svn_find_header(rec, "Text-delta")) ){
1293
+ deltaFlag = strncmp(zTemp, "true", 4)==0;
1294
+ }
1295
+ if( strncmp(zAction, "delete", 6)==0
1296
+ || strncmp(zAction, "replace", 7)==0 )
1297
+ {
1298
+ db_bind_int(&addRev, ":branch", branchId);
1299
+ db_step(&addRev);
1300
+ db_reset(&addRev);
1301
+ if( zFile[0]!=0 ){
1302
+ db_bind_text(&delPath, ":path", zFile);
1303
+ db_bind_int(&delPath, ":branch", branchId);
1304
+ db_step(&delPath);
1305
+ db_reset(&delPath);
1306
+ }else{
1307
+ db_multi_exec("DELETE FROM xfiles WHERE tbranch=%d", branchId);
1308
+ db_bind_int(&revSrc, ":parent", MAX_INT_32);
1309
+ db_bind_int(&revSrc, ":rev", gsvn.rev);
1310
+ db_bind_int(&revSrc, ":branch", branchId);
1311
+ db_step(&revSrc);
1312
+ db_reset(&revSrc);
1313
+ }
1314
+ } /* no 'else' here since 'replace' does both a 'delete' and an 'add' */
1315
+ if( strncmp(zAction, "add", 3)==0
1316
+ || strncmp(zAction, "replace", 7)==0 )
1317
+ {
1318
+ char *zSrcPath = svn_find_header(rec, "Node-copyfrom-path");
1319
+ char *zSrcFile;
1320
+ int srcRid = 0;
1321
+ if( zSrcPath ){
1322
+ int srcBranch;
1323
+ zTemp = svn_find_header(rec, "Node-copyfrom-rev");
1324
+ if( zTemp ){
1325
+ srcRev = atoi(zTemp);
1326
+ }else{
1327
+ fossil_fatal("Missing copyfrom-rev");
1328
+ }
1329
+ srcBranch = svn_parse_path(zSrcPath, &zSrcFile, &branchType);
1330
+ if( srcBranch==0 ){
1331
+ fossil_fatal("Copy from path outside the import paths");
1332
+ }
1333
+ srcRid = db_int(0, "SELECT trid, max(trev) FROM xrevisions"
1334
+ " WHERE trev<=%d AND tbranch=%d",
1335
+ srcRev, srcBranch);
1336
+ if( srcRid>0 && srcBranch!=branchId ){
1337
+ db_bind_int(&addRev, ":branch", branchId);
1338
+ db_step(&addRev);
1339
+ db_reset(&addRev);
1340
+ db_bind_int(&revSrc, ":parent", srcRid);
1341
+ db_bind_int(&revSrc, ":rev", gsvn.rev);
1342
+ db_bind_int(&revSrc, ":branch", branchId);
1343
+ db_step(&revSrc);
1344
+ db_reset(&revSrc);
1345
+ }
1346
+ }
1347
+ if( zKind==0 ){
1348
+ fossil_fatal("Missing Node-kind");
1349
+ }else if( strncmp(zKind, "dir", 3)==0 ){
1350
+ if( zSrcPath ){
1351
+ if( srcRid>0 ){
1352
+ if( zSrcFile[0]==0 ){
1353
+ db_bind_text(&cpyRoot, ":path", zFile);
1354
+ if( zFile[0]!=0 ){
1355
+ db_bind_text(&cpyRoot, ":sep", "/");
1356
+ }else{
1357
+ db_bind_text(&cpyRoot, ":sep", "");
1358
+ }
1359
+ db_bind_int(&cpyRoot, ":branch", branchId);
1360
+ db_bind_int(&cpyRoot, ":rid", srcRid);
1361
+ db_step(&cpyRoot);
1362
+ db_reset(&cpyRoot);
1363
+ }else{
1364
+ db_bind_text(&cpyPath, ":path", zFile);
1365
+ if( zFile[0]!=0 ){
1366
+ db_bind_text(&cpyPath, ":sep", "/");
1367
+ }else{
1368
+ db_bind_text(&cpyPath, ":sep", "");
1369
+ }
1370
+ db_bind_int(&cpyPath, ":branch", branchId);
1371
+ db_bind_text(&cpyPath, ":srcpath", zSrcFile);
1372
+ db_bind_int(&cpyPath, ":rid", srcRid);
1373
+ db_step(&cpyPath);
1374
+ db_reset(&cpyPath);
1375
+ }
1376
+ }
1377
+ }
1378
+ if( zFile[0]==0 ){
1379
+ bag_insert(&gsvn.newBranches, branchId);
1380
+ }
1381
+ }else{
1382
+ int rid = 0;
1383
+ if( zSrcPath ){
1384
+ rid = db_int(0, "SELECT rid FROM blob WHERE uuid=("
1385
+ " SELECT uuid FROM xfoci"
1386
+ " WHERE checkinID=%d AND filename=%Q"
1387
+ ")",
1388
+ srcRid, zSrcFile);
1389
+ }
1390
+ if( deltaFlag ){
1391
+ Blob deltaSrc;
1392
+ Blob target;
1393
+ if( rid!=0 ){
1394
+ content_get(rid, &deltaSrc);
1395
+ }else{
1396
+ blob_zero(&deltaSrc);
1397
+ }
1398
+ svn_apply_svndiff(&rec.content, &deltaSrc, &target);
1399
+ rid = content_put(&target);
1400
+ }else if( rec.contentFlag ){
1401
+ rid = content_put(&rec.content);
1402
+ }
1403
+ db_bind_text(&addFile, ":path", zFile);
1404
+ db_bind_int(&addFile, ":branch", branchId);
1405
+ db_bind_int(&addFile, ":rid", rid);
1406
+ db_bind_text(&addFile, ":perm", zPerm);
1407
+ db_step(&addFile);
1408
+ db_reset(&addFile);
1409
+ db_bind_int(&addRev, ":branch", branchId);
1410
+ db_step(&addRev);
1411
+ db_reset(&addRev);
1412
+ }
1413
+ }else
1414
+ if( strncmp(zAction, "change", 6)==0 ){
1415
+ int rid = 0;
1416
+ if( zKind==0 ){
1417
+ fossil_fatal("Missing Node-kind");
1418
+ }
1419
+ if( strncmp(zKind, "dir", 3)!=0 ){
1420
+ if( deltaFlag ){
1421
+ Blob deltaSrc;
1422
+ Blob target;
1423
+ rid = db_int(0, "SELECT rid FROM blob WHERE uuid=("
1424
+ " SELECT uuid FROM xfiles"
1425
+ " WHERE tpath=%Q AND tbranch=%d"
1426
+ ")", zFile, branchId);
1427
+ content_get(rid, &deltaSrc);
1428
+ svn_apply_svndiff(&rec.content, &deltaSrc, &target);
1429
+ rid = content_put(&target);
1430
+ }else{
1431
+ rid = content_put(&rec.content);
1432
+ }
1433
+ db_bind_text(&addFile, ":path", zFile);
1434
+ db_bind_int(&addFile, ":branch", branchId);
1435
+ db_bind_int(&addFile, ":rid", rid);
1436
+ db_bind_text(&addFile, ":perm", zPerm);
1437
+ db_step(&addFile);
1438
+ db_reset(&addFile);
1439
+ db_bind_int(&addRev, ":branch", branchId);
1440
+ db_step(&addRev);
1441
+ db_reset(&addRev);
1442
+ }
1443
+ }else
1444
+ if( strncmp(zAction, "delete", 6)!=0 ){ /* already did this one above */
1445
+ fossil_fatal("Unknown Node-action");
1446
+ }
1447
+ }else{
1448
+ fossil_fatal("Unknown record type");
1449
+ }
1450
+ svn_free_rec(&rec);
1451
+ }
1452
+ svn_finish_revision();
1453
+ fossil_free(gsvn.zUser);
1454
+ fossil_free(gsvn.zComment);
1455
+ fossil_free(gsvn.zDate);
1456
+ db_finalize(&addFile);
1457
+ db_finalize(&delPath);
1458
+ db_finalize(&addRev);
1459
+ db_finalize(&cpyPath);
1460
+ db_finalize(&cpyRoot);
1461
+ db_finalize(&revSrc);
1462
+ fossil_print(" Done!\n");
1463
+}
7211464
7221465
/*
7231466
** COMMAND: import
7241467
**
725
-** Usage: %fossil import ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?
1468
+** Usage: %fossil import ?--git? ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?
1469
+** or: %fossil import --svn ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?
7261470
**
7271471
** Read interchange format generated by another VCS and use it to
7281472
** construct a new Fossil repository named by the NEW-REPOSITORY
7291473
** argument. If no input file is supplied the interchange format
7301474
** data is read from standard input.
7311475
**
732
-** The git-fast-export file format is currently the only VCS interchange
733
-** format that is understood, though other interchange formats may be added
734
-** in the future.
1476
+** The following formats are currently understood by this command
1477
+**
1478
+** --git Import from the git-fast-export file format (default)
1479
+**
1480
+** --svn Import from the svnadmin-dump file format. The default
1481
+** behaviour (unless overridden by --flat) is to treat 3
1482
+** folders in the SVN root as special, following the
1483
+** common layout of SVN repositories. These are (by
1484
+** default) trunk/, branches/ and tags/
1485
+** Options:
1486
+** --trunk FOLDER Name of trunk folder
1487
+** --branches FOLDER Name of branches folder
1488
+** --tags FOLDER Name of tags folder
1489
+** --base PATH Path to project root in repository
1490
+** --flat The whole dump is a single branch
1491
+**
1492
+** Common Options:
1493
+** -i|--incremental allow importing into an existing repository
1494
+** -f|--force overwrite repository if already exist
7351495
**
7361496
** The --incremental option allows an existing repository to be extended
7371497
** with new content.
7381498
**
739
-** Options:
740
-** --git import from the git-fast-export file format (default)
741
-** --incremental allow importing into an existing repository
7421499
**
7431500
** See also: export
7441501
*/
7451502
void import_cmd(void){
7461503
char *zPassword;
7471504
FILE *pIn;
7481505
Stmt q;
7491506
int forceFlag = find_option("force", "f", 0)!=0;
750
- int incrFlag = find_option("incremental", "i", 0)!=0;
7511507
int svnFlag = find_option("svn", 0, 0)!=0;
7521508
753
- find_option("git",0,0); /* Skip the --git option for now */
1509
+ /* Options common to all input formats */
1510
+ int incrFlag = find_option("incremental", "i", 0)!=0;
1511
+
1512
+ /* Options for --svn only */
1513
+ const char *zBase="";
1514
+ int flatFlag=0;
1515
+
1516
+ if( svnFlag ){
1517
+ /* Get --svn related options here, so verify_all_options() fail when svn
1518
+ * only option are specify with --git
1519
+ */
1520
+ zBase = find_option("base", 0, 1);
1521
+ flatFlag = find_option("flat", 0, 0)!=0;
1522
+ gsvn.zTrunk = find_option("trunk", 0, 1);
1523
+ gsvn.zBranches = find_option("branches", 0, 1);
1524
+ gsvn.zTags = find_option("tags", 0, 1);
1525
+ gsvn.incrFlag = incrFlag;
1526
+ }else{
1527
+ find_option("git",0,0); /* Skip the --git option for now */
1528
+ }
7541529
verify_all_options();
755
- if( g.argc!=3 && g.argc!=4 ){
756
- usage("NEW-REPOSITORY ?INPUT-FILE?");
1530
+
1531
+ if( g.argc!=3 && g.argc!=4 ){
1532
+ usage("--git|--svn ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?");
7571533
}
7581534
if( g.argc==4 ){
7591535
pIn = fossil_fopen(g.argv[3], "rb");
7601536
}else{
7611537
pIn = stdin;
@@ -770,11 +1546,63 @@
7701546
7711547
db_begin_transaction();
7721548
if( !incrFlag ) db_initial_setup(0, 0, 0, 1);
7731549
7741550
if( svnFlag ){
775
- fossil_fatal("--svn format not implemented yet");
1551
+ db_multi_exec(
1552
+ "CREATE TEMP TABLE xrevisions("
1553
+ " trev INTEGER, tbranch INT, trid INT, tparent INT DEFAULT 0,"
1554
+ " UNIQUE(tbranch, trev)"
1555
+ ");"
1556
+ "CREATE INDEX temp.i_xrevisions ON xrevisions(trid);"
1557
+ "CREATE TEMP TABLE xfiles("
1558
+ " tpath TEXT, tbranch INT, tuuid TEXT, tperm TEXT,"
1559
+ " UNIQUE (tbranch, tpath) ON CONFLICT REPLACE"
1560
+ ");"
1561
+ "CREATE TEMP TABLE xbranches("
1562
+ " tid INTEGER PRIMARY KEY, tname TEXT, ttype INT,"
1563
+ " UNIQUE(tname, ttype)"
1564
+ ");"
1565
+ "CREATE VIRTUAL TABLE temp.xfoci USING files_of_checkin;"
1566
+ );
1567
+ if( zBase==0 ){ zBase = ""; }
1568
+ if( strlen(zBase)>0 ){
1569
+ if( zBase[strlen(zBase)-1]!='/' ){
1570
+ zBase = mprintf("%s/", zBase);
1571
+ }
1572
+ }
1573
+ if( flatFlag ){
1574
+ gsvn.zTrunk = zBase;
1575
+ gsvn.zBranches = 0;
1576
+ gsvn.zTags = 0;
1577
+ gsvn.lenTrunk = strlen(zBase);
1578
+ gsvn.lenBranches = 0;
1579
+ gsvn.lenTags = 0;
1580
+ }else{
1581
+ if( gsvn.zTrunk==0 ){ gsvn.zTrunk = "trunk/"; }
1582
+ if( gsvn.zBranches==0 ){ gsvn.zBranches = "branches/"; }
1583
+ if( gsvn.zTags==0 ){ gsvn.zTags = "tags/"; }
1584
+ gsvn.zTrunk = mprintf("%s%s", zBase, gsvn.zTrunk);
1585
+ gsvn.zBranches = mprintf("%s%s", zBase, gsvn.zBranches);
1586
+ gsvn.zTags = mprintf("%s%s", zBase, gsvn.zTags);
1587
+ gsvn.lenTrunk = strlen(gsvn.zTrunk);
1588
+ gsvn.lenBranches = strlen(gsvn.zBranches);
1589
+ gsvn.lenTags = strlen(gsvn.zTags);
1590
+ if( gsvn.zTrunk[gsvn.lenTrunk-1]!='/' ){
1591
+ gsvn.zTrunk = mprintf("%s/", gsvn.zTrunk);
1592
+ gsvn.lenTrunk++;
1593
+ }
1594
+ if( gsvn.zBranches[gsvn.lenBranches-1]!='/' ){
1595
+ gsvn.zBranches = mprintf("%s/", gsvn.zBranches);
1596
+ gsvn.lenBranches++;
1597
+ }
1598
+ if( gsvn.zTags[gsvn.lenTags-1]!='/' ){
1599
+ gsvn.zTags = mprintf("%s/", gsvn.zTags);
1600
+ gsvn.lenTags++;
1601
+ }
1602
+ }
1603
+ svn_dump_import(pIn);
7761604
}else{
7771605
/* The following temp-tables are used to hold information needed for
7781606
** the import.
7791607
**
7801608
** The XMARK table provides a mapping from fast-import "marks" and symbols
@@ -807,10 +1635,12 @@
8071635
fast_insert_content(&record, 0, 0);
8081636
import_reset(0);
8091637
}
8101638
db_finalize(&q);
8111639
}
1640
+
1641
+ verify_cancel();
8121642
db_end_transaction(0);
8131643
db_begin_transaction();
8141644
fossil_print("Rebuilding repository meta-data...\n");
8151645
rebuild_db(0, 1, !incrFlag);
8161646
verify_cancel();
8171647
--- src/import.c
+++ src/import.c
@@ -12,12 +12,12 @@
12 ** Author contact information:
13 ** [email protected]
14 **
15 *******************************************************************************
16 **
17 ** This file contains code used to import the content of a Git
18 ** repository in the git-fast-import format as a new Fossil
19 ** repository.
20 */
21 #include "config.h"
22 #include "import.h"
23 #include <assert.h>
@@ -716,46 +716,822 @@
716 malformed_line:
717 trim_newline(zLine);
718 fossil_fatal("bad fast-import line: [%s]", zLine);
719 return;
720 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
721
722 /*
723 ** COMMAND: import
724 **
725 ** Usage: %fossil import ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?
 
726 **
727 ** Read interchange format generated by another VCS and use it to
728 ** construct a new Fossil repository named by the NEW-REPOSITORY
729 ** argument. If no input file is supplied the interchange format
730 ** data is read from standard input.
731 **
732 ** The git-fast-export file format is currently the only VCS interchange
733 ** format that is understood, though other interchange formats may be added
734 ** in the future.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
735 **
736 ** The --incremental option allows an existing repository to be extended
737 ** with new content.
738 **
739 ** Options:
740 ** --git import from the git-fast-export file format (default)
741 ** --incremental allow importing into an existing repository
742 **
743 ** See also: export
744 */
745 void import_cmd(void){
746 char *zPassword;
747 FILE *pIn;
748 Stmt q;
749 int forceFlag = find_option("force", "f", 0)!=0;
750 int incrFlag = find_option("incremental", "i", 0)!=0;
751 int svnFlag = find_option("svn", 0, 0)!=0;
752
753 find_option("git",0,0); /* Skip the --git option for now */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
754 verify_all_options();
755 if( g.argc!=3 && g.argc!=4 ){
756 usage("NEW-REPOSITORY ?INPUT-FILE?");
 
757 }
758 if( g.argc==4 ){
759 pIn = fossil_fopen(g.argv[3], "rb");
760 }else{
761 pIn = stdin;
@@ -770,11 +1546,63 @@
770
771 db_begin_transaction();
772 if( !incrFlag ) db_initial_setup(0, 0, 0, 1);
773
774 if( svnFlag ){
775 fossil_fatal("--svn format not implemented yet");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
776 }else{
777 /* The following temp-tables are used to hold information needed for
778 ** the import.
779 **
780 ** The XMARK table provides a mapping from fast-import "marks" and symbols
@@ -807,10 +1635,12 @@
807 fast_insert_content(&record, 0, 0);
808 import_reset(0);
809 }
810 db_finalize(&q);
811 }
 
 
812 db_end_transaction(0);
813 db_begin_transaction();
814 fossil_print("Rebuilding repository meta-data...\n");
815 rebuild_db(0, 1, !incrFlag);
816 verify_cancel();
817
--- src/import.c
+++ src/import.c
@@ -12,12 +12,12 @@
12 ** Author contact information:
13 ** [email protected]
14 **
15 *******************************************************************************
16 **
17 ** This file contains code used to import the content of a Git/SVN
18 ** repository in the git-fast-import/svn-dump formats as a new Fossil
19 ** repository.
20 */
21 #include "config.h"
22 #include "import.h"
23 #include <assert.h>
@@ -716,46 +716,822 @@
716 malformed_line:
717 trim_newline(zLine);
718 fossil_fatal("bad fast-import line: [%s]", zLine);
719 return;
720 }
721
722 static struct{
723 int rev; /* SVN revision number */
724 char *zDate; /* Date/time stamp */
725 char *zUser; /* User name */
726 char *zComment; /* Comment of a commit */
727 const char *zTrunk; /* Name of trunk folder in repo root */
728 int lenTrunk; /* String length of zTrunk */
729 const char *zBranches; /* Name of branches folder in repo root */
730 int lenBranches; /* String length of zBranches */
731 const char *zTags; /* Name of tags folder in repo root */
732 int lenTags; /* String length of zTags */
733 Bag newBranches; /* Branches that were created in this revision */
734 int incrFlag; /* Add svn-rev-nn tags on every checkin */
735 } gsvn;
736 typedef struct {
737 char *zKey;
738 char *zVal;
739 } KeyVal;
740 typedef struct {
741 KeyVal *aHeaders;
742 int nHeaders;
743 char *pRawProps;
744 KeyVal *aProps;
745 int nProps;
746 Blob content;
747 int contentFlag;
748 } SvnRecord;
749
750 #define svn_find_header(rec, zHeader) \
751 svn_find_keyval((rec).aHeaders, (rec).nHeaders, (zHeader))
752 #define svn_find_prop(rec, zProp) \
753 svn_find_keyval((rec).aProps, (rec).nProps, (zProp))
754 static char *svn_find_keyval(
755 KeyVal *aKeyVal,
756 int nKeyVal,
757 const char *zKey
758 ){
759 int i;
760 for(i=0; i<nKeyVal; i++){
761 if( fossil_strcmp(aKeyVal[i].zKey, zKey)==0 ){
762 return aKeyVal[i].zVal;
763 }
764 }
765 return 0;
766 }
767
768 static void svn_free_rec(SvnRecord *rec){
769 int i;
770 for(i=0; i<rec->nHeaders; i++){
771 fossil_free(rec->aHeaders[i].zKey);
772 }
773 fossil_free(rec->aHeaders);
774 fossil_free(rec->aProps);
775 fossil_free(rec->pRawProps);
776 blob_reset(&rec->content);
777 }
778
779 static int svn_read_headers(FILE *pIn, SvnRecord *rec){
780 char zLine[1000];
781
782 rec->aHeaders = 0;
783 rec->nHeaders = 0;
784 while( fgets(zLine, sizeof(zLine), pIn) ){
785 if( zLine[0]!='\n' ) break;
786 }
787 if( feof(pIn) ) return 0;
788 do{
789 char *sep;
790 if( zLine[0]=='\n' ) break;
791 rec->nHeaders += 1;
792 rec->aHeaders = fossil_realloc(rec->aHeaders,
793 sizeof(rec->aHeaders[0])*rec->nHeaders);
794 rec->aHeaders[rec->nHeaders-1].zKey = mprintf("%s", zLine);
795 sep = strchr(rec->aHeaders[rec->nHeaders-1].zKey, ':');
796 if( !sep ){
797 trim_newline(zLine);
798 fossil_fatal("bad header line: [%s]", zLine);
799 }
800 *sep = 0;
801 rec->aHeaders[rec->nHeaders-1].zVal = sep+1;
802 sep = strchr(rec->aHeaders[rec->nHeaders-1].zVal, '\n');
803 *sep = 0;
804 while(rec->aHeaders[rec->nHeaders-1].zVal
805 && fossil_isspace(*(rec->aHeaders[rec->nHeaders-1].zVal)) )
806 {
807 rec->aHeaders[rec->nHeaders-1].zVal++;
808 }
809 }while( fgets(zLine, sizeof(zLine), pIn) );
810 if( zLine[0]!='\n' ){
811 trim_newline(zLine);
812 fossil_fatal("svn-dump data ended unexpectedly");
813 }
814 return 1;
815 }
816
817 static void svn_read_props(FILE *pIn, SvnRecord *rec){
818 int nRawProps = 0;
819 char *pRawProps;
820 const char *zLen;
821
822 rec->pRawProps = 0;
823 rec->aProps = 0;
824 rec->nProps = 0;
825 zLen = svn_find_header(*rec, "Prop-content-length");
826 if( zLen ){
827 nRawProps = atoi(zLen);
828 }
829 if( nRawProps ){
830 int got;
831 char *zLine;
832 rec->pRawProps = pRawProps = fossil_malloc( nRawProps );
833 got = fread(rec->pRawProps, 1, nRawProps, pIn);
834 if( got!=nRawProps ){
835 fossil_fatal("short read: got %d of %d bytes", got, nRawProps);
836 }
837 if( memcmp(&pRawProps[got-10], "PROPS-END\n", 10)!=0 ){
838 fossil_fatal("svn-dump data ended unexpectedly");
839 }
840 zLine = pRawProps;
841 while( zLine<(pRawProps+nRawProps-10) ){
842 char *eol;
843 int propLen;
844 if( zLine[0]=='D' ){
845 propLen = atoi(&zLine[2]);
846 eol = strchr(zLine, '\n');
847 zLine = eol+1+propLen+1;
848 }else{
849 if( zLine[0]!='K' ){
850 fossil_fatal("svn-dump data format broken");
851 }
852 propLen = atoi(&zLine[2]);
853 eol = strchr(zLine, '\n');
854 zLine = eol+1;
855 eol = zLine+propLen;
856 if( *eol!='\n' ){
857 fossil_fatal("svn-dump data format broken");
858 }
859 *eol = 0;
860 rec->nProps += 1;
861 rec->aProps = fossil_realloc(rec->aProps,
862 sizeof(rec->aProps[0])*rec->nProps);
863 rec->aProps[rec->nProps-1].zKey = zLine;
864 zLine = eol+1;
865 if( zLine[0]!='V' ){
866 fossil_fatal("svn-dump data format broken");
867 }
868 propLen = atoi(&zLine[2]);
869 eol = strchr(zLine, '\n');
870 zLine = eol+1;
871 eol = zLine+propLen;
872 if( *eol!='\n' ){
873 fossil_fatal("svn-dump data format broken");
874 }
875 *eol = 0;
876 rec->aProps[rec->nProps-1].zVal = zLine;
877 zLine = eol+1;
878 }
879 }
880 }
881 }
882
883 static int svn_read_rec(FILE *pIn, SvnRecord *rec){
884 const char *zLen;
885 int nLen = 0;
886 if( svn_read_headers(pIn, rec)==0 ) return 0;
887 svn_read_props(pIn, rec);
888 blob_zero(&rec->content);
889 zLen = svn_find_header(*rec, "Text-content-length");
890 if( zLen ){
891 rec->contentFlag = 1;
892 nLen = atoi(zLen);
893 blob_read_from_channel(&rec->content, pIn, nLen);
894 if( blob_size(&rec->content)!=nLen ){
895 fossil_fatal("short read: got %d of %d bytes",
896 blob_size(&rec->content), nLen
897 );
898 }
899 }else{
900 rec->contentFlag = 0;
901 }
902 return 1;
903 }
904
905 /*
906 ** Returns the UUID for the RID, or NULL if not found.
907 ** The returned string is allocated via db_text() and must be
908 ** free()d by the caller.
909 */
910 char * rid_to_uuid(int rid)
911 {
912 return db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
913 }
914
915 #define SVN_UNKNOWN 0
916 #define SVN_TRUNK 1
917 #define SVN_BRANCH 2
918 #define SVN_TAG 3
919
920 #define MAX_INT_32 (0x7FFFFFFFL)
921
922 static void svn_finish_revision(){
923 Blob manifest;
924 static Stmt getChanges;
925 static Stmt getFiles;
926 static Stmt setRid;
927 Blob mcksum;
928
929 blob_zero(&manifest);
930 db_static_prepare(&getChanges, "SELECT tid, tname, ttype, tparent"
931 " FROM xrevisions, xbranches ON (tbranch=tid)"
932 " WHERE trid ISNULL");
933 db_static_prepare(&getFiles, "SELECT tpath, tuuid, tperm FROM xfiles"
934 " WHERE tbranch=:branch ORDER BY tpath");
935 db_prepare(&setRid, "UPDATE xrevisions SET trid=:rid"
936 " WHERE trev=%d AND tbranch=:branch", gsvn.rev);
937 while( db_step(&getChanges)==SQLITE_ROW ){
938 int branchId = db_column_int(&getChanges, 0);
939 const char *zBranch = db_column_text(&getChanges, 1);
940 int branchType = db_column_int(&getChanges, 2);
941 int parentRid = db_column_int(&getChanges, 3);
942 int mergeRid = parentRid;
943 Manifest *pParentManifest = 0;
944 ManifestFile *pParentFile = 0;
945 int sameAsParent = 1;
946 int parentBranch = 0;
947 if( !bag_find(&gsvn.newBranches, branchId) ){
948 parentRid = db_int(0, "SELECT trid, max(trev) FROM xrevisions"
949 " WHERE trev<%d AND tbranch=%d",
950 gsvn.rev, branchId);
951 }
952 if( parentRid>0 ){
953 pParentManifest = manifest_get(parentRid, CFTYPE_MANIFEST, 0);
954 pParentFile = manifest_file_next(pParentManifest, 0);
955 parentBranch = db_int(0, "SELECT tbranch FROM xrevisions WHERE trid=%d",
956 parentRid);
957 if( parentBranch!=branchId && branchType!=SVN_TAG ){
958 sameAsParent = 0;
959 }
960 }
961 if( mergeRid<MAX_INT_32 ){
962 if( gsvn.zComment ){
963 blob_appendf(&manifest, "C %F\n", gsvn.zComment);
964 }else{
965 blob_append(&manifest, "C (no\\scomment)\n", 16);
966 }
967 blob_appendf(&manifest, "D %s\n", gsvn.zDate);
968 db_bind_int(&getFiles, ":branch", branchId);
969 while( db_step(&getFiles)==SQLITE_ROW ){
970 const char *zFile = db_column_text(&getFiles, 0);
971 const char *zUuid = db_column_text(&getFiles, 1);
972 const char *zPerm = db_column_text(&getFiles, 2);
973 if( zPerm ){
974 blob_appendf(&manifest, "F %F %s %s\n", zFile, zUuid, zPerm);
975 }else{
976 blob_appendf(&manifest, "F %F %s\n", zFile, zUuid);
977 }
978 if( sameAsParent ){
979 if( !pParentFile
980 || fossil_strcmp(pParentFile->zName,zFile)!=0
981 || fossil_strcmp(pParentFile->zUuid,zUuid)!=0
982 || fossil_strcmp(pParentFile->zPerm,zPerm)!=0
983 ){
984 sameAsParent = 0;
985 }else{
986 pParentFile = manifest_file_next(pParentManifest, 0);
987 }
988 }
989 }
990 if( pParentFile ){
991 sameAsParent = 0;
992 }
993 db_reset(&getFiles);
994 if( !sameAsParent ){
995 if( parentRid>0 ){
996 char *zParentUuid = rid_to_uuid(parentRid);
997 if( parentRid==mergeRid || mergeRid==0){
998 char *zParentBranch =
999 db_text(0, "SELECT tname FROM xbranches WHERE tid=%d",
1000 parentBranch
1001 );
1002 blob_appendf(&manifest, "P %s\n", zParentUuid);
1003 blob_appendf(&manifest, "T *branch * %F\n", zBranch);
1004 blob_appendf(&manifest, "T *sym-%F *\n", zBranch);
1005 if( gsvn.incrFlag ){
1006 blob_appendf(&manifest, "T +sym-svn-rev-%d *\n", gsvn.rev);
1007 }
1008 blob_appendf(&manifest, "T -sym-%F *\n", zParentBranch);
1009 fossil_free(zParentBranch);
1010 }else{
1011 char *zMergeUuid = rid_to_uuid(mergeRid);
1012 blob_appendf(&manifest, "P %s %s\n", zParentUuid, zMergeUuid);
1013 if( gsvn.incrFlag ){
1014 blob_appendf(&manifest, "T +sym-svn-rev-%d *\n", gsvn.rev);
1015 }
1016 fossil_free(zMergeUuid);
1017 }
1018 fossil_free(zParentUuid);
1019 }else{
1020 blob_appendf(&manifest, "T *branch * %F\n", zBranch);
1021 blob_appendf(&manifest, "T *sym-%F *\n", zBranch);
1022 if( gsvn.incrFlag ){
1023 blob_appendf(&manifest, "T +sym-svn-rev-%d *\n", gsvn.rev);
1024 }
1025 }
1026 }else if( branchType==SVN_TAG ){
1027 char *zParentUuid = rid_to_uuid(parentRid);
1028 blob_reset(&manifest);
1029 blob_appendf(&manifest, "D %s\n", gsvn.zDate);
1030 blob_appendf(&manifest, "T +sym-%F %s\n", zBranch, zParentUuid);
1031 fossil_free(zParentUuid);
1032 }
1033 }else{
1034 char *zParentUuid = rid_to_uuid(parentRid);
1035 blob_appendf(&manifest, "D %s\n", gsvn.zDate);
1036 if( branchType!=SVN_TAG ){
1037 blob_appendf(&manifest, "T +closed %s\n", zParentUuid);
1038 }else{
1039 blob_appendf(&manifest, "T -sym-%F %s\n", zBranch, zParentUuid);
1040 }
1041 fossil_free(zParentUuid);
1042 }
1043 if( gsvn.zUser ){
1044 blob_appendf(&manifest, "U %F\n", gsvn.zUser);
1045 }else{
1046 const char *zUserOvrd = find_option("user-override",0,1);
1047 blob_appendf(&manifest, "U %F\n", zUserOvrd ? zUserOvrd : login_name());
1048 }
1049 md5sum_blob(&manifest, &mcksum);
1050 blob_appendf(&manifest, "Z %b\n", &mcksum);
1051 blob_reset(&mcksum);
1052 if( !sameAsParent ){
1053 int rid = content_put(&manifest);
1054 db_bind_int(&setRid, ":branch", branchId);
1055 db_bind_int(&setRid, ":rid", rid);
1056 db_step(&setRid);
1057 db_reset(&setRid);
1058 }else if( branchType==SVN_TAG ){
1059 content_put(&manifest);
1060 db_bind_int(&setRid, ":branch", branchId);
1061 db_bind_int(&setRid, ":rid", parentRid);
1062 db_step(&setRid);
1063 db_reset(&setRid);
1064 }else if( mergeRid==MAX_INT_32 ){
1065 content_put(&manifest);
1066 db_multi_exec("DELETE FROM xrevisions WHERE tbranch=%d AND trev=%d",
1067 branchId, gsvn.rev);
1068 }else{
1069 db_multi_exec("DELETE FROM xrevisions WHERE tbranch=%d AND trev=%d",
1070 branchId, gsvn.rev);
1071 }
1072 blob_reset(&manifest);
1073 manifest_destroy(pParentManifest);
1074 }
1075 db_reset(&getChanges);
1076 db_finalize(&setRid);
1077 }
1078
1079 static u64 svn_get_varint(const char **pz){
1080 unsigned int v = 0;
1081 do{
1082 v = (v<<7) | ((*pz)[0]&0x7f);
1083 }while( (*pz)++[0]&0x80 );
1084 return v;
1085 }
1086
1087 static void svn_apply_svndiff(Blob *pDiff, Blob *pSrc, Blob *pOut){
1088 const char *zDiff = blob_buffer(pDiff);
1089 char *zOut;
1090 if( blob_size(pDiff)<4 || memcmp(zDiff, "SVN", 4)!=0 ){
1091 fossil_fatal("Invalid svndiff0 format");
1092 }
1093 zDiff += 4;
1094 blob_zero(pOut);
1095 while( zDiff<(blob_buffer(pDiff)+blob_size(pDiff)) ){
1096 u64 lenOut, lenInst, lenData, lenOld;
1097 const char *zInst;
1098 const char *zData;
1099
1100 u64 offSrc = svn_get_varint(&zDiff);
1101 /*lenSrc =*/ svn_get_varint(&zDiff);
1102 lenOut = svn_get_varint(&zDiff);
1103 lenInst = svn_get_varint(&zDiff);
1104 lenData = svn_get_varint(&zDiff);
1105 zInst = zDiff;
1106 zData = zInst+lenInst;
1107 lenOld = blob_size(pOut);
1108 blob_resize(pOut, lenOut+lenOld);
1109 zOut = blob_buffer(pOut)+lenOld;
1110 while( zDiff<zInst+lenInst ){
1111 u64 lenCpy = (*zDiff)&0x3f;
1112 const char *zCpy;
1113 switch( (*zDiff)&0xC0 ){
1114 case 0x00: zCpy = blob_buffer(pSrc)+offSrc; break;
1115 case 0x40: zCpy = blob_buffer(pOut); break;
1116 case 0x80: zCpy = zData; break;
1117 default: fossil_fatal("Invalid svndiff0 instruction");
1118 }
1119 zDiff++;
1120 if( lenCpy==0 ){
1121 lenCpy = svn_get_varint(&zDiff);
1122 }
1123 if( zCpy!=zData ){
1124 zCpy += svn_get_varint(&zDiff);
1125 }else{
1126 zData += lenCpy;
1127 }
1128 while( lenCpy-- > 0 ){
1129 *zOut++ = *zCpy++;
1130 }
1131 }
1132 zDiff += lenData;
1133 }
1134 }
1135
1136 /*
1137 ** Extract the branch or tag that the given path is on. Return the branch ID.
1138 */
1139 static int svn_parse_path(char *zPath, char **zFile, int *type){
1140 char *zBranch = 0;
1141 int branchId = 0;
1142 *type = SVN_UNKNOWN;
1143 *zFile = 0;
1144 if( gsvn.lenTrunk==0 ){
1145 zBranch = "trunk";
1146 *zFile = zPath;
1147 *type = SVN_TRUNK;
1148 }else
1149 if( strncmp(zPath, gsvn.zTrunk, gsvn.lenTrunk-1)==0 ){
1150 if( zPath[gsvn.lenTrunk-1]=='/' || zPath[gsvn.lenTrunk-1]==0 ){
1151 zBranch = "trunk";
1152 *zFile = zPath+gsvn.lenTrunk;
1153 *type = SVN_TRUNK;
1154 }else{
1155 zBranch = 0;
1156 *type = SVN_UNKNOWN;
1157 }
1158 }else{
1159 if( strncmp(zPath, gsvn.zBranches, gsvn.lenBranches)==0 ){
1160 *zFile = zBranch = zPath+gsvn.lenBranches;
1161 *type = SVN_BRANCH;
1162 }else
1163 if( strncmp(zPath, gsvn.zTags, gsvn.lenTags)==0 ){
1164 *zFile = zBranch = zPath+gsvn.lenTags;
1165 *type = SVN_TAG;
1166 }else{ /* Not a branch, tag or trunk */
1167 return 0;
1168 }
1169 while( **zFile && **zFile!='/' ){ (*zFile)++; }
1170 if( **zFile ){
1171 **zFile = '\0';
1172 (*zFile)++;
1173 }
1174 }
1175 if( *type!=SVN_UNKNOWN ){
1176 branchId = db_int(0,
1177 "SELECT tid FROM xbranches WHERE tname=%Q AND ttype=%d",
1178 zBranch, *type);
1179 if( branchId==0 ){
1180 db_multi_exec("INSERT INTO xbranches (tname, ttype) VALUES(%Q, %d)",
1181 zBranch, *type);
1182 branchId = db_last_insert_rowid();
1183 }
1184 }
1185 return branchId;
1186 }
1187
1188 /*
1189 ** Read the svn-dump format from pIn and insert the corresponding
1190 ** content into the database.
1191 */
1192 static void svn_dump_import(FILE *pIn){
1193 SvnRecord rec;
1194 int ver;
1195 char *zTemp;
1196 const char *zUuid;
1197 Stmt addFile;
1198 Stmt delPath;
1199 Stmt addRev;
1200 Stmt cpyPath;
1201 Stmt cpyRoot;
1202 Stmt revSrc;
1203
1204 /* version */
1205 if( svn_read_rec(pIn, &rec)
1206 && (zTemp = svn_find_header(rec, "SVN-fs-dump-format-version")) ){
1207 ver = atoi(zTemp);
1208 if( ver!=2 && ver!=3 ){
1209 fossil_fatal("Unknown svn-dump format version: %d", ver);
1210 }
1211 }else{
1212 fossil_fatal("Input is not an svn-dump!");
1213 }
1214 svn_free_rec(&rec);
1215 /* UUID */
1216 if( !svn_read_rec(pIn, &rec) || !(zUuid = svn_find_header(rec, "UUID")) ){
1217 /* Removed the following line since UUID is not actually used
1218 fossil_fatal("Missing UUID!"); */
1219 }
1220 svn_free_rec(&rec);
1221
1222 /* content */
1223 db_prepare(&addFile,
1224 "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)"
1225 " VALUES(:path, :branch, (SELECT uuid FROM blob WHERE rid=:rid), :perm)"
1226 );
1227 db_prepare(&delPath,
1228 "DELETE FROM xfiles"
1229 " WHERE (tpath=:path OR (tpath>:path||'/' AND tpath<:path||'0'))"
1230 " AND tbranch=:branch"
1231 );
1232 db_prepare(&addRev,
1233 "INSERT OR IGNORE INTO xrevisions (trev, tbranch) VALUES(:rev, :branch)"
1234 );
1235 db_prepare(&cpyPath,
1236 "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)"
1237 " SELECT :path||:sep||substr(filename, length(:srcpath)+2), :branch, uuid, perm"
1238 " FROM xfoci"
1239 " WHERE checkinID=:rid"
1240 " AND filename>:srcpath||'/'"
1241 " AND filename<:srcpath||'0'"
1242 );
1243 db_prepare(&cpyRoot,
1244 "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)"
1245 " SELECT :path||:sep||filename, :branch, uuid, perm"
1246 " FROM xfoci"
1247 " WHERE checkinID=:rid"
1248 );
1249 db_prepare(&revSrc,
1250 "UPDATE xrevisions SET tparent=:parent"
1251 " WHERE trev=:rev AND tbranch=:branch AND tparent<:parent"
1252 );
1253 gsvn.rev = -1;
1254 bag_init(&gsvn.newBranches);
1255 while( svn_read_rec(pIn, &rec) ){
1256 if( (zTemp = svn_find_header(rec, "Revision-number")) ){ /* revision node */
1257 /* finish previous revision */
1258 char *zDate = NULL;
1259 if( gsvn.rev>=0 ){
1260 svn_finish_revision();
1261 fossil_free(gsvn.zUser);
1262 fossil_free(gsvn.zComment);
1263 fossil_free(gsvn.zDate);
1264 bag_clear(&gsvn.newBranches);
1265 }
1266 /* start new revision */
1267 gsvn.rev = atoi(zTemp);
1268 gsvn.zUser = mprintf("%s", svn_find_prop(rec, "svn:author"));
1269 gsvn.zComment = mprintf("%s", svn_find_prop(rec, "svn:log"));
1270 zDate = svn_find_prop(rec, "svn:date");
1271 if( zDate ){
1272 gsvn.zDate = date_in_standard_format(zDate);
1273 }else{
1274 gsvn.zDate = date_in_standard_format("now");
1275 }
1276 db_bind_int(&addRev, ":rev", gsvn.rev);
1277 fossil_print("\rImporting SVN revision: %d", gsvn.rev);
1278 }else
1279 if( (zTemp = svn_find_header(rec, "Node-path")) ){ /* file/dir node */
1280 char *zFile;
1281 int branchType;
1282 int branchId = svn_parse_path(zTemp, &zFile, &branchType);
1283 char *zAction = svn_find_header(rec, "Node-action");
1284 char *zKind = svn_find_header(rec, "Node-kind");
1285 char *zPerm = svn_find_prop(rec, "svn:executable") ? "x" : 0;
1286 int deltaFlag = 0;
1287 int srcRev = 0;
1288 if( branchId==0 ){
1289 svn_free_rec(&rec);
1290 continue;
1291 }
1292 if( (zTemp = svn_find_header(rec, "Text-delta")) ){
1293 deltaFlag = strncmp(zTemp, "true", 4)==0;
1294 }
1295 if( strncmp(zAction, "delete", 6)==0
1296 || strncmp(zAction, "replace", 7)==0 )
1297 {
1298 db_bind_int(&addRev, ":branch", branchId);
1299 db_step(&addRev);
1300 db_reset(&addRev);
1301 if( zFile[0]!=0 ){
1302 db_bind_text(&delPath, ":path", zFile);
1303 db_bind_int(&delPath, ":branch", branchId);
1304 db_step(&delPath);
1305 db_reset(&delPath);
1306 }else{
1307 db_multi_exec("DELETE FROM xfiles WHERE tbranch=%d", branchId);
1308 db_bind_int(&revSrc, ":parent", MAX_INT_32);
1309 db_bind_int(&revSrc, ":rev", gsvn.rev);
1310 db_bind_int(&revSrc, ":branch", branchId);
1311 db_step(&revSrc);
1312 db_reset(&revSrc);
1313 }
1314 } /* no 'else' here since 'replace' does both a 'delete' and an 'add' */
1315 if( strncmp(zAction, "add", 3)==0
1316 || strncmp(zAction, "replace", 7)==0 )
1317 {
1318 char *zSrcPath = svn_find_header(rec, "Node-copyfrom-path");
1319 char *zSrcFile;
1320 int srcRid = 0;
1321 if( zSrcPath ){
1322 int srcBranch;
1323 zTemp = svn_find_header(rec, "Node-copyfrom-rev");
1324 if( zTemp ){
1325 srcRev = atoi(zTemp);
1326 }else{
1327 fossil_fatal("Missing copyfrom-rev");
1328 }
1329 srcBranch = svn_parse_path(zSrcPath, &zSrcFile, &branchType);
1330 if( srcBranch==0 ){
1331 fossil_fatal("Copy from path outside the import paths");
1332 }
1333 srcRid = db_int(0, "SELECT trid, max(trev) FROM xrevisions"
1334 " WHERE trev<=%d AND tbranch=%d",
1335 srcRev, srcBranch);
1336 if( srcRid>0 && srcBranch!=branchId ){
1337 db_bind_int(&addRev, ":branch", branchId);
1338 db_step(&addRev);
1339 db_reset(&addRev);
1340 db_bind_int(&revSrc, ":parent", srcRid);
1341 db_bind_int(&revSrc, ":rev", gsvn.rev);
1342 db_bind_int(&revSrc, ":branch", branchId);
1343 db_step(&revSrc);
1344 db_reset(&revSrc);
1345 }
1346 }
1347 if( zKind==0 ){
1348 fossil_fatal("Missing Node-kind");
1349 }else if( strncmp(zKind, "dir", 3)==0 ){
1350 if( zSrcPath ){
1351 if( srcRid>0 ){
1352 if( zSrcFile[0]==0 ){
1353 db_bind_text(&cpyRoot, ":path", zFile);
1354 if( zFile[0]!=0 ){
1355 db_bind_text(&cpyRoot, ":sep", "/");
1356 }else{
1357 db_bind_text(&cpyRoot, ":sep", "");
1358 }
1359 db_bind_int(&cpyRoot, ":branch", branchId);
1360 db_bind_int(&cpyRoot, ":rid", srcRid);
1361 db_step(&cpyRoot);
1362 db_reset(&cpyRoot);
1363 }else{
1364 db_bind_text(&cpyPath, ":path", zFile);
1365 if( zFile[0]!=0 ){
1366 db_bind_text(&cpyPath, ":sep", "/");
1367 }else{
1368 db_bind_text(&cpyPath, ":sep", "");
1369 }
1370 db_bind_int(&cpyPath, ":branch", branchId);
1371 db_bind_text(&cpyPath, ":srcpath", zSrcFile);
1372 db_bind_int(&cpyPath, ":rid", srcRid);
1373 db_step(&cpyPath);
1374 db_reset(&cpyPath);
1375 }
1376 }
1377 }
1378 if( zFile[0]==0 ){
1379 bag_insert(&gsvn.newBranches, branchId);
1380 }
1381 }else{
1382 int rid = 0;
1383 if( zSrcPath ){
1384 rid = db_int(0, "SELECT rid FROM blob WHERE uuid=("
1385 " SELECT uuid FROM xfoci"
1386 " WHERE checkinID=%d AND filename=%Q"
1387 ")",
1388 srcRid, zSrcFile);
1389 }
1390 if( deltaFlag ){
1391 Blob deltaSrc;
1392 Blob target;
1393 if( rid!=0 ){
1394 content_get(rid, &deltaSrc);
1395 }else{
1396 blob_zero(&deltaSrc);
1397 }
1398 svn_apply_svndiff(&rec.content, &deltaSrc, &target);
1399 rid = content_put(&target);
1400 }else if( rec.contentFlag ){
1401 rid = content_put(&rec.content);
1402 }
1403 db_bind_text(&addFile, ":path", zFile);
1404 db_bind_int(&addFile, ":branch", branchId);
1405 db_bind_int(&addFile, ":rid", rid);
1406 db_bind_text(&addFile, ":perm", zPerm);
1407 db_step(&addFile);
1408 db_reset(&addFile);
1409 db_bind_int(&addRev, ":branch", branchId);
1410 db_step(&addRev);
1411 db_reset(&addRev);
1412 }
1413 }else
1414 if( strncmp(zAction, "change", 6)==0 ){
1415 int rid = 0;
1416 if( zKind==0 ){
1417 fossil_fatal("Missing Node-kind");
1418 }
1419 if( strncmp(zKind, "dir", 3)!=0 ){
1420 if( deltaFlag ){
1421 Blob deltaSrc;
1422 Blob target;
1423 rid = db_int(0, "SELECT rid FROM blob WHERE uuid=("
1424 " SELECT uuid FROM xfiles"
1425 " WHERE tpath=%Q AND tbranch=%d"
1426 ")", zFile, branchId);
1427 content_get(rid, &deltaSrc);
1428 svn_apply_svndiff(&rec.content, &deltaSrc, &target);
1429 rid = content_put(&target);
1430 }else{
1431 rid = content_put(&rec.content);
1432 }
1433 db_bind_text(&addFile, ":path", zFile);
1434 db_bind_int(&addFile, ":branch", branchId);
1435 db_bind_int(&addFile, ":rid", rid);
1436 db_bind_text(&addFile, ":perm", zPerm);
1437 db_step(&addFile);
1438 db_reset(&addFile);
1439 db_bind_int(&addRev, ":branch", branchId);
1440 db_step(&addRev);
1441 db_reset(&addRev);
1442 }
1443 }else
1444 if( strncmp(zAction, "delete", 6)!=0 ){ /* already did this one above */
1445 fossil_fatal("Unknown Node-action");
1446 }
1447 }else{
1448 fossil_fatal("Unknown record type");
1449 }
1450 svn_free_rec(&rec);
1451 }
1452 svn_finish_revision();
1453 fossil_free(gsvn.zUser);
1454 fossil_free(gsvn.zComment);
1455 fossil_free(gsvn.zDate);
1456 db_finalize(&addFile);
1457 db_finalize(&delPath);
1458 db_finalize(&addRev);
1459 db_finalize(&cpyPath);
1460 db_finalize(&cpyRoot);
1461 db_finalize(&revSrc);
1462 fossil_print(" Done!\n");
1463 }
1464
1465 /*
1466 ** COMMAND: import
1467 **
1468 ** Usage: %fossil import ?--git? ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?
1469 ** or: %fossil import --svn ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?
1470 **
1471 ** Read interchange format generated by another VCS and use it to
1472 ** construct a new Fossil repository named by the NEW-REPOSITORY
1473 ** argument. If no input file is supplied the interchange format
1474 ** data is read from standard input.
1475 **
1476 ** The following formats are currently understood by this command
1477 **
1478 ** --git Import from the git-fast-export file format (default)
1479 **
1480 ** --svn Import from the svnadmin-dump file format. The default
1481 ** behaviour (unless overridden by --flat) is to treat 3
1482 ** folders in the SVN root as special, following the
1483 ** common layout of SVN repositories. These are (by
1484 ** default) trunk/, branches/ and tags/
1485 ** Options:
1486 ** --trunk FOLDER Name of trunk folder
1487 ** --branches FOLDER Name of branches folder
1488 ** --tags FOLDER Name of tags folder
1489 ** --base PATH Path to project root in repository
1490 ** --flat The whole dump is a single branch
1491 **
1492 ** Common Options:
1493 ** -i|--incremental allow importing into an existing repository
1494 ** -f|--force overwrite repository if already exist
1495 **
1496 ** The --incremental option allows an existing repository to be extended
1497 ** with new content.
1498 **
 
 
 
1499 **
1500 ** See also: export
1501 */
1502 void import_cmd(void){
1503 char *zPassword;
1504 FILE *pIn;
1505 Stmt q;
1506 int forceFlag = find_option("force", "f", 0)!=0;
 
1507 int svnFlag = find_option("svn", 0, 0)!=0;
1508
1509 /* Options common to all input formats */
1510 int incrFlag = find_option("incremental", "i", 0)!=0;
1511
1512 /* Options for --svn only */
1513 const char *zBase="";
1514 int flatFlag=0;
1515
1516 if( svnFlag ){
1517 /* Get --svn related options here, so verify_all_options() fail when svn
1518 * only option are specify with --git
1519 */
1520 zBase = find_option("base", 0, 1);
1521 flatFlag = find_option("flat", 0, 0)!=0;
1522 gsvn.zTrunk = find_option("trunk", 0, 1);
1523 gsvn.zBranches = find_option("branches", 0, 1);
1524 gsvn.zTags = find_option("tags", 0, 1);
1525 gsvn.incrFlag = incrFlag;
1526 }else{
1527 find_option("git",0,0); /* Skip the --git option for now */
1528 }
1529 verify_all_options();
1530
1531 if( g.argc!=3 && g.argc!=4 ){
1532 usage("--git|--svn ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?");
1533 }
1534 if( g.argc==4 ){
1535 pIn = fossil_fopen(g.argv[3], "rb");
1536 }else{
1537 pIn = stdin;
@@ -770,11 +1546,63 @@
1546
1547 db_begin_transaction();
1548 if( !incrFlag ) db_initial_setup(0, 0, 0, 1);
1549
1550 if( svnFlag ){
1551 db_multi_exec(
1552 "CREATE TEMP TABLE xrevisions("
1553 " trev INTEGER, tbranch INT, trid INT, tparent INT DEFAULT 0,"
1554 " UNIQUE(tbranch, trev)"
1555 ");"
1556 "CREATE INDEX temp.i_xrevisions ON xrevisions(trid);"
1557 "CREATE TEMP TABLE xfiles("
1558 " tpath TEXT, tbranch INT, tuuid TEXT, tperm TEXT,"
1559 " UNIQUE (tbranch, tpath) ON CONFLICT REPLACE"
1560 ");"
1561 "CREATE TEMP TABLE xbranches("
1562 " tid INTEGER PRIMARY KEY, tname TEXT, ttype INT,"
1563 " UNIQUE(tname, ttype)"
1564 ");"
1565 "CREATE VIRTUAL TABLE temp.xfoci USING files_of_checkin;"
1566 );
1567 if( zBase==0 ){ zBase = ""; }
1568 if( strlen(zBase)>0 ){
1569 if( zBase[strlen(zBase)-1]!='/' ){
1570 zBase = mprintf("%s/", zBase);
1571 }
1572 }
1573 if( flatFlag ){
1574 gsvn.zTrunk = zBase;
1575 gsvn.zBranches = 0;
1576 gsvn.zTags = 0;
1577 gsvn.lenTrunk = strlen(zBase);
1578 gsvn.lenBranches = 0;
1579 gsvn.lenTags = 0;
1580 }else{
1581 if( gsvn.zTrunk==0 ){ gsvn.zTrunk = "trunk/"; }
1582 if( gsvn.zBranches==0 ){ gsvn.zBranches = "branches/"; }
1583 if( gsvn.zTags==0 ){ gsvn.zTags = "tags/"; }
1584 gsvn.zTrunk = mprintf("%s%s", zBase, gsvn.zTrunk);
1585 gsvn.zBranches = mprintf("%s%s", zBase, gsvn.zBranches);
1586 gsvn.zTags = mprintf("%s%s", zBase, gsvn.zTags);
1587 gsvn.lenTrunk = strlen(gsvn.zTrunk);
1588 gsvn.lenBranches = strlen(gsvn.zBranches);
1589 gsvn.lenTags = strlen(gsvn.zTags);
1590 if( gsvn.zTrunk[gsvn.lenTrunk-1]!='/' ){
1591 gsvn.zTrunk = mprintf("%s/", gsvn.zTrunk);
1592 gsvn.lenTrunk++;
1593 }
1594 if( gsvn.zBranches[gsvn.lenBranches-1]!='/' ){
1595 gsvn.zBranches = mprintf("%s/", gsvn.zBranches);
1596 gsvn.lenBranches++;
1597 }
1598 if( gsvn.zTags[gsvn.lenTags-1]!='/' ){
1599 gsvn.zTags = mprintf("%s/", gsvn.zTags);
1600 gsvn.lenTags++;
1601 }
1602 }
1603 svn_dump_import(pIn);
1604 }else{
1605 /* The following temp-tables are used to hold information needed for
1606 ** the import.
1607 **
1608 ** The XMARK table provides a mapping from fast-import "marks" and symbols
@@ -807,10 +1635,12 @@
1635 fast_insert_content(&record, 0, 0);
1636 import_reset(0);
1637 }
1638 db_finalize(&q);
1639 }
1640
1641 verify_cancel();
1642 db_end_transaction(0);
1643 db_begin_transaction();
1644 fossil_print("Rebuilding repository meta-data...\n");
1645 rebuild_db(0, 1, !incrFlag);
1646 verify_cancel();
1647
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,11 @@
11
<title>Change Log</title>
22
3
+<h2>Changes for Version 1.33 (not released yet)</h2>
4
+ * Add [/help?cmd=import|fossil import --svn], for importing a subversion
5
+ repository into fossil which was exported using "svnadmin dump".
6
+
37
<h2>Changes for Version 1.32 (2015-03-14)</h2>
48
* When creating a new repository using [/help?cmd=init|fossil init], ensure
59
that the new repository is fully compatible with historical versions of
610
Fossil by having a valid manifest as RID 1.
711
* Anti-aliased rendering of arrowheads on timeline graphs.
812
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,11 @@
1 <title>Change Log</title>
2
 
 
 
 
3 <h2>Changes for Version 1.32 (2015-03-14)</h2>
4 * When creating a new repository using [/help?cmd=init|fossil init], ensure
5 that the new repository is fully compatible with historical versions of
6 Fossil by having a valid manifest as RID 1.
7 * Anti-aliased rendering of arrowheads on timeline graphs.
8
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,11 @@
1 <title>Change Log</title>
2
3 <h2>Changes for Version 1.33 (not released yet)</h2>
4 * Add [/help?cmd=import|fossil import --svn], for importing a subversion
5 repository into fossil which was exported using "svnadmin dump".
6
7 <h2>Changes for Version 1.32 (2015-03-14)</h2>
8 * When creating a new repository using [/help?cmd=init|fossil init], ensure
9 that the new repository is fully compatible with historical versions of
10 Fossil by having a valid manifest as RID 1.
11 * Anti-aliased rendering of arrowheads on timeline graphs.
12
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,11 @@
11
<title>Change Log</title>
22
3
+<h2>Changes for Version 1.33 (not released yet)</h2>
4
+ * Add [/help?cmd=import|fossil import --svn], for importing a subversion
5
+ repository into fossil which was exported using "svnadmin dump".
6
+
37
<h2>Changes for Version 1.32 (2015-03-14)</h2>
48
* When creating a new repository using [/help?cmd=init|fossil init], ensure
59
that the new repository is fully compatible with historical versions of
610
Fossil by having a valid manifest as RID 1.
711
* Anti-aliased rendering of arrowheads on timeline graphs.
812
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,11 @@
1 <title>Change Log</title>
2
 
 
 
 
3 <h2>Changes for Version 1.32 (2015-03-14)</h2>
4 * When creating a new repository using [/help?cmd=init|fossil init], ensure
5 that the new repository is fully compatible with historical versions of
6 Fossil by having a valid manifest as RID 1.
7 * Anti-aliased rendering of arrowheads on timeline graphs.
8
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,11 @@
1 <title>Change Log</title>
2
3 <h2>Changes for Version 1.33 (not released yet)</h2>
4 * Add [/help?cmd=import|fossil import --svn], for importing a subversion
5 repository into fossil which was exported using "svnadmin dump".
6
7 <h2>Changes for Version 1.32 (2015-03-14)</h2>
8 * When creating a new repository using [/help?cmd=init|fossil init], ensure
9 that the new repository is fully compatible with historical versions of
10 Fossil by having a valid manifest as RID 1.
11 * Anti-aliased rendering of arrowheads on timeline graphs.
12

Keyboard Shortcuts

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