Fossil SCM

Working on fixing/rewriting svn-import function. This is not in a stable state yet. DO NOT TEST

baruch 2014-12-31 15:46 svn-import
Commit 9604c28207d0aea31510a2c717d94c2c5e4fcf0e
2 files changed +5 -5 +273 -158
+5 -5
--- src/foci.c
+++ src/foci.c
@@ -25,17 +25,17 @@
2525
#include <assert.h>
2626
2727
/*
2828
** The schema for the virtual table:
2929
*/
30
-static const char zFociSchema[] =
30
+static const char zFociSchema[] =
3131
@ CREATE TABLE files_of_checkin(
3232
@ checkinID INTEGER, -- RID for the checkin manifest
3333
@ filename TEXT, -- Name of a file
3434
@ uuid TEXT, -- SHA1 hash of the file
3535
@ previousName TEXT, -- Name of the file in previous checkin
36
-@ prem TEXT -- Permissions on the file
36
+@ perm TEXT -- Permissions on the file
3737
@ );
3838
;
3939
4040
#if INTERFACE
4141
/*
@@ -139,11 +139,11 @@
139139
FociCursor *pCsr = (FociCursor *)pCursor;
140140
return pCsr->pFile==0;
141141
}
142142
143143
static int fociFilter(
144
- sqlite3_vtab_cursor *pCursor,
144
+ sqlite3_vtab_cursor *pCursor,
145145
int idxNum, const char *idxStr,
146146
int argc, sqlite3_value **argv
147147
){
148148
FociCursor *pCur = (FociCursor *)pCursor;
149149
manifest_destroy(pCur->pMan);
@@ -158,12 +158,12 @@
158158
}
159159
return SQLITE_OK;
160160
}
161161
162162
static int fociColumn(
163
- sqlite3_vtab_cursor *pCursor,
164
- sqlite3_context *ctx,
163
+ sqlite3_vtab_cursor *pCursor,
164
+ sqlite3_context *ctx,
165165
int i
166166
){
167167
FociCursor *pCsr = (FociCursor *)pCursor;
168168
switch( i ){
169169
case 0: /* checkinID */
170170
--- src/foci.c
+++ src/foci.c
@@ -25,17 +25,17 @@
25 #include <assert.h>
26
27 /*
28 ** The schema for the virtual table:
29 */
30 static const char zFociSchema[] =
31 @ CREATE TABLE files_of_checkin(
32 @ checkinID INTEGER, -- RID for the checkin manifest
33 @ filename TEXT, -- Name of a file
34 @ uuid TEXT, -- SHA1 hash of the file
35 @ previousName TEXT, -- Name of the file in previous checkin
36 @ prem TEXT -- Permissions on the file
37 @ );
38 ;
39
40 #if INTERFACE
41 /*
@@ -139,11 +139,11 @@
139 FociCursor *pCsr = (FociCursor *)pCursor;
140 return pCsr->pFile==0;
141 }
142
143 static int fociFilter(
144 sqlite3_vtab_cursor *pCursor,
145 int idxNum, const char *idxStr,
146 int argc, sqlite3_value **argv
147 ){
148 FociCursor *pCur = (FociCursor *)pCursor;
149 manifest_destroy(pCur->pMan);
@@ -158,12 +158,12 @@
158 }
159 return SQLITE_OK;
160 }
161
162 static int fociColumn(
163 sqlite3_vtab_cursor *pCursor,
164 sqlite3_context *ctx,
165 int i
166 ){
167 FociCursor *pCsr = (FociCursor *)pCursor;
168 switch( i ){
169 case 0: /* checkinID */
170
--- src/foci.c
+++ src/foci.c
@@ -25,17 +25,17 @@
25 #include <assert.h>
26
27 /*
28 ** The schema for the virtual table:
29 */
30 static const char zFociSchema[] =
31 @ CREATE TABLE files_of_checkin(
32 @ checkinID INTEGER, -- RID for the checkin manifest
33 @ filename TEXT, -- Name of a file
34 @ uuid TEXT, -- SHA1 hash of the file
35 @ previousName TEXT, -- Name of the file in previous checkin
36 @ perm TEXT -- Permissions on the file
37 @ );
38 ;
39
40 #if INTERFACE
41 /*
@@ -139,11 +139,11 @@
139 FociCursor *pCsr = (FociCursor *)pCursor;
140 return pCsr->pFile==0;
141 }
142
143 static int fociFilter(
144 sqlite3_vtab_cursor *pCursor,
145 int idxNum, const char *idxStr,
146 int argc, sqlite3_value **argv
147 ){
148 FociCursor *pCur = (FociCursor *)pCursor;
149 manifest_destroy(pCur->pMan);
@@ -158,12 +158,12 @@
158 }
159 return SQLITE_OK;
160 }
161
162 static int fociColumn(
163 sqlite3_vtab_cursor *pCursor,
164 sqlite3_context *ctx,
165 int i
166 ){
167 FociCursor *pCsr = (FociCursor *)pCursor;
168 switch( i ){
169 case 0: /* checkinID */
170
+273 -158
--- 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>
@@ -718,25 +718,125 @@
718718
fossil_fatal("bad fast-import line: [%s]", zLine);
719719
return;
720720
}
721721
722722
static struct{
723
- int rev; /* SVN revision number */
724
- int parentRev; /* SVN revision number of parent check-in */
725
- char *zParentBranch; /* Name of branch of parent check-in */
726723
char *zBranch; /* Name of a branch for a commit */
727724
char *zDate; /* Date/time stamp */
728725
char *zUser; /* User name */
729726
char *zComment; /* Comment of a commit */
730
- int flatFlag; /* True if whole repo is a single file tree */
727
+ char *zFrom; /* from value as a UUID */
728
+ int nMerge; /* Number of merge values */
729
+ int nMergeAlloc; /* Number of slots in azMerge[] */
730
+ char **azMerge; /* Merge values */
731
+ int nFile; /* Number of aFile values */
732
+ int nFileAlloc; /* Number of slots in aFile[] */
733
+ ImportFile *aFile; /* Information about files in a commit */
734
+ int fromLoaded; /* True zFrom content loaded into aFile[] */
735
+} gsvn;
736
+
737
+/*
738
+** Create a new entry in the gsvn.aFile[] array
739
+*/
740
+static ImportFile *svn_import_add_file(){
741
+ ImportFile *pFile;
742
+ if( gsvn.nFile>=gsvn.nFileAlloc ){
743
+ gsvn.nFileAlloc = gsvn.nFileAlloc*2 + 100;
744
+ gsvn.aFile = fossil_realloc(gsvn.aFile, gsvn.nFileAlloc*sizeof(gsvn.aFile[0]));
745
+ }
746
+ pFile = &gsvn.aFile[gsvn.nFile++];
747
+ memset(pFile, 0, sizeof(*pFile));
748
+ return pFile;
749
+}
750
+
751
+/*
752
+** Load all file information out of the gsvn.zFrom check-in
753
+*/
754
+static void svn_import_prior_files(void){
755
+ Manifest *p;
756
+ int rid;
757
+ ManifestFile *pOld;
758
+ ImportFile *pNew;
759
+ if( gsvn.fromLoaded ) return;
760
+ gsvn.fromLoaded = 1;
761
+ if( gsvn.zFrom==0 && gsvn.zPrevCheckin!=0
762
+ && fossil_strcmp(gsvn.zBranch, gsvn.zPrevBranch)==0
763
+ ){
764
+ gsvn.zFrom = gsvn.zPrevCheckin;
765
+ gsvn.zPrevCheckin = 0;
766
+ }
767
+ if( gsvn.zFrom==0 ) return;
768
+ rid = fast_uuid_to_rid(gsvn.zFrom);
769
+ if( rid==0 ) return;
770
+ p = manifest_get(rid, CFTYPE_MANIFEST, 0);
771
+ if( p==0 ) return;
772
+ manifest_file_rewind(p);
773
+ while( (pOld = manifest_file_next(p, 0))!=0 ){
774
+ pNew = import_add_file();
775
+ pNew->zName = fossil_strdup(pOld->zName);
776
+ pNew->isExe = pOld->zPerm && strstr(pOld->zPerm, "x")!=0;
777
+ pNew->isLink = pOld->zPerm && strstr(pOld->zPerm, "l")!=0;
778
+ pNew->zUuid = fossil_strdup(pOld->zUuid);
779
+ pNew->isFrom = 1;
780
+ }
781
+ manifest_destroy(p);
782
+}
783
+
784
+/*
785
+** Deallocate the state information.
786
+**
787
+** The azMerge[] and aFile[] arrays are zeroed but allocated space is
788
+** retained unless the freeAll flag is set.
789
+*/
790
+static void svn_import_reset(int freeAll){
791
+ int i;
792
+// gsvn.xFinish = 0;
793
+// fossil_free(gsvn.zTag); gsvn.zTag = 0;
794
+ fossil_free(gsvn.zBranch); gsvn.zBranch = 0;
795
+// fossil_free(gsvn.aData); gsvn.aData = 0;
796
+// fossil_free(gsvn.zMark); gsvn.zMark = 0;
797
+ fossil_free(gsvn.zDate); gsvn.zDate = 0;
798
+ fossil_free(gsvn.zUser); gsvn.zUser = 0;
799
+ fossil_free(gsvn.zComment); gsvn.zComment = 0;
800
+ fossil_free(gsvn.zFrom); gsvn.zFrom = 0;
801
+// fossil_free(gsvn.zFromMark); gsvn.zFromMark = 0;
802
+ for(i=0; i<gsvn.nMerge; i++){
803
+ fossil_free(gsvn.azMerge[i]); gsvn.azMerge[i] = 0;
804
+ }
805
+ gsvn.nMerge = 0;
806
+ for(i=0; i<gsvn.nFile; i++){
807
+ fossil_free(gsvn.aFile[i].zName);
808
+ fossil_free(gsvn.aFile[i].zUuid);
809
+ fossil_free(gsvn.aFile[i].zPrior);
810
+ }
811
+ memset(gsvn.aFile, 0, gsvn.nFile*sizeof(gsvn.aFile[0]));
812
+ gsvn.nFile = 0;
813
+ if( freeAll ){
814
+// fossil_free(gsvn.zPrevBranch);
815
+// fossil_free(gsvn.zPrevCheckin);
816
+ fossil_free(gsvn.azMerge);
817
+ fossil_free(gsvn.aFile);
818
+ memset(&gsvn, 0, sizeof(gsvn));
819
+ }
820
+// gsvn.xFinish = finish_noop;
821
+}
822
+
823
+static struct{
824
+ int rev; /* SVN revision number */
825
+// int parentRev; /* SVN revision number of parent check-in */
826
+// char *zParentBranch; /* Name of branch of parent check-in */
827
+// char *zBranch; /* Name of a branch for a commit */
828
+ char *zDate; /* Date/time stamp */
829
+ char *zUser; /* User name */
830
+ char *zComment; /* Comment of a commit */
831
+// int flatFlag; /* True if whole repo is a single file tree */
731832
const char *zTrunk; /* Name of trunk folder in repo root */
732833
int lenTrunk; /* String length of zTrunk */
733834
const char *zBranches; /* Name of branches folder in repo root */
734835
int lenBranches; /* String length of zBranches */
735836
const char *zTags; /* Name of tags folder in repo root */
736837
int lenTags; /* String length of zTags */
737
- Blob filter; /* Path to repo root */
738838
} gsvn;
739839
typedef struct {
740840
char *zKey;
741841
const char *zVal;
742842
} KeyVal;
@@ -774,11 +874,11 @@
774874
fossil_free(rec->aHeaders[i].zKey);
775875
}
776876
fossil_free(rec->aHeaders);
777877
fossil_free(rec->aProps);
778878
fossil_free(rec->pRawProps);
779
- blob_reset(&rec->content);
879
+ blob_reset(&rec->content);
780880
}
781881
782882
static int svn_read_headers(FILE *pIn, SvnRecord *rec){
783883
char zLine[1000];
784884
@@ -903,11 +1003,11 @@
9031003
rec->contentFlag = 0;
9041004
}
9051005
return 1;
9061006
}
9071007
908
-static void svn_create_manifest(
1008
+static void svn_finish_revision(
9091009
){
9101010
Blob manifest;
9111011
static Stmt insRev;
9121012
static Stmt qParent;
9131013
static Stmt qParent2;
@@ -1109,46 +1209,63 @@
11091209
}
11101210
zDiff += lenData;
11111211
}
11121212
}
11131213
1114
-static char *svn_extract_branch(const char *zPath){
1115
- int nFilter = blob_size(&gsvn.filter);
1116
- char *zBranch = 0;
1117
- if( strncmp(zPath, blob_str(&gsvn.filter), nFilter)==0 ){
1118
- if( strncmp(zPath+nFilter, gsvn.zBranches, gsvn.lenBranches)==0 ){
1119
- int lenBranch;
1120
- const char *zTemp = zPath+nFilter+gsvn.lenBranches;
1121
- while( *zTemp && *zTemp!='/' ){ zTemp++; }
1122
- lenBranch = zTemp-(zPath+nFilter+gsvn.lenBranches);
1123
- zTemp = zPath+nFilter+gsvn.lenBranches;
1124
- zBranch = fossil_malloc(lenBranch+1);
1125
- memcpy(zBranch, zTemp, lenBranch);
1126
- zBranch[lenBranch] = '\0';
1127
- }else
1128
- if( strncmp(zPath+nFilter, gsvn.zTrunk, gsvn.lenTrunk-1)==0 ){
1129
- zBranch = mprintf("trunk");
1130
- }
1131
- }
1132
- return zBranch;
1214
+/*
1215
+** Extract the name of the branch or tag that the given path is on.
1216
+** Returns: 1 - It is on the trunk
1217
+** 2 - It is on a branch
1218
+** 3 - It is a tag
1219
+** 0 - It is none of the above
1220
+ */
1221
+static int *svn_parse_path(char *zPath, char **zBranch, char **zFile){
1222
+ if( strncmp(zPath, gsvn.zTrunk, gsvn.lenTrunk)==0 ){
1223
+ *zBranch = "trunk";
1224
+ *zFile = zPath+gsvn.lenTrunk;
1225
+ return 1;
1226
+ }else
1227
+ if( strncmp(zPath, gsvn.zBranches, gsvn.lenBranches)==0 ){
1228
+ *zFile = *zBranch = zPath+gsvn.lenBranches;
1229
+ while( **zFile && **zFile!='/' ){ (*zFile)++; }
1230
+ if( *zFile ){
1231
+ **zFile = '\0';
1232
+ (*zFile)++;
1233
+ }else{
1234
+ *zFile = 0;
1235
+ }
1236
+ return 2;
1237
+ }else
1238
+ if( strncmp(zPath, gsvn.zTags, gsvn.lenTags)==0 ){
1239
+ *zFile = *zBranch = zPath+gsvn.lenTags;
1240
+ while( **zFile && **zFile!='/' ){ (*zFile)++; }
1241
+ if( *zFile ){
1242
+ **zFile = '\0';
1243
+ (*zFile)++;
1244
+ }else{
1245
+ *zFile = 0;
1246
+ }
1247
+ return 3;
1248
+ }
1249
+ *zFile = *zBranch = 0;
1250
+ return 0;
11331251
}
11341252
11351253
/*
11361254
** Read the svn-dump format from pIn and insert the corresponding
11371255
** content into the database.
11381256
*/
11391257
static void svn_dump_import(FILE *pIn){
11401258
SvnRecord rec;
11411259
int ver;
1142
- const char *zTemp;
1260
+ char *zTemp;
11431261
const char *zUuid;
1144
- Stmt addHist;
1145
- Stmt insTag;
1146
- Stmt cpyPath;
1262
+ Stmt addFile;
11471263
Stmt delPath;
1148
- int bHasFiles = 0;
1149
- int nFilter = blob_size(&gsvn.filter);
1264
+ Stmt addSrc;
1265
+ Stmt addBranch;
1266
+ Stmt cpyPath;
11501267
11511268
/* version */
11521269
if( svn_read_rec(pIn, &rec)
11531270
&& (zTemp = svn_find_header(rec, "SVN-fs-dump-format-version")) ){
11541271
ver = atoi(zTemp);
@@ -1162,68 +1279,76 @@
11621279
/* UUID */
11631280
if( !svn_read_rec(pIn, &rec) || !(zUuid = svn_find_header(rec, "UUID")) ){
11641281
fossil_fatal("Missing UUID!");
11651282
}
11661283
svn_free_rec(&rec);
1284
+
11671285
/* content */
1168
- db_prepare(&addHist,
1169
- "INSERT INTO xhist (trev, tpath, trid, tperm) "
1170
- "VALUES(:rev, :path, :rid, :perm)"
1171
- );
1172
- db_prepare(&insTag, "INSERT INTO xtags (trev, ttag) VALUES(:rev, :tag)");
1173
- db_prepare(&cpyPath,
1174
- "WITH xsrc AS (SELECT * FROM ("
1175
- " SELECT tpath, trid, tperm, max(trev) trev FROM xhist"
1176
- " WHERE trev<=:srcrev GROUP BY tpath"
1177
- " ) WHERE trid NOTNULL)"
1178
- "INSERT INTO xhist (trev, tpath, trid, tperm)"
1179
- " SELECT :rev, :path||substr(tpath, length(:srcpath)+1), trid, tperm"
1180
- " FROM xsrc WHERE tpath>:srcpath||'/' AND tpath<:srcpath||'0'"
1286
+ db_prepare(&addFile,
1287
+ "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)"
1288
+ " VALUES(:path, :branch, :uuid, :perm)"
11811289
);
11821290
db_prepare(&delPath,
1183
- "INSERT INTO xhist (trev, tpath, trid, tperm)"
1184
- " SELECT :rev, tpath, NULL, NULL"
1185
- " FROM xfiles WHERE (tpath>:path||'/' AND tpath<:path||'0') OR tpath=:path"
1291
+ "DELETE FROM xfiles"
1292
+ " WHERE (tpath=:path OR (tpath>:path||'/' AND tpath<:path||'0'))"
1293
+ " AND tbranch=:branch"
1294
+ );
1295
+ db_prepare(&addSrc,
1296
+ "INSERT INTO xsrc (tpath, tbranch, tsrc, tsrcbranch, tsrcrev)"
1297
+ " VALUES(:path, :branch, :srcpath, :srcbranch, :srcrev)"
1298
+ );
1299
+ db_prepare(&addBranch,
1300
+ "INSERT INTO xchanges (tbranch, ttype) VALUES(:branch, :type)"
1301
+ );
1302
+ db_prepare(&cpyPath,
1303
+ "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)"
1304
+ " SELECT :path||substr(filename, length(:srcpath)+1), :branch, uuid, perm"
1305
+ " FROM xfoci"
1306
+ " WHERE chekinID=:rid AND filename>:srcpath||'/' AND filename<:srcpath||'0'"
11861307
);
11871308
gsvn.rev = -1;
11881309
while( svn_read_rec(pIn, &rec) ){
11891310
if( (zTemp = svn_find_header(rec, "Revision-number")) ){ /* revision node */
11901311
/* finish previous revision */
11911312
const char *zDate = NULL;
1192
- if( bHasFiles ){
1193
- svn_create_manifest();
1194
- }
1195
- fossil_free(gsvn.zUser);
1196
- fossil_free(gsvn.zComment);
1197
- fossil_free(gsvn.zDate);
1198
- fossil_free(gsvn.zBranch);
1199
- fossil_free(gsvn.zParentBranch);
1313
+ if( gsvn.rev>=0 ){
1314
+ svn_finish_revision();
1315
+ fossil_free(gsvn.zUser);
1316
+ fossil_free(gsvn.zComment);
1317
+ fossil_free(gsvn.zDate);
1318
+ }
12001319
/* start new revision */
12011320
gsvn.rev = atoi(zTemp);
12021321
gsvn.zUser = mprintf("%s", svn_find_prop(rec, "svn:author"));
12031322
gsvn.zComment = mprintf("%s", svn_find_prop(rec, "svn:log"));
12041323
zDate = svn_find_prop(rec, "svn:date");
12051324
if( zDate ){
1206
- gsvn.zDate = date_in_standard_format(zDate);
1207
- }
1208
- gsvn.parentRev = -1;
1209
- gsvn.zParentBranch = 0;
1210
- gsvn.zBranch = 0;
1211
- bHasFiles = 0;
1212
- fossil_print("\rImporting SVN revision: %d", gsvn.rev);
1213
- db_bind_int(&addHist, ":rev", gsvn.rev);
1214
- db_bind_int(&cpyPath, ":rev", gsvn.rev);
1215
- db_bind_int(&delPath, ":rev", gsvn.rev);
1325
+ zDate = date_in_standard_format(zDate);
1326
+ }
1327
+ gsvn.zDate = zDate;
1328
+ fossil_print("\rImporting SVN revision: %d", gsvn.rev);
12161329
}else
12171330
if( (zTemp = svn_find_header(rec, "Node-path")) ){ /* file/dir node */
1218
- const char *zPath = zTemp;
12191331
const char *zAction = svn_find_header(rec, "Node-action");
12201332
const char *zKind = svn_find_header(rec, "Node-kind");
12211333
const char *zSrcPath = svn_find_header(rec, "Node-copyfrom-path");
12221334
const char *zPerm = svn_find_prop(rec, "svn:executable") ? "x" : 0;
1335
+ char *zBranch;
1336
+ char *zFile;
1337
+ char *zSrcBranch;
1338
+ char *zSrcFile;
12231339
int deltaFlag = 0;
12241340
int srcRev = 0;
1341
+ int branchType = svn_parse_path(zTemp, &zBranch, &zFile);
1342
+ if( branchType==0 ){
1343
+ svn_free_rec(&rec);
1344
+ continue;
1345
+ }
1346
+ db_bind_text(&addBranch, ":branch", zBranch);
1347
+ db_bind_int(&addBranch, ":type", branchType);
1348
+ db_step(&addBranch);
1349
+ db_reset(&addBranch);
12251350
if( (zTemp = svn_find_header(rec, "Text-delta")) ){
12261351
deltaFlag = strncmp(zTemp, "true", 4)==0;
12271352
}
12281353
if( zSrcPath ){
12291354
zTemp = svn_find_header(rec, "Node-copyfrom-rev");
@@ -1230,71 +1355,55 @@
12301355
if( zTemp ){
12311356
srcRev = atoi(zTemp);
12321357
}else{
12331358
fossil_fatal("Missing copyfrom-rev");
12341359
}
1235
- }
1236
- if( !gsvn.flatFlag ){
1237
- char *zBranch;
1238
- if( (zBranch=svn_extract_branch(zPath))!=0 ){
1239
- if( gsvn.zBranch!=0 ){
1240
- if( strcmp(zBranch, gsvn.zBranch)!=0
1241
- && strncmp(zAction, "delete", 6)!=0)
1242
- {
1243
- fossil_fatal("Commit to multiple branches");
1244
- }
1245
- fossil_free(zBranch);
1246
- }else{
1247
- gsvn.zBranch = zBranch;
1248
- }
1249
- }
1360
+ if( svn_extract_branch(zSrcPath, &zSrcBranch, &zSrcFile)==0 ){
1361
+ fossil_fatal("Copy from path outside the import paths");
1362
+ }
1363
+ db_bind_text(&addSrc, ":path", zFile);
1364
+ db_bind_text(&addSrc, ":branch", zBranch);
1365
+ db_bind_text(&addSrc, ":srcpath", zSrcFile);
1366
+ db_bind_text(&addSrc, ":srcbranch", zSrcBranch);
1367
+ db_bind_int(&addSrc, ":srcrev", srcRev);
1368
+ db_step(&addSrc);
1369
+ db_reset(&addSrc);
12501370
}
12511371
if( strncmp(zAction, "delete", 6)==0
12521372
|| strncmp(zAction, "replace", 7)==0 )
12531373
{
1254
- db_bind_text(&delPath, ":path", zPath);
1374
+ db_bind_text(&delPath, ":path", zFile);
1375
+ db_bind_text(&delPath, ":branch", zBranch);
12551376
db_step(&delPath);
12561377
db_reset(&delPath);
1257
- bHasFiles = 1;
12581378
} /* no 'else' here since 'replace' does both a 'delete' and an 'add' */
12591379
if( strncmp(zAction, "add", 3)==0
12601380
|| strncmp(zAction, "replace", 7)==0 )
12611381
{
12621382
if( zKind==0 ){
12631383
fossil_fatal("Missing Node-kind");
12641384
}else if( strncmp(zKind, "dir", 3)==0 ){
12651385
if( zSrcPath ){
1266
- db_bind_int(&cpyPath, ":srcrev", srcRev);
1267
- db_bind_text(&cpyPath, ":path", zPath);
1268
- db_bind_text(&cpyPath, ":srcpath", zSrcPath);
1386
+ int srcRid = db_int(0, "SELECT trid, max(trev) FROM xrevisions"
1387
+ " WHERE trev<=%d AND tbranch=%Q",
1388
+ srcRev, zSrcBranch);
1389
+ db_bind_text(&cpyPath, ":path", zFile);
1390
+ db_bind_text(&cpyPath, ":branch", zBranch);
1391
+ db_bind_text(&cpyPath, ":srcpath", zSrcFile);
1392
+ db_bind_int(&cpyPath, ":rid", srcRid);
12691393
db_step(&cpyPath);
12701394
db_reset(&cpyPath);
1271
- bHasFiles = 1;
1272
- if( !gsvn.flatFlag ){
1273
- if( strncmp(zPath+nFilter, gsvn.zBranches, gsvn.lenBranches)==0 ){
1274
- zTemp = zPath+nFilter+gsvn.lenBranches+strlen(gsvn.zBranch);
1275
- if( *zTemp==0 ){
1276
- gsvn.parentRev = srcRev;
1277
- gsvn.zParentBranch = svn_extract_branch(zSrcPath);
1278
- }
1279
- }else if( strncmp(zPath+nFilter, gsvn.zTags, gsvn.lenTags)==0 ){
1280
- zTemp = zPath+nFilter+gsvn.lenTags;
1281
- db_bind_int(&insTag, ":rev", srcRev);
1282
- db_bind_text(&insTag, ":tag", zTemp);
1283
- db_step(&insTag);
1284
- db_reset(&insTag);
1285
- }
1286
- }
12871395
}
12881396
}else{
12891397
int rid = 0;
12901398
if( zSrcPath ){
1291
- rid = db_int(0, "SELECT trid, max(trev) FROM xhist"
1292
- " WHERE trev<=%d AND tpath=%Q", srcRev, zSrcPath);
1293
- if( rid==0 ){
1294
- fossil_fatal("Reference to non-existent path/revision");
1295
- }
1399
+ int srcRid = db_int(0, "SELECT trid, max(trev) FROM xrevisions"
1400
+ " WHERE trev<=%d AND tbranch=%Q",
1401
+ srcRev, zSrcBranch);
1402
+ rid = db_int(0, "SELECT rid FROM xfoci"
1403
+ " WHERE chekinID=%d AND filename=%Q",
1404
+ srcRid, zSrcFile)
12961405
}
12971406
if( deltaFlag ){
12981407
Blob deltaSrc;
12991408
Blob target;
13001409
if( rid!=0 ){
@@ -1338,11 +1447,11 @@
13381447
db_step(&addHist);
13391448
db_reset(&addHist);
13401449
bHasFiles = 1;
13411450
}
13421451
}else
1343
- if( strncmp(zAction, "delete", 6)!=0 ){ /* already did this above */
1452
+ if( strncmp(zAction, "delete", 6)!=0 ){ /* already did this one above */
13441453
fossil_fatal("Unknown Node-action");
13451454
}
13461455
}else{
13471456
fossil_fatal("Unknown record type");
13481457
}
@@ -1374,18 +1483,19 @@
13741483
** The following formats are currently understood by this command
13751484
**
13761485
** git Import from the git-fast-export file format
13771486
**
13781487
** svn Import from the svnadmin-dump file format. The default
1379
-** behaviour is to treat 3 folders in the SVN root as special,
1380
-** following the common layout of SVN repositories. These are
1381
-** (by default) trunk/, branches/ and tags/
1488
+** behaviour (unless overridden by --flat) is to treat 3 folders
1489
+** in the SVN root as special, following the common layout of
1490
+** SVN repositories. These are (by default) trunk/, branches/
1491
+** and tags/
13821492
** Options:
13831493
** --trunk FOLDER Name of trunk folder
13841494
** --branches FOLDER Name of branches folder
13851495
** --tags FOLDER Name of tags folder
1386
-** --filter PATH Path to project root in repository
1496
+** --base PATH Path to project root in repository
13871497
** --flat The whole dump is a single branch
13881498
**
13891499
** The --incremental option allows an existing repository to be extended
13901500
** with new content.
13911501
**
@@ -1396,18 +1506,18 @@
13961506
*/
13971507
void import_cmd(void){
13981508
char *zPassword;
13991509
FILE *pIn;
14001510
Stmt q;
1401
- const char *zFilter = find_option("filter", 0, 1);
1511
+ const char *zBase = find_option("base", 0, 1);
14021512
int lenFilter;
14031513
int forceFlag = find_option("force", "f", 0)!=0;
14041514
int incrFlag = find_option("incremental", "i", 0)!=0;
14051515
gsvn.zTrunk = find_option("trunk", 0, 1);
14061516
gsvn.zBranches = find_option("branches", 0, 1);
14071517
gsvn.zTags = find_option("tags", 0, 1);
1408
- gsvn.flatFlag = find_option("flat", 0, 0)!=0;
1518
+ int flatFlag = find_option("flat", 0, 0)!=0;
14091519
14101520
verify_all_options();
14111521
if( g.argc!=4 && g.argc!=5 ){
14121522
usage("FORMAT REPOSITORY-NAME");
14131523
}
@@ -1464,54 +1574,59 @@
14641574
db_finalize(&q);
14651575
}else
14661576
if( strncmp(g.argv[2], "svn", 3)==0 ){
14671577
db_multi_exec(
14681578
"CREATE TEMP TABLE xrevisions("
1469
- " trev INTEGER PRIMARY KEY, tbranch TEXT, tuuid TEXT"
1470
- ");"
1471
- "CREATE TEMP TABLE xhist("
1472
- " trev INT, tpath TEXT NOT NULL, trid TEXT, tperm TEXT,"
1473
- " UNIQUE (trev, tpath) ON CONFLICT REPLACE"
1579
+ " trev INTEGER, tbranch TEXT, trid INT, PRIMARY KEY(tbranch, trev)"
14741580
");"
14751581
"CREATE TEMP TABLE xfiles("
1476
- " tpath TEXT NOT NULL, trid TEXT, tperm TEXT,"
1477
- " UNIQUE (tpath) ON CONFLICT REPLACE"
1478
- ");"
1479
- "CREATE TEMP TRIGGER xfilesdeltrig AFTER INSERT ON xhist FOR EACH ROW"
1480
- " WHEN new.trid ISNULL"
1481
- " BEGIN DELETE FROM xfiles WHERE xfiles.tpath=new.tpath; END;"
1482
- "CREATE TEMP TRIGGER xfilesaddtrig AFTER INSERT ON xhist FOR EACH ROW"
1483
- " WHEN new.trid NOTNULL BEGIN INSERT INTO xfiles(tpath,trid,tperm)"
1484
- " VALUES(new.tpath, new.trid, new.tperm); END;"
1485
- "CREATE TEMP TABLE xtags("
1486
- " trev INT, ttag TEXT"
1487
- ");"
1582
+ " tpath TEXT, tbranch TEXT, tuuid TEXT, tperm TEXT,"
1583
+ " UNIQUE (tbranch, tpath) ON CONFLICT REPLACE"
1584
+ ");"
1585
+ "CREATE TEMP TABLE xsrc("
1586
+ " tpath TEXT, tbranch TEXT, tsrc TEXT, tsrcbranch TEXT, tsrcrev INT"
1587
+ ");"
1588
+ "CREATE TEMP TABLE xchanged("
1589
+ " tbranch TEXT, ttype INT,"
1590
+ " UNIQUE (tbranch) ON CONFLICT REPLACE"
1591
+ ");"
1592
+ "CREATE VIRTUAL TABLE temp.xfoci USING files_of_checkin;"
14881593
);
1489
- if( gsvn.zTrunk==0 ){ gsvn.zTrunk = "trunk/"; }
1490
- if( gsvn.zBranches==0 ){ gsvn.zBranches = "branches/"; }
1491
- if( gsvn.zTags==0 ){ gsvn.zTags = "tags/"; }
1492
- gsvn.lenTrunk = strlen(gsvn.zTrunk);
1493
- gsvn.lenBranches = strlen(gsvn.zBranches);
1494
- gsvn.lenTags = strlen(gsvn.zTags);
1495
- if( gsvn.zTrunk[gsvn.lenTrunk-1]!='/' ){
1496
- gsvn.zTrunk = mprintf("%s/", gsvn.zTrunk);
1497
- gsvn.lenTrunk++;
1498
- }
1499
- if( gsvn.zBranches[gsvn.lenBranches-1]!='/' ){
1500
- gsvn.zBranches = mprintf("%s/", gsvn.zBranches);
1501
- gsvn.lenBranches++;
1502
- }
1503
- if( gsvn.zTags[gsvn.lenTags-1]!='/' ){
1504
- gsvn.zTags = mprintf("%s/", gsvn.zTags);
1505
- gsvn.lenTags++;
1506
- }
1507
- if( zFilter==0 ){ zFilter = ""; }
1508
- lenFilter = strlen(zFilter);
1509
- blob_zero(&gsvn.filter);
1510
- blob_set(&gsvn.filter, zFilter);
1511
- if( lenFilter>0 && zFilter[lenFilter-1]!='/' ){
1512
- blob_append(&gsvn.filter, "/", 1);
1594
+ if( zBase==0 ){ zBase = ""; }
1595
+ if( strlen(zBase)>0 ){
1596
+ if( zBase[strlen(zBase)-1]!='/' ){
1597
+ zBase = mprintf("%s/", zBase);
1598
+ }
1599
+ if( flatFlag ){
1600
+ gsvn.zTrunk = zBase;
1601
+ gsvn.zBranches = 0;
1602
+ gsvn.zTags = 0;
1603
+ gsvn.lenTrunk = strlen(zBase);
1604
+ gsvn.lenBranches = 0;
1605
+ gsvn.lenTags = 0;
1606
+ }else{
1607
+ if( gsvn.zTrunk==0 ){ gsvn.zTrunk = "trunk/"; }
1608
+ if( gsvn.zBranches==0 ){ gsvn.zBranches = "branches/"; }
1609
+ if( gsvn.zTags==0 ){ gsvn.zTags = "tags/"; }
1610
+ gsvn.zTrunk = mprintf("%s%s", zBase, gsvn.zTrunk);
1611
+ gsvn.zBranches = mprintf("%s%s", zBase, gsvn.zBranches);
1612
+ gsvn.zTags = mprintf("%s%s", zBase, gsvn.zTags);
1613
+ gsvn.lenTrunk = strlen(gsvn.zTrunk);
1614
+ gsvn.lenBranches = strlen(gsvn.zBranches);
1615
+ gsvn.lenTags = strlen(gsvn.zTags);
1616
+ if( gsvn.zTrunk[gsvn.lenTrunk-1]!='/' ){
1617
+ gsvn.zTrunk = mprintf("%s/", gsvn.zTrunk);
1618
+ gsvn.lenTrunk++;
1619
+ }
1620
+ if( gsvn.zBranches[gsvn.lenBranches-1]!='/' ){
1621
+ gsvn.zBranches = mprintf("%s/", gsvn.zBranches);
1622
+ gsvn.lenBranches++;
1623
+ }
1624
+ if( gsvn.zTags[gsvn.lenTags-1]!='/' ){
1625
+ gsvn.zTags = mprintf("%s/", gsvn.zTags);
1626
+ gsvn.lenTags++;
1627
+ }
15131628
}
15141629
svn_dump_import(pIn);
15151630
}
15161631
15171632
verify_cancel();
15181633
--- 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>
@@ -718,25 +718,125 @@
718 fossil_fatal("bad fast-import line: [%s]", zLine);
719 return;
720 }
721
722 static struct{
723 int rev; /* SVN revision number */
724 int parentRev; /* SVN revision number of parent check-in */
725 char *zParentBranch; /* Name of branch of parent check-in */
726 char *zBranch; /* Name of a branch for a commit */
727 char *zDate; /* Date/time stamp */
728 char *zUser; /* User name */
729 char *zComment; /* Comment of a commit */
730 int flatFlag; /* True if whole repo is a single file tree */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
731 const char *zTrunk; /* Name of trunk folder in repo root */
732 int lenTrunk; /* String length of zTrunk */
733 const char *zBranches; /* Name of branches folder in repo root */
734 int lenBranches; /* String length of zBranches */
735 const char *zTags; /* Name of tags folder in repo root */
736 int lenTags; /* String length of zTags */
737 Blob filter; /* Path to repo root */
738 } gsvn;
739 typedef struct {
740 char *zKey;
741 const char *zVal;
742 } KeyVal;
@@ -774,11 +874,11 @@
774 fossil_free(rec->aHeaders[i].zKey);
775 }
776 fossil_free(rec->aHeaders);
777 fossil_free(rec->aProps);
778 fossil_free(rec->pRawProps);
779 blob_reset(&rec->content);
780 }
781
782 static int svn_read_headers(FILE *pIn, SvnRecord *rec){
783 char zLine[1000];
784
@@ -903,11 +1003,11 @@
903 rec->contentFlag = 0;
904 }
905 return 1;
906 }
907
908 static void svn_create_manifest(
909 ){
910 Blob manifest;
911 static Stmt insRev;
912 static Stmt qParent;
913 static Stmt qParent2;
@@ -1109,46 +1209,63 @@
1109 }
1110 zDiff += lenData;
1111 }
1112 }
1113
1114 static char *svn_extract_branch(const char *zPath){
1115 int nFilter = blob_size(&gsvn.filter);
1116 char *zBranch = 0;
1117 if( strncmp(zPath, blob_str(&gsvn.filter), nFilter)==0 ){
1118 if( strncmp(zPath+nFilter, gsvn.zBranches, gsvn.lenBranches)==0 ){
1119 int lenBranch;
1120 const char *zTemp = zPath+nFilter+gsvn.lenBranches;
1121 while( *zTemp && *zTemp!='/' ){ zTemp++; }
1122 lenBranch = zTemp-(zPath+nFilter+gsvn.lenBranches);
1123 zTemp = zPath+nFilter+gsvn.lenBranches;
1124 zBranch = fossil_malloc(lenBranch+1);
1125 memcpy(zBranch, zTemp, lenBranch);
1126 zBranch[lenBranch] = '\0';
1127 }else
1128 if( strncmp(zPath+nFilter, gsvn.zTrunk, gsvn.lenTrunk-1)==0 ){
1129 zBranch = mprintf("trunk");
1130 }
1131 }
1132 return zBranch;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1133 }
1134
1135 /*
1136 ** Read the svn-dump format from pIn and insert the corresponding
1137 ** content into the database.
1138 */
1139 static void svn_dump_import(FILE *pIn){
1140 SvnRecord rec;
1141 int ver;
1142 const char *zTemp;
1143 const char *zUuid;
1144 Stmt addHist;
1145 Stmt insTag;
1146 Stmt cpyPath;
1147 Stmt delPath;
1148 int bHasFiles = 0;
1149 int nFilter = blob_size(&gsvn.filter);
 
1150
1151 /* version */
1152 if( svn_read_rec(pIn, &rec)
1153 && (zTemp = svn_find_header(rec, "SVN-fs-dump-format-version")) ){
1154 ver = atoi(zTemp);
@@ -1162,68 +1279,76 @@
1162 /* UUID */
1163 if( !svn_read_rec(pIn, &rec) || !(zUuid = svn_find_header(rec, "UUID")) ){
1164 fossil_fatal("Missing UUID!");
1165 }
1166 svn_free_rec(&rec);
 
1167 /* content */
1168 db_prepare(&addHist,
1169 "INSERT INTO xhist (trev, tpath, trid, tperm) "
1170 "VALUES(:rev, :path, :rid, :perm)"
1171 );
1172 db_prepare(&insTag, "INSERT INTO xtags (trev, ttag) VALUES(:rev, :tag)");
1173 db_prepare(&cpyPath,
1174 "WITH xsrc AS (SELECT * FROM ("
1175 " SELECT tpath, trid, tperm, max(trev) trev FROM xhist"
1176 " WHERE trev<=:srcrev GROUP BY tpath"
1177 " ) WHERE trid NOTNULL)"
1178 "INSERT INTO xhist (trev, tpath, trid, tperm)"
1179 " SELECT :rev, :path||substr(tpath, length(:srcpath)+1), trid, tperm"
1180 " FROM xsrc WHERE tpath>:srcpath||'/' AND tpath<:srcpath||'0'"
1181 );
1182 db_prepare(&delPath,
1183 "INSERT INTO xhist (trev, tpath, trid, tperm)"
1184 " SELECT :rev, tpath, NULL, NULL"
1185 " FROM xfiles WHERE (tpath>:path||'/' AND tpath<:path||'0') OR tpath=:path"
 
 
 
 
 
 
 
 
 
 
 
 
 
1186 );
1187 gsvn.rev = -1;
1188 while( svn_read_rec(pIn, &rec) ){
1189 if( (zTemp = svn_find_header(rec, "Revision-number")) ){ /* revision node */
1190 /* finish previous revision */
1191 const char *zDate = NULL;
1192 if( bHasFiles ){
1193 svn_create_manifest();
1194 }
1195 fossil_free(gsvn.zUser);
1196 fossil_free(gsvn.zComment);
1197 fossil_free(gsvn.zDate);
1198 fossil_free(gsvn.zBranch);
1199 fossil_free(gsvn.zParentBranch);
1200 /* start new revision */
1201 gsvn.rev = atoi(zTemp);
1202 gsvn.zUser = mprintf("%s", svn_find_prop(rec, "svn:author"));
1203 gsvn.zComment = mprintf("%s", svn_find_prop(rec, "svn:log"));
1204 zDate = svn_find_prop(rec, "svn:date");
1205 if( zDate ){
1206 gsvn.zDate = date_in_standard_format(zDate);
1207 }
1208 gsvn.parentRev = -1;
1209 gsvn.zParentBranch = 0;
1210 gsvn.zBranch = 0;
1211 bHasFiles = 0;
1212 fossil_print("\rImporting SVN revision: %d", gsvn.rev);
1213 db_bind_int(&addHist, ":rev", gsvn.rev);
1214 db_bind_int(&cpyPath, ":rev", gsvn.rev);
1215 db_bind_int(&delPath, ":rev", gsvn.rev);
1216 }else
1217 if( (zTemp = svn_find_header(rec, "Node-path")) ){ /* file/dir node */
1218 const char *zPath = zTemp;
1219 const char *zAction = svn_find_header(rec, "Node-action");
1220 const char *zKind = svn_find_header(rec, "Node-kind");
1221 const char *zSrcPath = svn_find_header(rec, "Node-copyfrom-path");
1222 const char *zPerm = svn_find_prop(rec, "svn:executable") ? "x" : 0;
 
 
 
 
1223 int deltaFlag = 0;
1224 int srcRev = 0;
 
 
 
 
 
 
 
 
 
1225 if( (zTemp = svn_find_header(rec, "Text-delta")) ){
1226 deltaFlag = strncmp(zTemp, "true", 4)==0;
1227 }
1228 if( zSrcPath ){
1229 zTemp = svn_find_header(rec, "Node-copyfrom-rev");
@@ -1230,71 +1355,55 @@
1230 if( zTemp ){
1231 srcRev = atoi(zTemp);
1232 }else{
1233 fossil_fatal("Missing copyfrom-rev");
1234 }
1235 }
1236 if( !gsvn.flatFlag ){
1237 char *zBranch;
1238 if( (zBranch=svn_extract_branch(zPath))!=0 ){
1239 if( gsvn.zBranch!=0 ){
1240 if( strcmp(zBranch, gsvn.zBranch)!=0
1241 && strncmp(zAction, "delete", 6)!=0)
1242 {
1243 fossil_fatal("Commit to multiple branches");
1244 }
1245 fossil_free(zBranch);
1246 }else{
1247 gsvn.zBranch = zBranch;
1248 }
1249 }
1250 }
1251 if( strncmp(zAction, "delete", 6)==0
1252 || strncmp(zAction, "replace", 7)==0 )
1253 {
1254 db_bind_text(&delPath, ":path", zPath);
 
1255 db_step(&delPath);
1256 db_reset(&delPath);
1257 bHasFiles = 1;
1258 } /* no 'else' here since 'replace' does both a 'delete' and an 'add' */
1259 if( strncmp(zAction, "add", 3)==0
1260 || strncmp(zAction, "replace", 7)==0 )
1261 {
1262 if( zKind==0 ){
1263 fossil_fatal("Missing Node-kind");
1264 }else if( strncmp(zKind, "dir", 3)==0 ){
1265 if( zSrcPath ){
1266 db_bind_int(&cpyPath, ":srcrev", srcRev);
1267 db_bind_text(&cpyPath, ":path", zPath);
1268 db_bind_text(&cpyPath, ":srcpath", zSrcPath);
 
 
 
 
1269 db_step(&cpyPath);
1270 db_reset(&cpyPath);
1271 bHasFiles = 1;
1272 if( !gsvn.flatFlag ){
1273 if( strncmp(zPath+nFilter, gsvn.zBranches, gsvn.lenBranches)==0 ){
1274 zTemp = zPath+nFilter+gsvn.lenBranches+strlen(gsvn.zBranch);
1275 if( *zTemp==0 ){
1276 gsvn.parentRev = srcRev;
1277 gsvn.zParentBranch = svn_extract_branch(zSrcPath);
1278 }
1279 }else if( strncmp(zPath+nFilter, gsvn.zTags, gsvn.lenTags)==0 ){
1280 zTemp = zPath+nFilter+gsvn.lenTags;
1281 db_bind_int(&insTag, ":rev", srcRev);
1282 db_bind_text(&insTag, ":tag", zTemp);
1283 db_step(&insTag);
1284 db_reset(&insTag);
1285 }
1286 }
1287 }
1288 }else{
1289 int rid = 0;
1290 if( zSrcPath ){
1291 rid = db_int(0, "SELECT trid, max(trev) FROM xhist"
1292 " WHERE trev<=%d AND tpath=%Q", srcRev, zSrcPath);
1293 if( rid==0 ){
1294 fossil_fatal("Reference to non-existent path/revision");
1295 }
 
1296 }
1297 if( deltaFlag ){
1298 Blob deltaSrc;
1299 Blob target;
1300 if( rid!=0 ){
@@ -1338,11 +1447,11 @@
1338 db_step(&addHist);
1339 db_reset(&addHist);
1340 bHasFiles = 1;
1341 }
1342 }else
1343 if( strncmp(zAction, "delete", 6)!=0 ){ /* already did this above */
1344 fossil_fatal("Unknown Node-action");
1345 }
1346 }else{
1347 fossil_fatal("Unknown record type");
1348 }
@@ -1374,18 +1483,19 @@
1374 ** The following formats are currently understood by this command
1375 **
1376 ** git Import from the git-fast-export file format
1377 **
1378 ** svn Import from the svnadmin-dump file format. The default
1379 ** behaviour is to treat 3 folders in the SVN root as special,
1380 ** following the common layout of SVN repositories. These are
1381 ** (by default) trunk/, branches/ and tags/
 
1382 ** Options:
1383 ** --trunk FOLDER Name of trunk folder
1384 ** --branches FOLDER Name of branches folder
1385 ** --tags FOLDER Name of tags folder
1386 ** --filter PATH Path to project root in repository
1387 ** --flat The whole dump is a single branch
1388 **
1389 ** The --incremental option allows an existing repository to be extended
1390 ** with new content.
1391 **
@@ -1396,18 +1506,18 @@
1396 */
1397 void import_cmd(void){
1398 char *zPassword;
1399 FILE *pIn;
1400 Stmt q;
1401 const char *zFilter = find_option("filter", 0, 1);
1402 int lenFilter;
1403 int forceFlag = find_option("force", "f", 0)!=0;
1404 int incrFlag = find_option("incremental", "i", 0)!=0;
1405 gsvn.zTrunk = find_option("trunk", 0, 1);
1406 gsvn.zBranches = find_option("branches", 0, 1);
1407 gsvn.zTags = find_option("tags", 0, 1);
1408 gsvn.flatFlag = find_option("flat", 0, 0)!=0;
1409
1410 verify_all_options();
1411 if( g.argc!=4 && g.argc!=5 ){
1412 usage("FORMAT REPOSITORY-NAME");
1413 }
@@ -1464,54 +1574,59 @@
1464 db_finalize(&q);
1465 }else
1466 if( strncmp(g.argv[2], "svn", 3)==0 ){
1467 db_multi_exec(
1468 "CREATE TEMP TABLE xrevisions("
1469 " trev INTEGER PRIMARY KEY, tbranch TEXT, tuuid TEXT"
1470 ");"
1471 "CREATE TEMP TABLE xhist("
1472 " trev INT, tpath TEXT NOT NULL, trid TEXT, tperm TEXT,"
1473 " UNIQUE (trev, tpath) ON CONFLICT REPLACE"
1474 ");"
1475 "CREATE TEMP TABLE xfiles("
1476 " tpath TEXT NOT NULL, trid TEXT, tperm TEXT,"
1477 " UNIQUE (tpath) ON CONFLICT REPLACE"
1478 ");"
1479 "CREATE TEMP TRIGGER xfilesdeltrig AFTER INSERT ON xhist FOR EACH ROW"
1480 " WHEN new.trid ISNULL"
1481 " BEGIN DELETE FROM xfiles WHERE xfiles.tpath=new.tpath; END;"
1482 "CREATE TEMP TRIGGER xfilesaddtrig AFTER INSERT ON xhist FOR EACH ROW"
1483 " WHEN new.trid NOTNULL BEGIN INSERT INTO xfiles(tpath,trid,tperm)"
1484 " VALUES(new.tpath, new.trid, new.tperm); END;"
1485 "CREATE TEMP TABLE xtags("
1486 " trev INT, ttag TEXT"
1487 ");"
1488 );
1489 if( gsvn.zTrunk==0 ){ gsvn.zTrunk = "trunk/"; }
1490 if( gsvn.zBranches==0 ){ gsvn.zBranches = "branches/"; }
1491 if( gsvn.zTags==0 ){ gsvn.zTags = "tags/"; }
1492 gsvn.lenTrunk = strlen(gsvn.zTrunk);
1493 gsvn.lenBranches = strlen(gsvn.zBranches);
1494 gsvn.lenTags = strlen(gsvn.zTags);
1495 if( gsvn.zTrunk[gsvn.lenTrunk-1]!='/' ){
1496 gsvn.zTrunk = mprintf("%s/", gsvn.zTrunk);
1497 gsvn.lenTrunk++;
1498 }
1499 if( gsvn.zBranches[gsvn.lenBranches-1]!='/' ){
1500 gsvn.zBranches = mprintf("%s/", gsvn.zBranches);
1501 gsvn.lenBranches++;
1502 }
1503 if( gsvn.zTags[gsvn.lenTags-1]!='/' ){
1504 gsvn.zTags = mprintf("%s/", gsvn.zTags);
1505 gsvn.lenTags++;
1506 }
1507 if( zFilter==0 ){ zFilter = ""; }
1508 lenFilter = strlen(zFilter);
1509 blob_zero(&gsvn.filter);
1510 blob_set(&gsvn.filter, zFilter);
1511 if( lenFilter>0 && zFilter[lenFilter-1]!='/' ){
1512 blob_append(&gsvn.filter, "/", 1);
 
 
 
 
 
 
 
 
 
 
1513 }
1514 svn_dump_import(pIn);
1515 }
1516
1517 verify_cancel();
1518
--- 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>
@@ -718,25 +718,125 @@
718 fossil_fatal("bad fast-import line: [%s]", zLine);
719 return;
720 }
721
722 static struct{
 
 
 
723 char *zBranch; /* Name of a branch for a commit */
724 char *zDate; /* Date/time stamp */
725 char *zUser; /* User name */
726 char *zComment; /* Comment of a commit */
727 char *zFrom; /* from value as a UUID */
728 int nMerge; /* Number of merge values */
729 int nMergeAlloc; /* Number of slots in azMerge[] */
730 char **azMerge; /* Merge values */
731 int nFile; /* Number of aFile values */
732 int nFileAlloc; /* Number of slots in aFile[] */
733 ImportFile *aFile; /* Information about files in a commit */
734 int fromLoaded; /* True zFrom content loaded into aFile[] */
735 } gsvn;
736
737 /*
738 ** Create a new entry in the gsvn.aFile[] array
739 */
740 static ImportFile *svn_import_add_file(){
741 ImportFile *pFile;
742 if( gsvn.nFile>=gsvn.nFileAlloc ){
743 gsvn.nFileAlloc = gsvn.nFileAlloc*2 + 100;
744 gsvn.aFile = fossil_realloc(gsvn.aFile, gsvn.nFileAlloc*sizeof(gsvn.aFile[0]));
745 }
746 pFile = &gsvn.aFile[gsvn.nFile++];
747 memset(pFile, 0, sizeof(*pFile));
748 return pFile;
749 }
750
751 /*
752 ** Load all file information out of the gsvn.zFrom check-in
753 */
754 static void svn_import_prior_files(void){
755 Manifest *p;
756 int rid;
757 ManifestFile *pOld;
758 ImportFile *pNew;
759 if( gsvn.fromLoaded ) return;
760 gsvn.fromLoaded = 1;
761 if( gsvn.zFrom==0 && gsvn.zPrevCheckin!=0
762 && fossil_strcmp(gsvn.zBranch, gsvn.zPrevBranch)==0
763 ){
764 gsvn.zFrom = gsvn.zPrevCheckin;
765 gsvn.zPrevCheckin = 0;
766 }
767 if( gsvn.zFrom==0 ) return;
768 rid = fast_uuid_to_rid(gsvn.zFrom);
769 if( rid==0 ) return;
770 p = manifest_get(rid, CFTYPE_MANIFEST, 0);
771 if( p==0 ) return;
772 manifest_file_rewind(p);
773 while( (pOld = manifest_file_next(p, 0))!=0 ){
774 pNew = import_add_file();
775 pNew->zName = fossil_strdup(pOld->zName);
776 pNew->isExe = pOld->zPerm && strstr(pOld->zPerm, "x")!=0;
777 pNew->isLink = pOld->zPerm && strstr(pOld->zPerm, "l")!=0;
778 pNew->zUuid = fossil_strdup(pOld->zUuid);
779 pNew->isFrom = 1;
780 }
781 manifest_destroy(p);
782 }
783
784 /*
785 ** Deallocate the state information.
786 **
787 ** The azMerge[] and aFile[] arrays are zeroed but allocated space is
788 ** retained unless the freeAll flag is set.
789 */
790 static void svn_import_reset(int freeAll){
791 int i;
792 // gsvn.xFinish = 0;
793 // fossil_free(gsvn.zTag); gsvn.zTag = 0;
794 fossil_free(gsvn.zBranch); gsvn.zBranch = 0;
795 // fossil_free(gsvn.aData); gsvn.aData = 0;
796 // fossil_free(gsvn.zMark); gsvn.zMark = 0;
797 fossil_free(gsvn.zDate); gsvn.zDate = 0;
798 fossil_free(gsvn.zUser); gsvn.zUser = 0;
799 fossil_free(gsvn.zComment); gsvn.zComment = 0;
800 fossil_free(gsvn.zFrom); gsvn.zFrom = 0;
801 // fossil_free(gsvn.zFromMark); gsvn.zFromMark = 0;
802 for(i=0; i<gsvn.nMerge; i++){
803 fossil_free(gsvn.azMerge[i]); gsvn.azMerge[i] = 0;
804 }
805 gsvn.nMerge = 0;
806 for(i=0; i<gsvn.nFile; i++){
807 fossil_free(gsvn.aFile[i].zName);
808 fossil_free(gsvn.aFile[i].zUuid);
809 fossil_free(gsvn.aFile[i].zPrior);
810 }
811 memset(gsvn.aFile, 0, gsvn.nFile*sizeof(gsvn.aFile[0]));
812 gsvn.nFile = 0;
813 if( freeAll ){
814 // fossil_free(gsvn.zPrevBranch);
815 // fossil_free(gsvn.zPrevCheckin);
816 fossil_free(gsvn.azMerge);
817 fossil_free(gsvn.aFile);
818 memset(&gsvn, 0, sizeof(gsvn));
819 }
820 // gsvn.xFinish = finish_noop;
821 }
822
823 static struct{
824 int rev; /* SVN revision number */
825 // int parentRev; /* SVN revision number of parent check-in */
826 // char *zParentBranch; /* Name of branch of parent check-in */
827 // char *zBranch; /* Name of a branch for a commit */
828 char *zDate; /* Date/time stamp */
829 char *zUser; /* User name */
830 char *zComment; /* Comment of a commit */
831 // int flatFlag; /* True if whole repo is a single file tree */
832 const char *zTrunk; /* Name of trunk folder in repo root */
833 int lenTrunk; /* String length of zTrunk */
834 const char *zBranches; /* Name of branches folder in repo root */
835 int lenBranches; /* String length of zBranches */
836 const char *zTags; /* Name of tags folder in repo root */
837 int lenTags; /* String length of zTags */
 
838 } gsvn;
839 typedef struct {
840 char *zKey;
841 const char *zVal;
842 } KeyVal;
@@ -774,11 +874,11 @@
874 fossil_free(rec->aHeaders[i].zKey);
875 }
876 fossil_free(rec->aHeaders);
877 fossil_free(rec->aProps);
878 fossil_free(rec->pRawProps);
879 blob_reset(&rec->content);
880 }
881
882 static int svn_read_headers(FILE *pIn, SvnRecord *rec){
883 char zLine[1000];
884
@@ -903,11 +1003,11 @@
1003 rec->contentFlag = 0;
1004 }
1005 return 1;
1006 }
1007
1008 static void svn_finish_revision(
1009 ){
1010 Blob manifest;
1011 static Stmt insRev;
1012 static Stmt qParent;
1013 static Stmt qParent2;
@@ -1109,46 +1209,63 @@
1209 }
1210 zDiff += lenData;
1211 }
1212 }
1213
1214 /*
1215 ** Extract the name of the branch or tag that the given path is on.
1216 ** Returns: 1 - It is on the trunk
1217 ** 2 - It is on a branch
1218 ** 3 - It is a tag
1219 ** 0 - It is none of the above
1220 */
1221 static int *svn_parse_path(char *zPath, char **zBranch, char **zFile){
1222 if( strncmp(zPath, gsvn.zTrunk, gsvn.lenTrunk)==0 ){
1223 *zBranch = "trunk";
1224 *zFile = zPath+gsvn.lenTrunk;
1225 return 1;
1226 }else
1227 if( strncmp(zPath, gsvn.zBranches, gsvn.lenBranches)==0 ){
1228 *zFile = *zBranch = zPath+gsvn.lenBranches;
1229 while( **zFile && **zFile!='/' ){ (*zFile)++; }
1230 if( *zFile ){
1231 **zFile = '\0';
1232 (*zFile)++;
1233 }else{
1234 *zFile = 0;
1235 }
1236 return 2;
1237 }else
1238 if( strncmp(zPath, gsvn.zTags, gsvn.lenTags)==0 ){
1239 *zFile = *zBranch = zPath+gsvn.lenTags;
1240 while( **zFile && **zFile!='/' ){ (*zFile)++; }
1241 if( *zFile ){
1242 **zFile = '\0';
1243 (*zFile)++;
1244 }else{
1245 *zFile = 0;
1246 }
1247 return 3;
1248 }
1249 *zFile = *zBranch = 0;
1250 return 0;
1251 }
1252
1253 /*
1254 ** Read the svn-dump format from pIn and insert the corresponding
1255 ** content into the database.
1256 */
1257 static void svn_dump_import(FILE *pIn){
1258 SvnRecord rec;
1259 int ver;
1260 char *zTemp;
1261 const char *zUuid;
1262 Stmt addFile;
 
 
1263 Stmt delPath;
1264 Stmt addSrc;
1265 Stmt addBranch;
1266 Stmt cpyPath;
1267
1268 /* version */
1269 if( svn_read_rec(pIn, &rec)
1270 && (zTemp = svn_find_header(rec, "SVN-fs-dump-format-version")) ){
1271 ver = atoi(zTemp);
@@ -1162,68 +1279,76 @@
1279 /* UUID */
1280 if( !svn_read_rec(pIn, &rec) || !(zUuid = svn_find_header(rec, "UUID")) ){
1281 fossil_fatal("Missing UUID!");
1282 }
1283 svn_free_rec(&rec);
1284
1285 /* content */
1286 db_prepare(&addFile,
1287 "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)"
1288 " VALUES(:path, :branch, :uuid, :perm)"
 
 
 
 
 
 
 
 
 
 
1289 );
1290 db_prepare(&delPath,
1291 "DELETE FROM xfiles"
1292 " WHERE (tpath=:path OR (tpath>:path||'/' AND tpath<:path||'0'))"
1293 " AND tbranch=:branch"
1294 );
1295 db_prepare(&addSrc,
1296 "INSERT INTO xsrc (tpath, tbranch, tsrc, tsrcbranch, tsrcrev)"
1297 " VALUES(:path, :branch, :srcpath, :srcbranch, :srcrev)"
1298 );
1299 db_prepare(&addBranch,
1300 "INSERT INTO xchanges (tbranch, ttype) VALUES(:branch, :type)"
1301 );
1302 db_prepare(&cpyPath,
1303 "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)"
1304 " SELECT :path||substr(filename, length(:srcpath)+1), :branch, uuid, perm"
1305 " FROM xfoci"
1306 " WHERE chekinID=:rid AND filename>:srcpath||'/' AND filename<:srcpath||'0'"
1307 );
1308 gsvn.rev = -1;
1309 while( svn_read_rec(pIn, &rec) ){
1310 if( (zTemp = svn_find_header(rec, "Revision-number")) ){ /* revision node */
1311 /* finish previous revision */
1312 const char *zDate = NULL;
1313 if( gsvn.rev>=0 ){
1314 svn_finish_revision();
1315 fossil_free(gsvn.zUser);
1316 fossil_free(gsvn.zComment);
1317 fossil_free(gsvn.zDate);
1318 }
 
 
1319 /* start new revision */
1320 gsvn.rev = atoi(zTemp);
1321 gsvn.zUser = mprintf("%s", svn_find_prop(rec, "svn:author"));
1322 gsvn.zComment = mprintf("%s", svn_find_prop(rec, "svn:log"));
1323 zDate = svn_find_prop(rec, "svn:date");
1324 if( zDate ){
1325 zDate = date_in_standard_format(zDate);
1326 }
1327 gsvn.zDate = zDate;
1328 fossil_print("\rImporting SVN revision: %d", gsvn.rev);
 
 
 
 
 
 
1329 }else
1330 if( (zTemp = svn_find_header(rec, "Node-path")) ){ /* file/dir node */
 
1331 const char *zAction = svn_find_header(rec, "Node-action");
1332 const char *zKind = svn_find_header(rec, "Node-kind");
1333 const char *zSrcPath = svn_find_header(rec, "Node-copyfrom-path");
1334 const char *zPerm = svn_find_prop(rec, "svn:executable") ? "x" : 0;
1335 char *zBranch;
1336 char *zFile;
1337 char *zSrcBranch;
1338 char *zSrcFile;
1339 int deltaFlag = 0;
1340 int srcRev = 0;
1341 int branchType = svn_parse_path(zTemp, &zBranch, &zFile);
1342 if( branchType==0 ){
1343 svn_free_rec(&rec);
1344 continue;
1345 }
1346 db_bind_text(&addBranch, ":branch", zBranch);
1347 db_bind_int(&addBranch, ":type", branchType);
1348 db_step(&addBranch);
1349 db_reset(&addBranch);
1350 if( (zTemp = svn_find_header(rec, "Text-delta")) ){
1351 deltaFlag = strncmp(zTemp, "true", 4)==0;
1352 }
1353 if( zSrcPath ){
1354 zTemp = svn_find_header(rec, "Node-copyfrom-rev");
@@ -1230,71 +1355,55 @@
1355 if( zTemp ){
1356 srcRev = atoi(zTemp);
1357 }else{
1358 fossil_fatal("Missing copyfrom-rev");
1359 }
1360 if( svn_extract_branch(zSrcPath, &zSrcBranch, &zSrcFile)==0 ){
1361 fossil_fatal("Copy from path outside the import paths");
1362 }
1363 db_bind_text(&addSrc, ":path", zFile);
1364 db_bind_text(&addSrc, ":branch", zBranch);
1365 db_bind_text(&addSrc, ":srcpath", zSrcFile);
1366 db_bind_text(&addSrc, ":srcbranch", zSrcBranch);
1367 db_bind_int(&addSrc, ":srcrev", srcRev);
1368 db_step(&addSrc);
1369 db_reset(&addSrc);
 
 
 
 
 
1370 }
1371 if( strncmp(zAction, "delete", 6)==0
1372 || strncmp(zAction, "replace", 7)==0 )
1373 {
1374 db_bind_text(&delPath, ":path", zFile);
1375 db_bind_text(&delPath, ":branch", zBranch);
1376 db_step(&delPath);
1377 db_reset(&delPath);
 
1378 } /* no 'else' here since 'replace' does both a 'delete' and an 'add' */
1379 if( strncmp(zAction, "add", 3)==0
1380 || strncmp(zAction, "replace", 7)==0 )
1381 {
1382 if( zKind==0 ){
1383 fossil_fatal("Missing Node-kind");
1384 }else if( strncmp(zKind, "dir", 3)==0 ){
1385 if( zSrcPath ){
1386 int srcRid = db_int(0, "SELECT trid, max(trev) FROM xrevisions"
1387 " WHERE trev<=%d AND tbranch=%Q",
1388 srcRev, zSrcBranch);
1389 db_bind_text(&cpyPath, ":path", zFile);
1390 db_bind_text(&cpyPath, ":branch", zBranch);
1391 db_bind_text(&cpyPath, ":srcpath", zSrcFile);
1392 db_bind_int(&cpyPath, ":rid", srcRid);
1393 db_step(&cpyPath);
1394 db_reset(&cpyPath);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1395 }
1396 }else{
1397 int rid = 0;
1398 if( zSrcPath ){
1399 int srcRid = db_int(0, "SELECT trid, max(trev) FROM xrevisions"
1400 " WHERE trev<=%d AND tbranch=%Q",
1401 srcRev, zSrcBranch);
1402 rid = db_int(0, "SELECT rid FROM xfoci"
1403 " WHERE chekinID=%d AND filename=%Q",
1404 srcRid, zSrcFile)
1405 }
1406 if( deltaFlag ){
1407 Blob deltaSrc;
1408 Blob target;
1409 if( rid!=0 ){
@@ -1338,11 +1447,11 @@
1447 db_step(&addHist);
1448 db_reset(&addHist);
1449 bHasFiles = 1;
1450 }
1451 }else
1452 if( strncmp(zAction, "delete", 6)!=0 ){ /* already did this one above */
1453 fossil_fatal("Unknown Node-action");
1454 }
1455 }else{
1456 fossil_fatal("Unknown record type");
1457 }
@@ -1374,18 +1483,19 @@
1483 ** The following formats are currently understood by this command
1484 **
1485 ** git Import from the git-fast-export file format
1486 **
1487 ** svn Import from the svnadmin-dump file format. The default
1488 ** behaviour (unless overridden by --flat) is to treat 3 folders
1489 ** in the SVN root as special, following the common layout of
1490 ** SVN repositories. These are (by default) trunk/, branches/
1491 ** and tags/
1492 ** Options:
1493 ** --trunk FOLDER Name of trunk folder
1494 ** --branches FOLDER Name of branches folder
1495 ** --tags FOLDER Name of tags folder
1496 ** --base PATH Path to project root in repository
1497 ** --flat The whole dump is a single branch
1498 **
1499 ** The --incremental option allows an existing repository to be extended
1500 ** with new content.
1501 **
@@ -1396,18 +1506,18 @@
1506 */
1507 void import_cmd(void){
1508 char *zPassword;
1509 FILE *pIn;
1510 Stmt q;
1511 const char *zBase = find_option("base", 0, 1);
1512 int lenFilter;
1513 int forceFlag = find_option("force", "f", 0)!=0;
1514 int incrFlag = find_option("incremental", "i", 0)!=0;
1515 gsvn.zTrunk = find_option("trunk", 0, 1);
1516 gsvn.zBranches = find_option("branches", 0, 1);
1517 gsvn.zTags = find_option("tags", 0, 1);
1518 int flatFlag = find_option("flat", 0, 0)!=0;
1519
1520 verify_all_options();
1521 if( g.argc!=4 && g.argc!=5 ){
1522 usage("FORMAT REPOSITORY-NAME");
1523 }
@@ -1464,54 +1574,59 @@
1574 db_finalize(&q);
1575 }else
1576 if( strncmp(g.argv[2], "svn", 3)==0 ){
1577 db_multi_exec(
1578 "CREATE TEMP TABLE xrevisions("
1579 " trev INTEGER, tbranch TEXT, trid INT, PRIMARY KEY(tbranch, trev)"
 
 
 
 
1580 ");"
1581 "CREATE TEMP TABLE xfiles("
1582 " tpath TEXT, tbranch TEXT, tuuid TEXT, tperm TEXT,"
1583 " UNIQUE (tbranch, tpath) ON CONFLICT REPLACE"
1584 ");"
1585 "CREATE TEMP TABLE xsrc("
1586 " tpath TEXT, tbranch TEXT, tsrc TEXT, tsrcbranch TEXT, tsrcrev INT"
1587 ");"
1588 "CREATE TEMP TABLE xchanged("
1589 " tbranch TEXT, ttype INT,"
1590 " UNIQUE (tbranch) ON CONFLICT REPLACE"
1591 ");"
1592 "CREATE VIRTUAL TABLE temp.xfoci USING files_of_checkin;"
 
1593 );
1594 if( zBase==0 ){ zBase = ""; }
1595 if( strlen(zBase)>0 ){
1596 if( zBase[strlen(zBase)-1]!='/' ){
1597 zBase = mprintf("%s/", zBase);
1598 }
1599 if( flatFlag ){
1600 gsvn.zTrunk = zBase;
1601 gsvn.zBranches = 0;
1602 gsvn.zTags = 0;
1603 gsvn.lenTrunk = strlen(zBase);
1604 gsvn.lenBranches = 0;
1605 gsvn.lenTags = 0;
1606 }else{
1607 if( gsvn.zTrunk==0 ){ gsvn.zTrunk = "trunk/"; }
1608 if( gsvn.zBranches==0 ){ gsvn.zBranches = "branches/"; }
1609 if( gsvn.zTags==0 ){ gsvn.zTags = "tags/"; }
1610 gsvn.zTrunk = mprintf("%s%s", zBase, gsvn.zTrunk);
1611 gsvn.zBranches = mprintf("%s%s", zBase, gsvn.zBranches);
1612 gsvn.zTags = mprintf("%s%s", zBase, gsvn.zTags);
1613 gsvn.lenTrunk = strlen(gsvn.zTrunk);
1614 gsvn.lenBranches = strlen(gsvn.zBranches);
1615 gsvn.lenTags = strlen(gsvn.zTags);
1616 if( gsvn.zTrunk[gsvn.lenTrunk-1]!='/' ){
1617 gsvn.zTrunk = mprintf("%s/", gsvn.zTrunk);
1618 gsvn.lenTrunk++;
1619 }
1620 if( gsvn.zBranches[gsvn.lenBranches-1]!='/' ){
1621 gsvn.zBranches = mprintf("%s/", gsvn.zBranches);
1622 gsvn.lenBranches++;
1623 }
1624 if( gsvn.zTags[gsvn.lenTags-1]!='/' ){
1625 gsvn.zTags = mprintf("%s/", gsvn.zTags);
1626 gsvn.lenTags++;
1627 }
1628 }
1629 svn_dump_import(pIn);
1630 }
1631
1632 verify_cancel();
1633

Keyboard Shortcuts

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