1 /*
2 ** Copyright (c) 2010 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 **
12 ** Author contact information:
13 ** drh@sqlite.org
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>
24
25 #if INTERFACE
26 /*
27 ** A single file change record.
28 */
29 struct ImportFile {
30 char *zName; /* Name of a file */
31 char *zUuid; /* Hash of the file */
32 char *zPrior; /* Prior name if the name was changed */
33 char isFrom; /* True if obtained from the parent */
34 char isExe; /* True if executable */
35 char isLink; /* True if symlink */
36 };
37 #endif
38
39 /*
40 ** State information common to all import types.
41 */
42 static struct {
43 const char *zTrunkName; /* Name of trunk branch */
44 const char *zBranchPre; /* Prepended to non-trunk branch names */
45 const char *zBranchSuf; /* Appended to non-trunk branch names */
46 const char *zTagPre; /* Prepended to non-trunk tag names */
47 const char *zTagSuf; /* Appended to non-trunk tag names */
48 } gimport;
49
50 /*
51 ** State information about an on-going fast-import parse.
52 */
53 static struct {
54 void (*xFinish)(void); /* Function to finish a prior record */
55 int nData; /* Bytes of data */
56 char *zTag; /* Name of a tag */
57 char *zBranch; /* Name of a branch for a commit */
58 char *zPrevBranch; /* The branch of the previous check-in */
59 char *aData; /* Data content */
60 char *zMark; /* The current mark */
61 char *zDate; /* Date/time stamp */
62 char *zUser; /* User name */
63 char *zComment; /* Comment of a commit */
64 char *zFrom; /* from value as a hash */
65 char *zPrevCheckin; /* Name of the previous check-in */
66 char *zFromMark; /* The mark of the "from" field */
67 int nMerge; /* Number of merge values */
68 int nMergeAlloc; /* Number of slots in azMerge[] */
69 char **azMerge; /* Merge values */
70 int nFile; /* Number of aFile values */
71 int nFileAlloc; /* Number of slots in aFile[] */
72 ImportFile *aFile; /* Information about files in a commit */
73 ImportFile *pInlineFile; /* File marked "inline" */
74 int fromLoaded; /* True zFrom content loaded into aFile[] */
75 int tagCommit; /* True if the commit adds a tag */
76 } gg;
77
78 /*
79 ** Duplicate a string.
80 */
fossil_strndup(const char * zOrig,int len)81 char *fossil_strndup(const char *zOrig, int len){
82 char *z = 0;
83 if( zOrig ){
84 int n;
85 if( len<0 ){
86 n = strlen(zOrig);
87 }else{
88 for( n=0; zOrig[n] && n<len; ++n );
89 }
90 z = fossil_malloc( n+1 );
91 memcpy(z, zOrig, n);
92 z[n] = 0;
93 }
94 return z;
95 }
fossil_strdup(const char * zOrig)96 char *fossil_strdup(const char *zOrig){
97 return fossil_strndup(zOrig, -1);
98 }
99
100 /*
101 ** A no-op "xFinish" method
102 */
finish_noop(void)103 static void finish_noop(void){}
104
105 /*
106 ** Deallocate the state information.
107 **
108 ** The azMerge[] and aFile[] arrays are zeroed by allocated space is
109 ** retained unless the freeAll flag is set.
110 */
import_reset(int freeAll)111 static void import_reset(int freeAll){
112 int i;
113 gg.xFinish = 0;
114 fossil_free(gg.zTag); gg.zTag = 0;
115 fossil_free(gg.zBranch); gg.zBranch = 0;
116 fossil_free(gg.aData); gg.aData = 0;
117 fossil_free(gg.zMark); gg.zMark = 0;
118 fossil_free(gg.zDate); gg.zDate = 0;
119 fossil_free(gg.zUser); gg.zUser = 0;
120 fossil_free(gg.zComment); gg.zComment = 0;
121 fossil_free(gg.zFrom); gg.zFrom = 0;
122 fossil_free(gg.zFromMark); gg.zFromMark = 0;
123 for(i=0; i<gg.nMerge; i++){
124 fossil_free(gg.azMerge[i]); gg.azMerge[i] = 0;
125 }
126 gg.nMerge = 0;
127 for(i=0; i<gg.nFile; i++){
128 fossil_free(gg.aFile[i].zName);
129 fossil_free(gg.aFile[i].zUuid);
130 fossil_free(gg.aFile[i].zPrior);
131 }
132 memset(gg.aFile, 0, gg.nFile*sizeof(gg.aFile[0]));
133 gg.nFile = 0;
134 if( freeAll ){
135 fossil_free(gg.zPrevBranch);
136 fossil_free(gg.zPrevCheckin);
137 fossil_free(gg.azMerge);
138 fossil_free(gg.aFile);
139 memset(&gg, 0, sizeof(gg));
140 }
141 gg.xFinish = finish_noop;
142 }
143
144 /*
145 ** Insert an artifact into the BLOB table if it isn't there already.
146 ** If zMark is not zero, create a cross-reference from that mark back
147 ** to the newly inserted artifact.
148 **
149 ** If saveHash is true, then pContent is a commit record. Record its
150 ** artifact hash in gg.zPrevCheckin.
151 */
fast_insert_content(Blob * pContent,const char * zMark,ImportFile * pFile,int saveHash,int doParse)152 static int fast_insert_content(
153 Blob *pContent, /* Content to insert */
154 const char *zMark, /* Label using this mark, if not NULL */
155 ImportFile *pFile, /* Save hash on this file, if not NULL */
156 int saveHash, /* Save artifact hash in gg.zPrevCheckin */
157 int doParse /* Invoke manifest_crosslink() */
158 ){
159 Blob hash;
160 Blob cmpr;
161 int rid;
162
163 hname_hash(pContent, 0, &hash);
164 rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &hash);
165 if( rid==0 ){
166 static Stmt ins;
167 assert( g.rcvid>0 );
168 db_static_prepare(&ins,
169 "INSERT INTO blob(uuid, size, rcvid, content)"
170 "VALUES(:uuid, :size, %d, :content)", g.rcvid
171 );
172 db_bind_text(&ins, ":uuid", blob_str(&hash));
173 db_bind_int(&ins, ":size", gg.nData);
174 blob_compress(pContent, &cmpr);
175 db_bind_blob(&ins, ":content", &cmpr);
176 db_step(&ins);
177 db_reset(&ins);
178 blob_reset(&cmpr);
179 rid = db_last_insert_rowid();
180 if( doParse ){
181 manifest_crosslink(rid, pContent, MC_NONE);
182 }
183 }
184 if( zMark ){
185 db_multi_exec(
186 "INSERT OR IGNORE INTO xmark(tname, trid, tuuid)"
187 "VALUES(%Q,%d,%B)",
188 zMark, rid, &hash
189 );
190 db_multi_exec(
191 "INSERT OR IGNORE INTO xmark(tname, trid, tuuid)"
192 "VALUES(%B,%d,%B)",
193 &hash, rid, &hash
194 );
195 }
196 if( saveHash ){
197 fossil_free(gg.zPrevCheckin);
198 gg.zPrevCheckin = fossil_strdup(blob_str(&hash));
199 }
200 if( pFile ){
201 fossil_free(pFile->zUuid);
202 pFile->zUuid = fossil_strdup(blob_str(&hash));
203 }
204 blob_reset(&hash);
205 return rid;
206 }
207
208 /*
209 ** Check to ensure the file in gg.aData,gg.nData is not a control
210 ** artifact. Then add the file to the repository.
211 */
check_and_add_file(const char * zMark,ImportFile * pFile)212 static void check_and_add_file(const char *zMark, ImportFile *pFile){
213 Blob content;
214 blob_init(&content, gg.aData, gg.nData);
215 if( gg.nData && manifest_is_well_formed(gg.aData, gg.nData) ){
216 sterilize_manifest(&content, -1);
217 }
218 fast_insert_content(&content, zMark, pFile, 0, 0);
219 blob_reset(&content);
220 }
221
222 /*
223 ** Use data accumulated in gg from a "blob" record to add a new file
224 ** to the BLOB table.
225 */
finish_blob(void)226 static void finish_blob(void){
227 check_and_add_file(gg.zMark, 0);
228 import_reset(0);
229 }
230
231 /*
232 ** Use data accumulated in gg from a "tag" record to add a new
233 ** control artifact to the BLOB table.
234 */
finish_tag(void)235 static void finish_tag(void){
236 if( gg.zDate && gg.zTag && gg.zFrom && gg.zUser ){
237 Blob record, cksum;
238 blob_zero(&record);
239 blob_appendf(&record, "D %s\n", gg.zDate);
240 blob_appendf(&record, "T +sym-%F%F%F %s", gimport.zTagPre, gg.zTag,
241 gimport.zTagSuf, gg.zFrom);
242 if( gg.zComment ){
243 blob_appendf(&record, " %F", gg.zComment);
244 }
245 blob_appendf(&record, "\nU %F\n", gg.zUser);
246 md5sum_blob(&record, &cksum);
247 blob_appendf(&record, "Z %b\n", &cksum);
248 fast_insert_content(&record, 0, 0, 0, 1);
249 blob_reset(&cksum);
250 blob_reset(&record);
251 }
252 import_reset(0);
253 }
254
255 /*
256 ** Compare two ImportFile objects for sorting
257 */
mfile_cmp(const void * pLeft,const void * pRight)258 static int mfile_cmp(const void *pLeft, const void *pRight){
259 const ImportFile *pA = (const ImportFile*)pLeft;
260 const ImportFile *pB = (const ImportFile*)pRight;
261 return fossil_strcmp(pA->zName, pB->zName);
262 }
263
264 /*
265 ** Compare two strings for sorting.
266 */
string_cmp(const void * pLeft,const void * pRight)267 static int string_cmp(const void *pLeft, const void *pRight){
268 const char *zLeft = *(const char **)pLeft;
269 const char *zRight = *(const char **)pRight;
270 return fossil_strcmp(zLeft, zRight);
271 }
272
273 /* Forward reference */
274 static void import_prior_files(void);
275
276 /*
277 ** Use data accumulated in gg from a "commit" record to add a new
278 ** manifest artifact to the BLOB table.
279 */
finish_commit(void)280 static void finish_commit(void){
281 int i;
282 char *zFromBranch;
283 char *aTCard[4]; /* Array of T cards for manifest */
284 int nTCard = 0; /* Entries used in aTCard[] */
285 Blob record, cksum;
286
287 import_prior_files();
288 qsort(gg.aFile, gg.nFile, sizeof(gg.aFile[0]), mfile_cmp);
289 blob_zero(&record);
290 blob_appendf(&record, "C %F\n", gg.zComment);
291 blob_appendf(&record, "D %s\n", gg.zDate);
292 if( !g.fQuiet ){
293 fossil_print("%.10s\r", gg.zDate);
294 fflush(stdout);
295 }
296 for(i=0; i<gg.nFile; i++){
297 const char *zUuid = gg.aFile[i].zUuid;
298 if( zUuid==0 ) continue;
299 blob_appendf(&record, "F %F %s", gg.aFile[i].zName, zUuid);
300 if( gg.aFile[i].isExe ){
301 blob_append(&record, " x\n", 3);
302 }else if( gg.aFile[i].isLink ){
303 blob_append(&record, " l\n", 3);
304 }else{
305 blob_append(&record, "\n", 1);
306 }
307 }
308 if( gg.zFrom ){
309 blob_appendf(&record, "P %s", gg.zFrom);
310 for(i=0; i<gg.nMerge; i++){
311 blob_appendf(&record, " %s", gg.azMerge[i]);
312 }
313 blob_append(&record, "\n", 1);
314 zFromBranch = db_text(0, "SELECT brnm FROM xbranch WHERE tname=%Q",
315 gg.zFromMark);
316 }else{
317 zFromBranch = 0;
318 }
319
320 /* Add the required "T" cards to the manifest. Make sure they are added
321 ** in sorted order and without any duplicates. Otherwise, fossil will not
322 ** recognize the document as a valid manifest. */
323 if( !gg.tagCommit && fossil_strcmp(zFromBranch, gg.zBranch)!=0 ){
324 aTCard[nTCard++] = mprintf("T *branch * %F%F%F\n", gimport.zBranchPre,
325 gg.zBranch, gimport.zBranchSuf);
326 aTCard[nTCard++] = mprintf("T *sym-%F%F%F *\n", gimport.zBranchPre,
327 gg.zBranch, gimport.zBranchSuf);
328 if( zFromBranch ){
329 aTCard[nTCard++] = mprintf("T -sym-%F%F%F *\n", gimport.zBranchPre,
330 zFromBranch, gimport.zBranchSuf);
331 }
332 }
333 if( gg.zFrom==0 ){
334 aTCard[nTCard++] = mprintf("T *sym-%F *\n", gimport.zTrunkName);
335 }
336 qsort(aTCard, nTCard, sizeof(char *), string_cmp);
337 for(i=0; i<nTCard; i++){
338 if( i==0 || fossil_strcmp(aTCard[i-1], aTCard[i]) ){
339 blob_appendf(&record, "%s", aTCard[i]);
340 }
341 }
342 for(i=0; i<nTCard; i++) free(aTCard[i]);
343
344 free(zFromBranch);
345 db_multi_exec("INSERT INTO xbranch(tname, brnm) VALUES(%Q,%Q)",
346 gg.zMark, gg.zBranch);
347 blob_appendf(&record, "U %F\n", gg.zUser);
348 md5sum_blob(&record, &cksum);
349 blob_appendf(&record, "Z %b\n", &cksum);
350 fast_insert_content(&record, gg.zMark, 0, 1, 1);
351 blob_reset(&cksum);
352
353 /* The "git fast-export" command might output multiple "commit" lines
354 ** that reference a tag using "refs/tags/TAGNAME". The tag should only
355 ** be applied to the last commit that is output. The problem is we do not
356 ** know at this time if the current commit is the last one to hold this
357 ** tag or not. So make an entry in the XTAG table to record this tag
358 ** but overwrite that entry if a later instance of the same tag appears.
359 **
360 ** This behavior seems like a bug in git-fast-export, but it is easier
361 ** to work around the problem than to fix git-fast-export.
362 */
363 if( gg.tagCommit && gg.zDate && gg.zUser && gg.zFrom ){
364 record.nUsed = 0
365 /*in case fast_insert_comment() did not indirectly blob_reset() it */;
366 blob_appendf(&record, "D %s\n", gg.zDate);
367 blob_appendf(&record, "T +sym-%F%F%F %s\n", gimport.zBranchPre, gg.zBranch,
368 gimport.zBranchSuf, gg.zPrevCheckin);
369 blob_appendf(&record, "U %F\n", gg.zUser);
370 md5sum_blob(&record, &cksum);
371 blob_appendf(&record, "Z %b\n", &cksum);
372 db_multi_exec(
373 "INSERT OR REPLACE INTO xtag(tname, tcontent)"
374 " VALUES(%Q,%Q)", gg.zBranch, blob_str(&record)
375 );
376 blob_reset(&cksum);
377 }
378
379 blob_reset(&record);
380 fossil_free(gg.zPrevBranch);
381 gg.zPrevBranch = gg.zBranch;
382 gg.zBranch = 0;
383 import_reset(0);
384 }
385
386 /*
387 ** Turn the first \n in the input string into a \000
388 */
trim_newline(char * z)389 static void trim_newline(char *z){
390 while( z[0] && z[0]!='\n' ){ z++; }
391 z[0] = 0;
392 }
393
394 /*
395 ** Get a token from a line of text. Return a pointer to the first
396 ** character of the token and zero-terminate the token. Make
397 ** *pzIn point to the first character past the end of the zero
398 ** terminator, or at the zero-terminator at EOL.
399 */
next_token(char ** pzIn)400 static char *next_token(char **pzIn){
401 char *z = *pzIn;
402 int i;
403 if( z[0]==0 ) return z;
404 for(i=0; z[i] && z[i]!=' ' && z[i]!='\n'; i++){}
405 if( z[i] ){
406 z[i] = 0;
407 *pzIn = &z[i+1];
408 }else{
409 *pzIn = &z[i];
410 }
411 return z;
412 }
413
414 /*
415 ** Return a token that is all text up to (but omitting) the next \n
416 ** or \r\n.
417 */
rest_of_line(char ** pzIn)418 static char *rest_of_line(char **pzIn){
419 char *z = *pzIn;
420 int i;
421 if( z[0]==0 ) return z;
422 for(i=0; z[i] && z[i]!='\r' && z[i]!='\n'; i++){}
423 if( z[i] ){
424 if( z[i]=='\r' && z[i+1]=='\n' ){
425 z[i] = 0;
426 i++;
427 }else{
428 z[i] = 0;
429 }
430 *pzIn = &z[i+1];
431 }else{
432 *pzIn = &z[i];
433 }
434 return z;
435 }
436
437 /*
438 ** Convert a "mark" or "committish" into the artifact hash.
439 */
resolve_committish(const char * zCommittish)440 static char *resolve_committish(const char *zCommittish){
441 char *zRes;
442
443 zRes = db_text(0, "SELECT tuuid FROM xmark WHERE tname=%Q", zCommittish);
444 return zRes;
445 }
446
447 /*
448 ** Create a new entry in the gg.aFile[] array
449 */
import_add_file(void)450 static ImportFile *import_add_file(void){
451 ImportFile *pFile;
452 if( gg.nFile>=gg.nFileAlloc ){
453 gg.nFileAlloc = gg.nFileAlloc*2 + 100;
454 gg.aFile = fossil_realloc(gg.aFile, gg.nFileAlloc*sizeof(gg.aFile[0]));
455 }
456 pFile = &gg.aFile[gg.nFile++];
457 memset(pFile, 0, sizeof(*pFile));
458 return pFile;
459 }
460
461
462 /*
463 ** Load all file information out of the gg.zFrom check-in
464 */
import_prior_files(void)465 static void import_prior_files(void){
466 Manifest *p;
467 int rid;
468 ManifestFile *pOld;
469 ImportFile *pNew;
470 if( gg.fromLoaded ) return;
471 gg.fromLoaded = 1;
472 if( gg.zFrom==0 && gg.zPrevCheckin!=0
473 && fossil_strcmp(gg.zBranch, gg.zPrevBranch)==0
474 ){
475 gg.zFrom = gg.zPrevCheckin;
476 gg.zPrevCheckin = 0;
477 }
478 if( gg.zFrom==0 ) return;
479 rid = fast_uuid_to_rid(gg.zFrom);
480 if( rid==0 ) return;
481 p = manifest_get(rid, CFTYPE_MANIFEST, 0);
482 if( p==0 ) return;
483 manifest_file_rewind(p);
484 while( (pOld = manifest_file_next(p, 0))!=0 ){
485 pNew = import_add_file();
486 pNew->zName = fossil_strdup(pOld->zName);
487 pNew->isExe = pOld->zPerm && strstr(pOld->zPerm, "x")!=0;
488 pNew->isLink = pOld->zPerm && strstr(pOld->zPerm, "l")!=0;
489 pNew->zUuid = fossil_strdup(pOld->zUuid);
490 pNew->isFrom = 1;
491 }
492 manifest_destroy(p);
493 }
494
495 /*
496 ** Locate a file in the gg.aFile[] array by its name. Begin the search
497 ** with the *pI-th file. Update *pI to be one past the file found.
498 ** Do not search past the mx-th file.
499 */
import_find_file(const char * zName,int * pI,int mx)500 static ImportFile *import_find_file(const char *zName, int *pI, int mx){
501 int i = *pI;
502 int nName = strlen(zName);
503 while( i<mx ){
504 const char *z = gg.aFile[i].zName;
505 if( strncmp(zName, z, nName)==0 && (z[nName]==0 || z[nName]=='/') ){
506 *pI = i+1;
507 return &gg.aFile[i];
508 }
509 i++;
510 }
511 return 0;
512 }
513
514 /*
515 ** Dequote a fast-export filename. Filenames are normally unquoted. But
516 ** if the contain some obscure special characters, quotes might be added.
517 */
dequote_git_filename(char * zName)518 static void dequote_git_filename(char *zName){
519 int n, i, j;
520 if( zName==0 || zName[0]!='"' ) return;
521 n = (int)strlen(zName);
522 if( zName[n-1]!='"' ) return;
523 for(i=0, j=1; j<n-1; j++){
524 char c = zName[j];
525 int x;
526 if( c=='\\' ){
527 if( j+3 <= n-1
528 && zName[j+1]>='0' && zName[j+1]<='3'
529 && zName[j+2]>='0' && zName[j+2]<='7'
530 && zName[j+3]>='0' && zName[j+3]<='7'
531 && (x = 64*(zName[j+1]-'0') + 8*(zName[j+2]-'0') + zName[j+3]-'0')!=0
532 ){
533 c = (unsigned char)x;
534 j += 3;
535 }else{
536 c = zName[++j];
537 }
538 }
539 zName[i++] = c;
540 }
541 zName[i] = 0;
542 }
543
544
545 static struct{
546 const char *zMasterName; /* Name of master branch */
547 int authorFlag; /* Use author as checkin committer */
548 int nGitAttr; /* Number of Git --attribute entries */
549 struct { /* Git --attribute details */
550 char *zUser;
551 char *zEmail;
552 } *gitUserInfo;
553 } ggit;
554
555 /*
556 ** Read the git-fast-import format from pIn and insert the corresponding
557 ** content into the database.
558 */
git_fast_import(FILE * pIn)559 static void git_fast_import(FILE *pIn){
560 ImportFile *pFile, *pNew;
561 int i, mx;
562 char *z;
563 char *zUuid;
564 char *zName;
565 char *zPerm;
566 char *zFrom;
567 char *zTo;
568 char zLine[1000];
569
570 gg.xFinish = finish_noop;
571 while( fgets(zLine, sizeof(zLine), pIn) ){
572 if( zLine[0]=='\n' || zLine[0]=='#' ) continue;
573 if( strncmp(zLine, "blob", 4)==0 ){
574 gg.xFinish();
575 gg.xFinish = finish_blob;
576 }else
577 if( strncmp(zLine, "commit ", 7)==0 ){
578 const char *zRefName;
579 gg.xFinish();
580 gg.xFinish = finish_commit;
581 trim_newline(&zLine[7]);
582 zRefName = &zLine[7];
583
584 /* The argument to the "commit" line might match either of these
585 ** patterns:
586 **
587 ** (A) refs/heads/BRANCHNAME
588 ** (B) refs/tags/TAGNAME
589 **
590 ** If pattern A is used, then the branchname used is as shown.
591 ** Except, the "master" branch which is the default branch name in
592 ** Git is changed to "trunk" which is the default name in Fossil.
593 ** If the pattern is B, then the new commit should be on the same
594 ** branch as its parent. And, we might need to add the TAGNAME
595 ** tag to the new commit. However, if there are multiple instances
596 ** of pattern B with the same TAGNAME, then only put the tag on the
597 ** last commit that holds that tag.
598 **
599 ** None of the above is explained in the git-fast-export
600 ** documentation. We had to figure it out via trial and error.
601 */
602 for(i=5; i<strlen(zRefName) && zRefName[i]!='/'; i++){}
603 gg.tagCommit = strncmp(&zRefName[5], "tags", 4)==0; /* pattern B */
604 if( zRefName[i+1]!=0 ) zRefName += i+1;
605 if( fossil_strcmp(zRefName, "master")==0 ) zRefName = ggit.zMasterName;
606 gg.zBranch = fossil_strdup(zRefName);
607 gg.fromLoaded = 0;
608 }else
609 if( strncmp(zLine, "tag ", 4)==0 ){
610 gg.xFinish();
611 gg.xFinish = finish_tag;
612 trim_newline(&zLine[4]);
613 gg.zTag = fossil_strdup(&zLine[4]);
614 }else
615 if( strncmp(zLine, "reset ", 6)==0 ){
616 gg.xFinish();
617 }else
618 if( strncmp(zLine, "checkpoint", 10)==0 ){
619 gg.xFinish();
620 }else
621 if( strncmp(zLine, "feature", 7)==0 ){
622 gg.xFinish();
623 }else
624 if( strncmp(zLine, "option", 6)==0 ){
625 gg.xFinish();
626 }else
627 if( strncmp(zLine, "progress ", 9)==0 ){
628 gg.xFinish();
629 trim_newline(&zLine[9]);
630 fossil_print("%s\n", &zLine[9]);
631 fflush(stdout);
632 }else
633 if( strncmp(zLine, "data ", 5)==0 ){
634 fossil_free(gg.aData); gg.aData = 0;
635 gg.nData = atoi(&zLine[5]);
636 if( gg.nData ){
637 int got;
638 gg.aData = fossil_malloc( gg.nData+1 );
639 got = fread(gg.aData, 1, gg.nData, pIn);
640 if( got!=gg.nData ){
641 fossil_fatal("short read: got %d of %d bytes", got, gg.nData);
642 }
643 gg.aData[got] = '\0';
644 if( gg.zComment==0 &&
645 (gg.xFinish==finish_commit || gg.xFinish==finish_tag) ){
646 /* Strip trailing newline, it's appended to the comment. */
647 if( gg.aData[got-1] == '\n' )
648 gg.aData[got-1] = '\0';
649 gg.zComment = gg.aData;
650 gg.aData = 0;
651 gg.nData = 0;
652 }
653 }
654 if( gg.pInlineFile ){
655 check_and_add_file(0, gg.pInlineFile);
656 gg.pInlineFile = 0;
657 }
658 }else
659 if( (!ggit.authorFlag && strncmp(zLine, "author ", 7)==0)
660 || (ggit.authorFlag && strncmp(zLine, "committer ",10)==0
661 && gg.zUser!=NULL) ){
662 /* No-op */
663 }else
664 if( strncmp(zLine, "mark ", 5)==0 ){
665 trim_newline(&zLine[5]);
666 fossil_free(gg.zMark);
667 gg.zMark = fossil_strdup(&zLine[5]);
668 }else
669 if( strncmp(zLine, "tagger ", 7)==0
670 || (ggit.authorFlag && strncmp(zLine, "author ", 7)==0)
671 || strncmp(zLine, "committer ",10)==0 ){
672 sqlite3_int64 secSince1970;
673 z = strchr(zLine, ' ');
674 while( fossil_isspace(*z) ) z++;
675 if( (zTo=strchr(z, '>'))==NULL ) goto malformed_line;
676 *(++zTo) = '\0';
677 /*
678 ** If --attribute requested, lookup user in fx_ table by email address,
679 ** otherwise lookup Git {author,committer} contact info in user table. If
680 ** no matches, use email address as username for check-in attribution.
681 */
682 fossil_free(gg.zUser);
683 gg.zUser = db_text(0, "SELECT login FROM user WHERE info=%Q", z);
684 if( gg.zUser==NULL ){
685 /* If there is no user with this contact info,
686 * then use the email address as the username. */
687 if ( (z=strchr(z, '<'))==NULL ) goto malformed_line;
688 z++;
689 *(zTo-1) = '\0';
690 gg.zUser = fossil_strdup(z);
691 }
692 if (ggit.nGitAttr > 0 || db_table_exists("repository", "fx_git")) {
693 gg.zUser = db_text(gg.zUser,
694 "SELECT user FROM fx_git WHERE email=%Q", z);
695 }
696 secSince1970 = 0;
697 for(zTo++; fossil_isdigit(*zTo); zTo++){
698 secSince1970 = secSince1970*10 + *zTo - '0';
699 }
700 fossil_free(gg.zDate);
701 gg.zDate = db_text(0, "SELECT datetime(%lld, 'unixepoch')",secSince1970);
702 gg.zDate[10] = 'T';
703 }else
704 if( strncmp(zLine, "from ", 5)==0 ){
705 trim_newline(&zLine[5]);
706 fossil_free(gg.zFromMark);
707 gg.zFromMark = fossil_strdup(&zLine[5]);
708 fossil_free(gg.zFrom);
709 gg.zFrom = resolve_committish(&zLine[5]);
710 }else
711 if( strncmp(zLine, "merge ", 6)==0 ){
712 trim_newline(&zLine[6]);
713 if( gg.nMerge>=gg.nMergeAlloc ){
714 gg.nMergeAlloc = gg.nMergeAlloc*2 + 10;
715 gg.azMerge = fossil_realloc(gg.azMerge, gg.nMergeAlloc*sizeof(char*));
716 }
717 gg.azMerge[gg.nMerge] = resolve_committish(&zLine[6]);
718 if( gg.azMerge[gg.nMerge] ) gg.nMerge++;
719 }else
720 if( strncmp(zLine, "M ", 2)==0 ){
721 import_prior_files();
722 z = &zLine[2];
723 zPerm = next_token(&z);
724 zUuid = next_token(&z);
725 zName = rest_of_line(&z);
726 dequote_git_filename(zName);
727 i = 0;
728 pFile = import_find_file(zName, &i, gg.nFile);
729 if( pFile==0 ){
730 pFile = import_add_file();
731 pFile->zName = fossil_strdup(zName);
732 }
733 pFile->isExe = (sqlite3_strglob("*755",zPerm)==0);
734 pFile->isLink = (fossil_strcmp(zPerm, "120000")==0);
735 fossil_free(pFile->zUuid);
736 if( strcmp(zUuid,"inline")==0 ){
737 pFile->zUuid = 0;
738 gg.pInlineFile = pFile;
739 }else{
740 pFile->zUuid = resolve_committish(zUuid);
741 }
742 pFile->isFrom = 0;
743 }else
744 if( strncmp(zLine, "D ", 2)==0 ){
745 import_prior_files();
746 z = &zLine[2];
747 zName = rest_of_line(&z);
748 dequote_git_filename(zName);
749 i = 0;
750 while( (pFile = import_find_file(zName, &i, gg.nFile))!=0 ){
751 if( pFile->isFrom==0 ) continue;
752 fossil_free(pFile->zName);
753 fossil_free(pFile->zPrior);
754 fossil_free(pFile->zUuid);
755 *pFile = gg.aFile[--gg.nFile];
756 i--;
757 }
758 }else
759 if( strncmp(zLine, "C ", 2)==0 ){
760 int nFrom;
761 import_prior_files();
762 z = &zLine[2];
763 zFrom = next_token(&z);
764 zTo = rest_of_line(&z);
765 i = 0;
766 mx = gg.nFile;
767 nFrom = strlen(zFrom);
768 while( (pFile = import_find_file(zFrom, &i, mx))!=0 ){
769 if( pFile->isFrom==0 ) continue;
770 pNew = import_add_file();
771 pFile = &gg.aFile[i-1];
772 if( strlen(pFile->zName)>nFrom ){
773 pNew->zName = mprintf("%s%s", zTo, pFile->zName+nFrom);
774 }else{
775 pNew->zName = fossil_strdup(zTo);
776 }
777 pNew->isExe = pFile->isExe;
778 pNew->isLink = pFile->isLink;
779 pNew->zUuid = fossil_strdup(pFile->zUuid);
780 pNew->isFrom = 0;
781 }
782 }else
783 if( strncmp(zLine, "R ", 2)==0 ){
784 int nFrom;
785 import_prior_files();
786 z = &zLine[2];
787 zFrom = next_token(&z);
788 zTo = rest_of_line(&z);
789 i = 0;
790 nFrom = strlen(zFrom);
791 while( (pFile = import_find_file(zFrom, &i, gg.nFile))!=0 ){
792 if( pFile->isFrom==0 ) continue;
793 pNew = import_add_file();
794 pFile = &gg.aFile[i-1];
795 if( strlen(pFile->zName)>nFrom ){
796 pNew->zName = mprintf("%s%s", zTo, pFile->zName+nFrom);
797 }else{
798 pNew->zName = fossil_strdup(zTo);
799 }
800 pNew->zPrior = pFile->zName;
801 pNew->isExe = pFile->isExe;
802 pNew->isLink = pFile->isLink;
803 pNew->zUuid = pFile->zUuid;
804 pNew->isFrom = 0;
805 gg.nFile--;
806 *pFile = *pNew;
807 memset(pNew, 0, sizeof(*pNew));
808 }
809 }else
810 if( strncmp(zLine, "deleteall", 9)==0 ){
811 gg.fromLoaded = 1;
812 }else
813 if( strncmp(zLine, "N ", 2)==0 ){
814 /* No-op */
815 }else
816 if( strncmp(zLine, "property branch-nick ", 21)==0 ){
817 /* Breezy uses this property to store the branch name.
818 ** It has two values. Integer branch number, then the
819 ** user-readable branch name. */
820 z = &zLine[21];
821 next_token(&z);
822 fossil_free(gg.zBranch);
823 gg.zBranch = fossil_strdup(next_token(&z));
824 }else
825 if( strncmp(zLine, "property rebase-of ", 19)==0 ){
826 /* Breezy uses this property to record that a branch
827 ** was rebased. Silently ignore it. */
828 }else
829 {
830 goto malformed_line;
831 }
832 }
833 gg.xFinish();
834 import_reset(1);
835 return;
836
837 malformed_line:
838 trim_newline(zLine);
839 fossil_fatal("bad fast-import line: [%s]", zLine);
840 return;
841 }
842
843 static struct{
844 int rev; /* SVN revision number */
845 char *zDate; /* Date/time stamp */
846 char *zUser; /* User name */
847 char *zComment; /* Comment of a commit */
848 const char *zTrunk; /* Name of trunk folder in repo root */
849 int lenTrunk; /* String length of zTrunk */
850 const char *zBranches; /* Name of branches folder in repo root */
851 int lenBranches; /* String length of zBranches */
852 const char *zTags; /* Name of tags folder in repo root */
853 int lenTags; /* String length of zTags */
854 Bag newBranches; /* Branches that were created in this revision */
855 int revFlag; /* Add svn-rev-nn tags on every checkin */
856 const char *zRevPre; /* Prepended to revision tag names */
857 const char *zRevSuf; /* Appended to revision tag names */
858 const char **azIgnTree; /* NULL-terminated list of dirs to ignore */
859 } gsvn;
860 typedef struct {
861 char *zKey;
862 char *zVal;
863 } KeyVal;
864 typedef struct {
865 KeyVal *aHeaders;
866 int nHeaders;
867 char *pRawProps;
868 KeyVal *aProps;
869 int nProps;
870 Blob content;
871 int contentFlag;
872 } SvnRecord;
873
874 #define svn_find_header(rec, zHeader) \
875 svn_find_keyval((rec).aHeaders, (rec).nHeaders, (zHeader))
876 #define svn_find_prop(rec, zProp) \
877 svn_find_keyval((rec).aProps, (rec).nProps, (zProp))
svn_find_keyval(KeyVal * aKeyVal,int nKeyVal,const char * zKey)878 static char *svn_find_keyval(
879 KeyVal *aKeyVal,
880 int nKeyVal,
881 const char *zKey
882 ){
883 int i;
884 for(i=0; i<nKeyVal; i++){
885 if( fossil_strcmp(aKeyVal[i].zKey, zKey)==0 ){
886 return aKeyVal[i].zVal;
887 }
888 }
889 return 0;
890 }
891
svn_free_rec(SvnRecord * rec)892 static void svn_free_rec(SvnRecord *rec){
893 int i;
894 for(i=0; i<rec->nHeaders; i++){
895 fossil_free(rec->aHeaders[i].zKey);
896 }
897 fossil_free(rec->aHeaders);
898 fossil_free(rec->aProps);
899 fossil_free(rec->pRawProps);
900 blob_reset(&rec->content);
901 }
902
svn_read_headers(FILE * pIn,SvnRecord * rec)903 static int svn_read_headers(FILE *pIn, SvnRecord *rec){
904 char zLine[1000];
905
906 rec->aHeaders = 0;
907 rec->nHeaders = 0;
908 while( fgets(zLine, sizeof(zLine), pIn) ){
909 if( zLine[0]!='\n' ) break;
910 }
911 if( feof(pIn) ) return 0;
912 do{
913 char *sep;
914 if( zLine[0]=='\n' ) break;
915 rec->nHeaders += 1;
916 rec->aHeaders = fossil_realloc(rec->aHeaders,
917 sizeof(rec->aHeaders[0])*rec->nHeaders);
918 rec->aHeaders[rec->nHeaders-1].zKey = mprintf("%s", zLine);
919 sep = strchr(rec->aHeaders[rec->nHeaders-1].zKey, ':');
920 if( !sep ){
921 trim_newline(zLine);
922 fossil_fatal("bad header line: [%s]", zLine);
923 }
924 *sep = 0;
925 rec->aHeaders[rec->nHeaders-1].zVal = sep+1;
926 sep = strchr(rec->aHeaders[rec->nHeaders-1].zVal, '\n');
927 *sep = 0;
928 while(rec->aHeaders[rec->nHeaders-1].zVal
929 && fossil_isspace(*(rec->aHeaders[rec->nHeaders-1].zVal)) )
930 {
931 rec->aHeaders[rec->nHeaders-1].zVal++;
932 }
933 }while( fgets(zLine, sizeof(zLine), pIn) );
934 if( zLine[0]!='\n' ){
935 trim_newline(zLine);
936 fossil_fatal("svn-dump data ended unexpectedly");
937 }
938 return 1;
939 }
940
svn_read_props(FILE * pIn,SvnRecord * rec)941 static void svn_read_props(FILE *pIn, SvnRecord *rec){
942 int nRawProps = 0;
943 char *pRawProps;
944 const char *zLen;
945
946 rec->pRawProps = 0;
947 rec->aProps = 0;
948 rec->nProps = 0;
949 zLen = svn_find_header(*rec, "Prop-content-length");
950 if( zLen ){
951 nRawProps = atoi(zLen);
952 }
953 if( nRawProps ){
954 int got;
955 char *zLine;
956 rec->pRawProps = pRawProps = fossil_malloc( nRawProps );
957 got = fread(rec->pRawProps, 1, nRawProps, pIn);
958 if( got!=nRawProps ){
959 fossil_fatal("short read: got %d of %d bytes", got, nRawProps);
960 }
961 if( memcmp(&pRawProps[got-10], "PROPS-END\n", 10)!=0 ){
962 fossil_fatal("svn-dump data ended unexpectedly");
963 }
964 zLine = pRawProps;
965 while( zLine<(pRawProps+nRawProps-10) ){
966 char *eol;
967 int propLen;
968 if( zLine[0]=='D' ){
969 propLen = atoi(&zLine[2]);
970 eol = strchr(zLine, '\n');
971 zLine = eol+1+propLen+1;
972 }else{
973 if( zLine[0]!='K' ){
974 fossil_fatal("svn-dump data format broken");
975 }
976 propLen = atoi(&zLine[2]);
977 eol = strchr(zLine, '\n');
978 zLine = eol+1;
979 eol = zLine+propLen;
980 if( *eol!='\n' ){
981 fossil_fatal("svn-dump data format broken");
982 }
983 *eol = 0;
984 rec->nProps += 1;
985 rec->aProps = fossil_realloc(rec->aProps,
986 sizeof(rec->aProps[0])*rec->nProps);
987 rec->aProps[rec->nProps-1].zKey = zLine;
988 zLine = eol+1;
989 if( zLine[0]!='V' ){
990 fossil_fatal("svn-dump data format broken");
991 }
992 propLen = atoi(&zLine[2]);
993 eol = strchr(zLine, '\n');
994 zLine = eol+1;
995 eol = zLine+propLen;
996 if( *eol!='\n' ){
997 fossil_fatal("svn-dump data format broken");
998 }
999 *eol = 0;
1000 rec->aProps[rec->nProps-1].zVal = zLine;
1001 zLine = eol+1;
1002 }
1003 }
1004 }
1005 }
1006
svn_read_rec(FILE * pIn,SvnRecord * rec)1007 static int svn_read_rec(FILE *pIn, SvnRecord *rec){
1008 const char *zLen;
1009 int nLen = 0;
1010 if( svn_read_headers(pIn, rec)==0 ) return 0;
1011 svn_read_props(pIn, rec);
1012 blob_zero(&rec->content);
1013 zLen = svn_find_header(*rec, "Text-content-length");
1014 if( zLen ){
1015 rec->contentFlag = 1;
1016 nLen = atoi(zLen);
1017 blob_read_from_channel(&rec->content, pIn, nLen);
1018 if( blob_size(&rec->content)!=nLen ){
1019 fossil_fatal("short read: got %d of %d bytes",
1020 blob_size(&rec->content), nLen
1021 );
1022 }
1023 }else{
1024 rec->contentFlag = 0;
1025 }
1026 return 1;
1027 }
1028
1029 /*
1030 ** Returns the UUID for the RID, or NULL if not found.
1031 ** The returned string is allocated via db_text() and must be
1032 ** free()d by the caller.
1033 */
rid_to_uuid(int rid)1034 char *rid_to_uuid(int rid){
1035 return db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
1036 }
1037
1038 #define SVN_UNKNOWN 0
1039 #define SVN_TRUNK 1
1040 #define SVN_BRANCH 2
1041 #define SVN_TAG 3
1042
1043 #define MAX_INT_32 (0x7FFFFFFFL)
1044
svn_finish_revision()1045 static void svn_finish_revision(){
1046 Blob manifest;
1047 static Stmt getChanges;
1048 static Stmt getFiles;
1049 static Stmt setRid;
1050 Blob mcksum;
1051
1052 blob_zero(&manifest);
1053 db_static_prepare(&getChanges, "SELECT tid, tname, ttype, tparent"
1054 " FROM xrevisions, xbranches ON (tbranch=tid)"
1055 " WHERE trid ISNULL");
1056 db_static_prepare(&getFiles, "SELECT tpath, tuuid, tperm FROM xfiles"
1057 " WHERE tbranch=:branch ORDER BY tpath");
1058 db_prepare(&setRid, "UPDATE xrevisions SET trid=:rid"
1059 " WHERE trev=%d AND tbranch=:branch", gsvn.rev);
1060 while( db_step(&getChanges)==SQLITE_ROW ){
1061 int branchId = db_column_int(&getChanges, 0);
1062 const char *zBranch = db_column_text(&getChanges, 1);
1063 int branchType = db_column_int(&getChanges, 2);
1064 int parentRid = db_column_int(&getChanges, 3);
1065 int mergeRid = parentRid;
1066 Manifest *pParentManifest = 0;
1067 ManifestFile *pParentFile = 0;
1068 int sameAsParent = 1;
1069 int parentBranch = 0;
1070 if( !bag_find(&gsvn.newBranches, branchId) ){
1071 parentRid = db_int(0, "SELECT trid, max(trev) FROM xrevisions"
1072 " WHERE trev<%d AND tbranch=%d",
1073 gsvn.rev, branchId);
1074 }
1075 if( parentRid>0 ){
1076 pParentManifest = manifest_get(parentRid, CFTYPE_MANIFEST, 0);
1077 if( pParentManifest ){
1078 pParentFile = manifest_file_next(pParentManifest, 0);
1079 parentBranch = db_int(0, "SELECT tbranch FROM xrevisions WHERE trid=%d",
1080 parentRid);
1081 if( parentBranch!=branchId && branchType!=SVN_TAG ){
1082 sameAsParent = 0;
1083 }
1084 }
1085 }
1086 if( mergeRid<MAX_INT_32 ){
1087 if( gsvn.zComment ){
1088 blob_appendf(&manifest, "C %F\n", gsvn.zComment);
1089 }else{
1090 blob_append(&manifest, "C (no\\scomment)\n", 16);
1091 }
1092 blob_appendf(&manifest, "D %s\n", gsvn.zDate);
1093 db_bind_int(&getFiles, ":branch", branchId);
1094 while( db_step(&getFiles)==SQLITE_ROW ){
1095 const char *zFile = db_column_text(&getFiles, 0);
1096 const char *zUuid = db_column_text(&getFiles, 1);
1097 const char *zPerm = db_column_text(&getFiles, 2);
1098 if( zPerm ){
1099 blob_appendf(&manifest, "F %F %s %s\n", zFile, zUuid, zPerm);
1100 }else{
1101 blob_appendf(&manifest, "F %F %s\n", zFile, zUuid);
1102 }
1103 if( sameAsParent ){
1104 if( !pParentFile
1105 || fossil_strcmp(pParentFile->zName,zFile)!=0
1106 || fossil_strcmp(pParentFile->zUuid,zUuid)!=0
1107 || fossil_strcmp(pParentFile->zPerm,zPerm)!=0
1108 ){
1109 sameAsParent = 0;
1110 }else{
1111 pParentFile = manifest_file_next(pParentManifest, 0);
1112 }
1113 }
1114 }
1115 if( pParentFile ){
1116 sameAsParent = 0;
1117 }
1118 db_reset(&getFiles);
1119 if( !sameAsParent ){
1120 if( parentRid>0 ){
1121 char *zParentUuid = rid_to_uuid(parentRid);
1122 if( parentRid==mergeRid || mergeRid==0){
1123 char *zParentBranch =
1124 db_text(0, "SELECT tname FROM xbranches WHERE tid=%d",
1125 parentBranch
1126 );
1127 blob_appendf(&manifest, "P %s\n", zParentUuid);
1128 blob_appendf(&manifest, "T *branch * %F%F%F\n", gimport.zBranchPre,
1129 zBranch, gimport.zBranchSuf);
1130 blob_appendf(&manifest, "T *sym-%F%F%F *\n", gimport.zBranchPre,
1131 zBranch, gimport.zBranchSuf);
1132 if( gsvn.revFlag ){
1133 blob_appendf(&manifest, "T +sym-%Fr%d%F *\n", gimport.zTagPre,
1134 gsvn.rev, gimport.zTagSuf);
1135 }
1136 blob_appendf(&manifest, "T -sym-%F%F%F *\n", gimport.zBranchPre,
1137 zParentBranch, gimport.zBranchSuf);
1138 fossil_free(zParentBranch);
1139 }else{
1140 char *zMergeUuid = rid_to_uuid(mergeRid);
1141 blob_appendf(&manifest, "P %s %s\n", zParentUuid, zMergeUuid);
1142 if( gsvn.revFlag ){
1143 blob_appendf(&manifest, "T +sym-%F%d%F *\n", gsvn.zRevPre,
1144 gsvn.rev, gsvn.zRevSuf);
1145 }
1146 fossil_free(zMergeUuid);
1147 }
1148 fossil_free(zParentUuid);
1149 }else{
1150 blob_appendf(&manifest, "T *branch * %F%F%F\n",
1151 gimport.zBranchPre, zBranch, gimport.zBranchSuf);
1152 blob_appendf(&manifest, "T *sym-%F%F%F *\n", gimport.zBranchPre,
1153 zBranch, gimport.zBranchSuf);
1154 if( gsvn.revFlag ){
1155 blob_appendf(&manifest, "T +sym-%F%d%F *\n", gsvn.zRevPre, gsvn.rev,
1156 gsvn.zRevSuf);
1157 }
1158 }
1159 }else if( branchType==SVN_TAG ){
1160 char *zParentUuid = rid_to_uuid(parentRid);
1161 blob_reset(&manifest);
1162 blob_appendf(&manifest, "D %s\n", gsvn.zDate);
1163 blob_appendf(&manifest, "T +sym-%F%F%F %s\n", gimport.zTagPre, zBranch,
1164 gimport.zTagSuf, zParentUuid);
1165 fossil_free(zParentUuid);
1166 }
1167 }else{
1168 char *zParentUuid = rid_to_uuid(parentRid);
1169 blob_appendf(&manifest, "D %s\n", gsvn.zDate);
1170 if( branchType!=SVN_TAG ){
1171 blob_appendf(&manifest, "T +closed %s\n", zParentUuid);
1172 }else{
1173 blob_appendf(&manifest, "T -sym-%F%F%F %s\n", gimport.zBranchPre,
1174 zBranch, gimport.zBranchSuf, zParentUuid);
1175 }
1176 fossil_free(zParentUuid);
1177 }
1178 if( gsvn.zUser ){
1179 blob_appendf(&manifest, "U %F\n", gsvn.zUser);
1180 }else{
1181 const char *zUserOvrd = find_option("user-override",0,1);
1182 blob_appendf(&manifest, "U %F\n", zUserOvrd ? zUserOvrd : login_name());
1183 }
1184 md5sum_blob(&manifest, &mcksum);
1185 blob_appendf(&manifest, "Z %b\n", &mcksum);
1186 blob_reset(&mcksum);
1187 if( !sameAsParent ){
1188 int rid = content_put(&manifest);
1189 db_bind_int(&setRid, ":branch", branchId);
1190 db_bind_int(&setRid, ":rid", rid);
1191 db_step(&setRid);
1192 db_reset(&setRid);
1193 }else if( branchType==SVN_TAG ){
1194 content_put(&manifest);
1195 db_bind_int(&setRid, ":branch", branchId);
1196 db_bind_int(&setRid, ":rid", parentRid);
1197 db_step(&setRid);
1198 db_reset(&setRid);
1199 }else if( mergeRid==MAX_INT_32 ){
1200 content_put(&manifest);
1201 db_multi_exec("DELETE FROM xrevisions WHERE tbranch=%d AND trev=%d",
1202 branchId, gsvn.rev);
1203 }else{
1204 db_multi_exec("DELETE FROM xrevisions WHERE tbranch=%d AND trev=%d",
1205 branchId, gsvn.rev);
1206 }
1207 blob_reset(&manifest);
1208 manifest_destroy(pParentManifest);
1209 }
1210 db_reset(&getChanges);
1211 db_finalize(&setRid);
1212 }
1213
svn_get_varint(const char ** pz)1214 static u64 svn_get_varint(const char **pz){
1215 unsigned int v = 0;
1216 do{
1217 v = (v<<7) | ((*pz)[0]&0x7f);
1218 }while( (*pz)++[0]&0x80 );
1219 return v;
1220 }
1221
svn_apply_svndiff(Blob * pDiff,Blob * pSrc,Blob * pOut)1222 static void svn_apply_svndiff(Blob *pDiff, Blob *pSrc, Blob *pOut){
1223 const char *zDiff = blob_buffer(pDiff);
1224 char *zOut;
1225 if( blob_size(pDiff)<4 || memcmp(zDiff, "SVN", 4)!=0 ){
1226 fossil_fatal("Invalid svndiff0 format");
1227 }
1228 zDiff += 4;
1229 blob_zero(pOut);
1230 while( zDiff<(blob_buffer(pDiff)+blob_size(pDiff)) ){
1231 u64 lenOut, lenInst, lenData, lenOld;
1232 const char *zInst;
1233 const char *zData;
1234
1235 u64 offSrc = svn_get_varint(&zDiff);
1236 /*lenSrc =*/ svn_get_varint(&zDiff);
1237 lenOut = svn_get_varint(&zDiff);
1238 lenInst = svn_get_varint(&zDiff);
1239 lenData = svn_get_varint(&zDiff);
1240 zInst = zDiff;
1241 zData = zInst+lenInst;
1242 lenOld = blob_size(pOut);
1243 blob_resize(pOut, lenOut+lenOld);
1244 zOut = blob_buffer(pOut)+lenOld;
1245 while( zDiff<zInst+lenInst ){
1246 u64 lenCpy = (*zDiff)&0x3f;
1247 const char *zCpy;
1248 switch( (*zDiff)&0xC0 ){
1249 case 0x00: zCpy = blob_buffer(pSrc)+offSrc; break;
1250 case 0x40: zCpy = blob_buffer(pOut); break;
1251 case 0x80: zCpy = zData; break;
1252 default: fossil_fatal("Invalid svndiff0 instruction");
1253 }
1254 zDiff++;
1255 if( lenCpy==0 ){
1256 lenCpy = svn_get_varint(&zDiff);
1257 }
1258 if( zCpy!=zData ){
1259 zCpy += svn_get_varint(&zDiff);
1260 }else{
1261 zData += lenCpy;
1262 }
1263 while( lenCpy-- > 0 ){
1264 *zOut++ = *zCpy++;
1265 }
1266 }
1267 zDiff += lenData;
1268 }
1269 }
1270
1271 /*
1272 ** Extract the branch or tag that the given path is on. Return the branch ID.
1273 ** Return 0 if not a branch, tag, or trunk, or if ignored by --ignore-tree.
1274 */
svn_parse_path(char * zPath,char ** zFile,int * type)1275 static int svn_parse_path(char *zPath, char **zFile, int *type){
1276 char *zBranch = 0;
1277 int branchId = 0;
1278 if( gsvn.azIgnTree ){
1279 const char **pzIgnTree;
1280 unsigned nPath = strlen(zPath);
1281 for( pzIgnTree = gsvn.azIgnTree; *pzIgnTree; ++pzIgnTree ){
1282 const char *zIgn = *pzIgnTree;
1283 int nIgn = strlen(zIgn);
1284 if( strncmp(zPath, zIgn, nIgn) == 0
1285 && ( nPath == nIgn || (nPath > nIgn && zPath[nIgn] == '/')) ){
1286 return 0;
1287 }
1288 }
1289 }
1290 *type = SVN_UNKNOWN;
1291 *zFile = 0;
1292 if( gsvn.lenTrunk==0 ){
1293 zBranch = "trunk";
1294 *zFile = zPath;
1295 *type = SVN_TRUNK;
1296 }else
1297 if( strncmp(zPath, gsvn.zTrunk, gsvn.lenTrunk-1)==0 ){
1298 if( zPath[gsvn.lenTrunk-1]=='/' || zPath[gsvn.lenTrunk-1]==0 ){
1299 zBranch = "trunk";
1300 *zFile = zPath+gsvn.lenTrunk;
1301 *type = SVN_TRUNK;
1302 }else{
1303 zBranch = 0;
1304 *type = SVN_UNKNOWN;
1305 }
1306 }else{
1307 if( strncmp(zPath, gsvn.zBranches, gsvn.lenBranches)==0 ){
1308 *zFile = zBranch = zPath+gsvn.lenBranches;
1309 *type = SVN_BRANCH;
1310 }else
1311 if( strncmp(zPath, gsvn.zTags, gsvn.lenTags)==0 ){
1312 *zFile = zBranch = zPath+gsvn.lenTags;
1313 *type = SVN_TAG;
1314 }else{ /* Not a branch, tag or trunk */
1315 return 0;
1316 }
1317 while( **zFile && **zFile!='/' ){ (*zFile)++; }
1318 if( **zFile ){
1319 **zFile = '\0';
1320 (*zFile)++;
1321 }
1322 }
1323 if( *type!=SVN_UNKNOWN ){
1324 branchId = db_int(0,
1325 "SELECT tid FROM xbranches WHERE tname=%Q AND ttype=%d",
1326 zBranch, *type);
1327 if( branchId==0 ){
1328 db_multi_exec("INSERT INTO xbranches (tname, ttype) VALUES(%Q, %d)",
1329 zBranch, *type);
1330 branchId = db_last_insert_rowid();
1331 }
1332 }
1333 return branchId;
1334 }
1335
1336 /*
1337 ** Insert content of corresponding content blob into the database.
1338 ** If content is identified as a symbolic link, then trailing
1339 ** "link " characters are removed from content.
1340 **
1341 ** content is considered to be a symlink if zPerm contains at least
1342 ** one "l" character.
1343 */
svn_handle_symlinks(const char * perms,Blob * content)1344 static int svn_handle_symlinks(const char *perms, Blob *content){
1345 Blob link_blob;
1346 if( perms && strstr(perms, "l")!=0 ){
1347 if( blob_size(content)>5 ){
1348 /* Skip trailing 'link ' characters */
1349 blob_seek(content, 5, BLOB_SEEK_SET);
1350 blob_tail(content, &link_blob);
1351 return content_put(&link_blob);
1352 }else{
1353 fossil_fatal("Too short symbolic link path");
1354 }
1355 }else{
1356 return content_put(content);
1357 }
1358 }
1359
1360 /*
1361 ** Read the svn-dump format from pIn and insert the corresponding
1362 ** content into the database.
1363 */
svn_dump_import(FILE * pIn)1364 static void svn_dump_import(FILE *pIn){
1365 SvnRecord rec;
1366 int ver;
1367 char *zTemp;
1368 const char *zUuid;
1369 Stmt addFile;
1370 Stmt delPath;
1371 Stmt addRev;
1372 Stmt cpyPath;
1373 Stmt cpyRoot;
1374 Stmt revSrc;
1375
1376 /* version */
1377 if( svn_read_rec(pIn, &rec)
1378 && (zTemp = svn_find_header(rec, "SVN-fs-dump-format-version")) ){
1379 ver = atoi(zTemp);
1380 if( ver!=2 && ver!=3 ){
1381 fossil_fatal("Unknown svn-dump format version: %d", ver);
1382 }
1383 }else{
1384 fossil_fatal("Input is not an svn-dump!");
1385 }
1386 svn_free_rec(&rec);
1387 /* UUID */
1388 if( !svn_read_rec(pIn, &rec) || !(zUuid = svn_find_header(rec, "UUID")) ){
1389 /* Removed the following line since UUID is not actually used
1390 fossil_fatal("Missing UUID!"); */
1391 }
1392 svn_free_rec(&rec);
1393
1394 /* content */
1395 db_prepare(&addFile,
1396 "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)"
1397 " VALUES(:path, :branch, (SELECT uuid FROM blob WHERE rid=:rid), :perm)"
1398 );
1399 db_prepare(&delPath,
1400 "DELETE FROM xfiles"
1401 " WHERE (tpath=:path OR (tpath>:path||'/' AND tpath<:path||'0'))"
1402 " AND tbranch=:branch"
1403 );
1404 db_prepare(&addRev,
1405 "INSERT OR IGNORE INTO xrevisions (trev, tbranch) VALUES(:rev, :branch)"
1406 );
1407 db_prepare(&cpyPath,
1408 "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)"
1409 " SELECT :path||:sep||substr(filename,"
1410 " length(:srcpath)+2), :branch, uuid, perm"
1411 " FROM xfoci"
1412 " WHERE checkinID=:rid"
1413 " AND filename>:srcpath||'/'"
1414 " AND filename<:srcpath||'0'"
1415 );
1416 db_prepare(&cpyRoot,
1417 "INSERT INTO xfiles (tpath, tbranch, tuuid, tperm)"
1418 " SELECT :path||:sep||filename, :branch, uuid, perm"
1419 " FROM xfoci"
1420 " WHERE checkinID=:rid"
1421 );
1422 db_prepare(&revSrc,
1423 "UPDATE xrevisions SET tparent=:parent"
1424 " WHERE trev=:rev AND tbranch=:branch AND tparent<:parent"
1425 );
1426 gsvn.rev = -1;
1427 bag_init(&gsvn.newBranches);
1428 while( svn_read_rec(pIn, &rec) ){
1429 if( (zTemp = svn_find_header(rec, "Revision-number")) ){ /* revision node */
1430 /* finish previous revision */
1431 char *zDate = NULL;
1432 if( gsvn.rev>=0 ){
1433 svn_finish_revision();
1434 fossil_free(gsvn.zUser);
1435 fossil_free(gsvn.zComment);
1436 fossil_free(gsvn.zDate);
1437 bag_clear(&gsvn.newBranches);
1438 }
1439 /* start new revision */
1440 gsvn.rev = atoi(zTemp);
1441 gsvn.zUser = mprintf("%s", svn_find_prop(rec, "svn:author"));
1442 gsvn.zComment = mprintf("%s", svn_find_prop(rec, "svn:log"));
1443 zDate = svn_find_prop(rec, "svn:date");
1444 if( zDate ){
1445 gsvn.zDate = date_in_standard_format(zDate);
1446 }else{
1447 gsvn.zDate = date_in_standard_format("now");
1448 }
1449 db_bind_int(&addRev, ":rev", gsvn.rev);
1450 fossil_print("\rImporting SVN revision: %d", gsvn.rev);
1451 }else
1452 if( (zTemp = svn_find_header(rec, "Node-path")) ){ /* file/dir node */
1453 char *zFile;
1454 int branchType;
1455 int branchId = svn_parse_path(zTemp, &zFile, &branchType);
1456 char *zAction = svn_find_header(rec, "Node-action");
1457 char *zKind = svn_find_header(rec, "Node-kind");
1458 char *zPerm = svn_find_prop(rec, "svn:executable") ? "x" : 0;
1459 int deltaFlag = 0;
1460 int srcRev = 0;
1461
1462 if ( zPerm==0 ){
1463 zPerm = svn_find_prop(rec, "svn:special") ? "l" : 0;
1464 }
1465 if( branchId==0 ){
1466 svn_free_rec(&rec);
1467 continue;
1468 }
1469 if( (zTemp = svn_find_header(rec, "Text-delta")) ){
1470 deltaFlag = strncmp(zTemp, "true", 4)==0;
1471 }
1472 if( strncmp(zAction, "delete", 6)==0
1473 || strncmp(zAction, "replace", 7)==0 )
1474 {
1475 db_bind_int(&addRev, ":branch", branchId);
1476 db_step(&addRev);
1477 db_reset(&addRev);
1478 if( zFile[0]!=0 ){
1479 db_bind_text(&delPath, ":path", zFile);
1480 db_bind_int(&delPath, ":branch", branchId);
1481 db_step(&delPath);
1482 db_reset(&delPath);
1483 }else{
1484 db_multi_exec("DELETE FROM xfiles WHERE tbranch=%d", branchId);
1485 db_bind_int(&revSrc, ":parent", MAX_INT_32);
1486 db_bind_int(&revSrc, ":rev", gsvn.rev);
1487 db_bind_int(&revSrc, ":branch", branchId);
1488 db_step(&revSrc);
1489 db_reset(&revSrc);
1490 }
1491 } /* no 'else' here since 'replace' does both a 'delete' and an 'add' */
1492 if( strncmp(zAction, "add", 3)==0
1493 || strncmp(zAction, "replace", 7)==0 )
1494 {
1495 char *zSrcPath = svn_find_header(rec, "Node-copyfrom-path");
1496 char *zSrcFile;
1497 int srcRid = 0;
1498 if( zSrcPath ){
1499 int srcBranch;
1500 zTemp = svn_find_header(rec, "Node-copyfrom-rev");
1501 if( zTemp ){
1502 srcRev = atoi(zTemp);
1503 }else{
1504 fossil_fatal("Missing copyfrom-rev");
1505 }
1506 srcBranch = svn_parse_path(zSrcPath, &zSrcFile, &branchType);
1507 if( srcBranch==0 ){
1508 fossil_fatal("Copy from path outside the import paths");
1509 }
1510 srcRid = db_int(0, "SELECT trid, max(trev) FROM xrevisions"
1511 " WHERE trev<=%d AND tbranch=%d",
1512 srcRev, srcBranch);
1513 if( srcRid>0 && srcBranch!=branchId ){
1514 db_bind_int(&addRev, ":branch", branchId);
1515 db_step(&addRev);
1516 db_reset(&addRev);
1517 db_bind_int(&revSrc, ":parent", srcRid);
1518 db_bind_int(&revSrc, ":rev", gsvn.rev);
1519 db_bind_int(&revSrc, ":branch", branchId);
1520 db_step(&revSrc);
1521 db_reset(&revSrc);
1522 }
1523 }
1524 if( zKind==0 ){
1525 fossil_fatal("Missing Node-kind");
1526 }else if( strncmp(zKind, "dir", 3)==0 ){
1527 if( zSrcPath ){
1528 if( srcRid>0 ){
1529 if( zSrcFile[0]==0 ){
1530 db_bind_text(&cpyRoot, ":path", zFile);
1531 if( zFile[0]!=0 ){
1532 db_bind_text(&cpyRoot, ":sep", "/");
1533 }else{
1534 db_bind_text(&cpyRoot, ":sep", "");
1535 }
1536 db_bind_int(&cpyRoot, ":branch", branchId);
1537 db_bind_int(&cpyRoot, ":rid", srcRid);
1538 db_step(&cpyRoot);
1539 db_reset(&cpyRoot);
1540 }else{
1541 db_bind_text(&cpyPath, ":path", zFile);
1542 if( zFile[0]!=0 ){
1543 db_bind_text(&cpyPath, ":sep", "/");
1544 }else{
1545 db_bind_text(&cpyPath, ":sep", "");
1546 }
1547 db_bind_int(&cpyPath, ":branch", branchId);
1548 db_bind_text(&cpyPath, ":srcpath", zSrcFile);
1549 db_bind_int(&cpyPath, ":rid", srcRid);
1550 db_step(&cpyPath);
1551 db_reset(&cpyPath);
1552 }
1553 }
1554 }
1555 if( zFile[0]==0 ){
1556 bag_insert(&gsvn.newBranches, branchId);
1557 }
1558 }else{
1559 int rid = 0;
1560 if( zSrcPath ){
1561 rid = db_int(0, "SELECT rid FROM blob WHERE uuid=("
1562 " SELECT uuid FROM xfoci"
1563 " WHERE checkinID=%d AND filename=%Q"
1564 ")",
1565 srcRid, zSrcFile);
1566 }
1567 if( deltaFlag ){
1568 Blob deltaSrc;
1569 Blob target;
1570 if( rid!=0 ){
1571 content_get(rid, &deltaSrc);
1572 }else{
1573 blob_zero(&deltaSrc);
1574 }
1575 svn_apply_svndiff(&rec.content, &deltaSrc, &target);
1576 rid = svn_handle_symlinks(zPerm, &target);
1577 }else if( rec.contentFlag ){
1578 rid = svn_handle_symlinks(zPerm, &rec.content);
1579 }else if( zSrcPath ){
1580 if ( zPerm==0 ){
1581 zPerm = db_text(0, "SELECT tperm FROM xfiles"
1582 " WHERE tpath=%Q AND tbranch=%d"
1583 "", zSrcPath, branchId);
1584 }
1585 }
1586 db_bind_text(&addFile, ":path", zFile);
1587 db_bind_int(&addFile, ":branch", branchId);
1588 db_bind_int(&addFile, ":rid", rid);
1589 db_bind_text(&addFile, ":perm", zPerm);
1590 db_step(&addFile);
1591 db_reset(&addFile);
1592 db_bind_int(&addRev, ":branch", branchId);
1593 db_step(&addRev);
1594 db_reset(&addRev);
1595 }
1596 }else
1597 if( strncmp(zAction, "change", 6)==0 ){
1598 int rid = 0;
1599 if( zKind==0 ){
1600 fossil_fatal("Missing Node-kind");
1601 }
1602 if( rec.contentFlag && strncmp(zKind, "dir", 3)!=0 ){
1603 if ( zPerm==0 ){
1604 zPerm = db_text(0, "SELECT tperm FROM xfiles"
1605 " WHERE tpath=%Q AND tbranch=%d"
1606 "", zFile, branchId);
1607 }
1608
1609 if( deltaFlag ){
1610 Blob deltaSrc;
1611 Blob target;
1612 rid = db_int(0, "SELECT rid FROM blob WHERE uuid=("
1613 " SELECT tuuid FROM xfiles"
1614 " WHERE tpath=%Q AND tbranch=%d"
1615 ")", zFile, branchId);
1616 content_get(rid, &deltaSrc);
1617 svn_apply_svndiff(&rec.content, &deltaSrc, &target);
1618 rid = svn_handle_symlinks(zPerm, &target);
1619 }else{
1620 rid = svn_handle_symlinks(zPerm, &rec.content);
1621 }
1622 db_bind_text(&addFile, ":path", zFile);
1623 db_bind_int(&addFile, ":branch", branchId);
1624 db_bind_int(&addFile, ":rid", rid);
1625 db_bind_text(&addFile, ":perm", zPerm);
1626 db_step(&addFile);
1627 db_reset(&addFile);
1628 db_bind_int(&addRev, ":branch", branchId);
1629 db_step(&addRev);
1630 db_reset(&addRev);
1631 }
1632 }else
1633 if( strncmp(zAction, "delete", 6)!=0 ){ /* already did this one above */
1634 fossil_fatal("Unknown Node-action");
1635 }
1636 }else{
1637 fossil_fatal("Unknown record type");
1638 }
1639 svn_free_rec(&rec);
1640 }
1641 svn_finish_revision();
1642 fossil_free(gsvn.zUser);
1643 fossil_free(gsvn.zComment);
1644 fossil_free(gsvn.zDate);
1645 db_finalize(&addFile);
1646 db_finalize(&delPath);
1647 db_finalize(&addRev);
1648 db_finalize(&cpyPath);
1649 db_finalize(&cpyRoot);
1650 db_finalize(&revSrc);
1651 fossil_print(" Done!\n");
1652 }
1653
1654 /*
1655 ** COMMAND: import*
1656 **
1657 ** Usage: %fossil import ?--git? ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?
1658 ** or: %fossil import --svn ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?
1659 **
1660 ** Read interchange format generated by another VCS and use it to
1661 ** construct a new Fossil repository named by the NEW-REPOSITORY
1662 ** argument. If no input file is supplied the interchange format
1663 ** data is read from standard input.
1664 **
1665 ** The following formats are currently understood by this command
1666 **
1667 ** --git Import from the git-fast-export file format (default)
1668 ** Options:
1669 ** --import-marks FILE Restore marks table from FILE
1670 ** --export-marks FILE Save marks table to FILE
1671 ** --rename-master NAME Renames the master branch to NAME
1672 ** --use-author Uses author as the committer
1673 ** --attribute "EMAIL USER" Attribute commits to USER
1674 ** instead of Git committer EMAIL address
1675 **
1676 ** --svn Import from the svnadmin-dump file format. The default
1677 ** behaviour (unless overridden by --flat) is to treat 3
1678 ** folders in the SVN root as special, following the
1679 ** common layout of SVN repositories. These are (by
1680 ** default) trunk/, branches/ and tags/. The SVN --deltas
1681 ** format is supported but not required.
1682 ** Options:
1683 ** --trunk FOLDER Name of trunk folder
1684 ** --branches FOLDER Name of branches folder
1685 ** --tags FOLDER Name of tags folder
1686 ** --base PATH Path to project root in repository
1687 ** --flat The whole dump is a single branch
1688 ** --rev-tags Tag each revision, implied by -i
1689 ** --no-rev-tags Disables tagging effect of -i
1690 ** --rename-rev PAT Rev tag names, default "svn-rev-%"
1691 ** --ignore-tree DIR Ignores subtree rooted at DIR
1692 **
1693 ** Common Options:
1694 ** -i|--incremental allow importing into an existing repository
1695 ** -f|--force overwrite repository if already exists
1696 ** -q|--quiet omit progress output
1697 ** --no-rebuild skip the "rebuilding metadata" step
1698 ** --no-vacuum skip the final VACUUM of the database file
1699 ** --rename-trunk NAME use NAME as name of imported trunk branch
1700 ** --rename-branch PAT rename all branch names using PAT pattern
1701 ** --rename-tag PAT rename all tag names using PAT pattern
1702 ** -A|--admin-user NAME use NAME for the admin user
1703 **
1704 ** The --incremental option allows an existing repository to be extended
1705 ** with new content. The --rename-* options may be useful to avoid name
1706 ** conflicts when using the --incremental option. The --admin-user
1707 ** option is ignored if --incremental is specified.
1708 **
1709 ** The argument to --rename-* contains one "%" character to be replaced
1710 ** with the original name. For example, "--rename-tag svn-%-tag" renames
1711 ** the tag called "release" to "svn-release-tag".
1712 **
1713 ** --ignore-tree is useful for importing Subversion repositories which
1714 ** move branches to subdirectories of "branches/deleted" instead of
1715 ** deleting them. It can be supplied multiple times if necessary.
1716 **
1717 ** The --attribute option takes a quoted string argument comprised of a
1718 ** Git committer email and the username to be attributed to corresponding
1719 ** check-ins in the Fossil repository. This option can be repeated. For
1720 ** example, --attribute "drh@sqlite.org drh" --attribute "xyz@abc.net X"
1721 **
1722 ** See also: export
1723 */
import_cmd(void)1724 void import_cmd(void){
1725 char *zPassword;
1726 FILE *pIn;
1727 Stmt q;
1728 int forceFlag = find_option("force", "f", 0)!=0;
1729 int svnFlag = find_option("svn", 0, 0)!=0;
1730 int gitFlag = find_option("git", 0, 0)!=0;
1731 int omitRebuild = find_option("no-rebuild",0,0)!=0;
1732 int omitVacuum = find_option("no-vacuum",0,0)!=0;
1733 const char *zDefaultUser = find_option("admin-user","A",1);
1734
1735 /* Options common to all input formats */
1736 int incrFlag = find_option("incremental", "i", 0)!=0;
1737
1738 /* Options for --svn only */
1739 const char *zBase = "";
1740 int flatFlag = 0;
1741
1742 /* Options for --git only */
1743 const char *markfile_in = 0;
1744 const char *markfile_out = 0;
1745
1746 /* Interpret --rename-* options. Use a table to avoid code duplication. */
1747 const struct {
1748 const char *zOpt, **varPre, *zDefaultPre, **varSuf, *zDefaultSuf;
1749 int format; /* 1=git, 2=svn, 3=any */
1750 } renOpts[] = {
1751 {"rename-branch", &gimport.zBranchPre, "", &gimport.zBranchSuf, "", 3},
1752 {"rename-tag" , &gimport.zTagPre , "", &gimport.zTagSuf , "", 3},
1753 {"rename-rev" , &gsvn.zRevPre, "svn-rev-", &gsvn.zRevSuf , "", 2},
1754 }, *renOpt = renOpts;
1755 int i;
1756 for( i = 0; i < count(renOpts); ++i, ++renOpt ){
1757 if( 1 << svnFlag & renOpt->format ){
1758 const char *zArgument = find_option(renOpt->zOpt, 0, 1);
1759 if( zArgument ){
1760 const char *sep = strchr(zArgument, '%');
1761 if( !sep ){
1762 fossil_fatal("missing '%%' in argument to --%s", renOpt->zOpt);
1763 }else if( strchr(sep + 1, '%') ){
1764 fossil_fatal("multiple '%%' in argument to --%s", renOpt->zOpt);
1765 }
1766 *renOpt->varPre = fossil_malloc(sep - zArgument + 1);
1767 memcpy((char *)*renOpt->varPre, zArgument, sep - zArgument);
1768 ((char *)*renOpt->varPre)[sep - zArgument] = 0;
1769 *renOpt->varSuf = sep + 1;
1770 }else{
1771 *renOpt->varPre = renOpt->zDefaultPre;
1772 *renOpt->varSuf = renOpt->zDefaultSuf;
1773 }
1774 }
1775 }
1776 if( !(gimport.zTrunkName = find_option("rename-trunk", 0, 1)) ){
1777 gimport.zTrunkName = "trunk";
1778 }
1779
1780 if( svnFlag ){
1781 /* Get --svn related options here, so verify_all_options() fails when
1782 * svn-only options are specified with --git
1783 */
1784 const char *zIgnTree;
1785 unsigned nIgnTree = 0;
1786 while( (zIgnTree = find_option("ignore-tree", 0, 1)) ){
1787 if ( *zIgnTree ){
1788 gsvn.azIgnTree = fossil_realloc((void *)gsvn.azIgnTree,
1789 sizeof(*gsvn.azIgnTree) * (nIgnTree + 2));
1790 gsvn.azIgnTree[nIgnTree++] = zIgnTree;
1791 gsvn.azIgnTree[nIgnTree] = 0;
1792 }
1793 }
1794 zBase = find_option("base", 0, 1);
1795 flatFlag = find_option("flat", 0, 0)!=0;
1796 gsvn.zTrunk = find_option("trunk", 0, 1);
1797 gsvn.zBranches = find_option("branches", 0, 1);
1798 gsvn.zTags = find_option("tags", 0, 1);
1799 gsvn.revFlag = find_option("rev-tags", 0, 0)
1800 || (incrFlag && !find_option("no-rev-tags", 0, 0));
1801 }else if( gitFlag ){
1802 const char *zGitUser;
1803 markfile_in = find_option("import-marks", 0, 1);
1804 markfile_out = find_option("export-marks", 0, 1);
1805 if( !(ggit.zMasterName = find_option("rename-master", 0, 1)) ){
1806 ggit.zMasterName = "master";
1807 }
1808 ggit.authorFlag = find_option("use-author", 0, 0)!=0;
1809 /*
1810 ** Extract --attribute 'emailaddr username' args that will populate
1811 ** new 'fx_' table to later match username for check-in attribution.
1812 */
1813 zGitUser = find_option("attribute", 0, 1);
1814 while( zGitUser != 0 ){
1815 char *currGitUser;
1816 ggit.gitUserInfo = fossil_realloc(ggit.gitUserInfo, ++ggit.nGitAttr
1817 * sizeof(ggit.gitUserInfo[0]));
1818 currGitUser = fossil_strdup(zGitUser);
1819 ggit.gitUserInfo[ggit.nGitAttr-1].zEmail = next_token(&currGitUser);
1820 ggit.gitUserInfo[ggit.nGitAttr-1].zUser = rest_of_line(&currGitUser);
1821 zGitUser = find_option("attribute", 0, 1);
1822 }
1823 }
1824 verify_all_options();
1825
1826 if( g.argc!=3 && g.argc!=4 ){
1827 usage("--git|--svn ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?");
1828 }
1829 if( g.argc==4 ){
1830 pIn = fossil_fopen(g.argv[3], "rb");
1831 if( pIn==0 ) fossil_fatal("cannot open input file \"%s\"", g.argv[3]);
1832 }else{
1833 pIn = stdin;
1834 fossil_binary_mode(pIn);
1835 }
1836 if( !incrFlag ){
1837 if( forceFlag ) file_delete(g.argv[2]);
1838 db_create_repository(g.argv[2]);
1839 }
1840 db_open_repository(g.argv[2]);
1841 db_open_config(0, 0);
1842 db_unprotect(PROTECT_ALL);
1843
1844 db_begin_transaction();
1845 if( !incrFlag ){
1846 db_initial_setup(0, 0, zDefaultUser);
1847 db_set("main-branch", gimport.zTrunkName, 0);
1848 }
1849 content_rcvid_init(svnFlag ? "svn-import" : "git-import");
1850
1851 if( svnFlag ){
1852 db_multi_exec(
1853 "CREATE TEMP TABLE xrevisions("
1854 " trev INTEGER, tbranch INT, trid INT, tparent INT DEFAULT 0,"
1855 " UNIQUE(tbranch, trev)"
1856 ");"
1857 "CREATE INDEX temp.i_xrevisions ON xrevisions(trid);"
1858 "CREATE TEMP TABLE xfiles("
1859 " tpath TEXT, tbranch INT, tuuid TEXT, tperm TEXT,"
1860 " UNIQUE (tbranch, tpath) ON CONFLICT REPLACE"
1861 ");"
1862 "CREATE TEMP TABLE xbranches("
1863 " tid INTEGER PRIMARY KEY, tname TEXT, ttype INT,"
1864 " UNIQUE(tname, ttype)"
1865 ");"
1866 "CREATE VIRTUAL TABLE temp.xfoci USING files_of_checkin;"
1867 );
1868 if( zBase==0 ){ zBase = ""; }
1869 if( strlen(zBase)>0 ){
1870 if( zBase[strlen(zBase)-1]!='/' ){
1871 zBase = mprintf("%s/", zBase);
1872 }
1873 }
1874 if( flatFlag ){
1875 gsvn.zTrunk = zBase;
1876 gsvn.zBranches = 0;
1877 gsvn.zTags = 0;
1878 gsvn.lenTrunk = strlen(zBase);
1879 gsvn.lenBranches = 0;
1880 gsvn.lenTags = 0;
1881 }else{
1882 if( gsvn.zTrunk==0 ){ gsvn.zTrunk = "trunk/"; }
1883 if( gsvn.zBranches==0 ){ gsvn.zBranches = "branches/"; }
1884 if( gsvn.zTags==0 ){ gsvn.zTags = "tags/"; }
1885 gsvn.zTrunk = mprintf("%s%s", zBase, gsvn.zTrunk);
1886 gsvn.zBranches = mprintf("%s%s", zBase, gsvn.zBranches);
1887 gsvn.zTags = mprintf("%s%s", zBase, gsvn.zTags);
1888 gsvn.lenTrunk = strlen(gsvn.zTrunk);
1889 gsvn.lenBranches = strlen(gsvn.zBranches);
1890 gsvn.lenTags = strlen(gsvn.zTags);
1891 if( gsvn.zTrunk[gsvn.lenTrunk-1]!='/' ){
1892 gsvn.zTrunk = mprintf("%s/", gsvn.zTrunk);
1893 gsvn.lenTrunk++;
1894 }
1895 if( gsvn.zBranches[gsvn.lenBranches-1]!='/' ){
1896 gsvn.zBranches = mprintf("%s/", gsvn.zBranches);
1897 gsvn.lenBranches++;
1898 }
1899 if( gsvn.zTags[gsvn.lenTags-1]!='/' ){
1900 gsvn.zTags = mprintf("%s/", gsvn.zTags);
1901 gsvn.lenTags++;
1902 }
1903 }
1904 svn_dump_import(pIn);
1905 }else{
1906 Bag blobs, vers;
1907 bag_init(&blobs);
1908 bag_init(&vers);
1909 /* The following temp-tables are used to hold information needed for
1910 ** the import.
1911 **
1912 ** The XMARK table provides a mapping from fast-import "marks" and symbols
1913 ** into artifact hashes.
1914 **
1915 ** Given any valid fast-import symbol, the corresponding fossil rid and
1916 ** hash can found by searching against the xmark.tname field.
1917 **
1918 ** The XBRANCH table maps commit marks and symbols into the branch those
1919 ** commits belong to. If xbranch.tname is a fast-import symbol for a
1920 ** check-in then xbranch.brnm is the branch that check-in is part of.
1921 **
1922 ** The XTAG table records information about tags that need to be applied
1923 ** to various branches after the import finishes. The xtag.tcontent field
1924 ** contains the text of an artifact that will add a tag to a check-in.
1925 ** The git-fast-export file format might specify the same tag multiple
1926 ** times but only the last tag should be used. And we do not know which
1927 ** occurrence of the tag is the last until the import finishes.
1928 */
1929 db_multi_exec(
1930 "CREATE TEMP TABLE xmark(tname TEXT UNIQUE, trid INT, tuuid TEXT);"
1931 "CREATE INDEX temp.i_xmark ON xmark(trid);"
1932 "CREATE TEMP TABLE xbranch(tname TEXT UNIQUE, brnm TEXT);"
1933 "CREATE TEMP TABLE xtag(tname TEXT UNIQUE, tcontent TEXT);"
1934 );
1935
1936 if( markfile_in ){
1937 FILE *f = fossil_fopen(markfile_in, "r");
1938 if( !f ){
1939 fossil_fatal("cannot open %s for reading", markfile_in);
1940 }
1941 if( import_marks(f, &blobs, NULL, NULL)<0 ){
1942 fossil_fatal("error importing marks from file: %s", markfile_in);
1943 }
1944 fclose(f);
1945 }
1946
1947 manifest_crosslink_begin();
1948 /*
1949 ** The following 'fx_' table is used to hold information needed for
1950 ** importing and exporting to attribute Fossil check-ins or Git commits
1951 ** to either a desired username or full contact information string.
1952 */
1953 if(ggit.nGitAttr > 0) {
1954 int idx;
1955 db_unprotect(PROTECT_ALL);
1956 db_multi_exec(
1957 "CREATE TABLE fx_git(user TEXT, email TEXT UNIQUE);"
1958 );
1959 for(idx = 0; idx < ggit.nGitAttr; ++idx ){
1960 db_multi_exec(
1961 "INSERT OR IGNORE INTO fx_git(user, email) VALUES(%Q, %Q)",
1962 ggit.gitUserInfo[idx].zUser, ggit.gitUserInfo[idx].zEmail
1963 );
1964 }
1965 db_protect_pop();
1966 }
1967 git_fast_import(pIn);
1968 db_prepare(&q, "SELECT tcontent FROM xtag");
1969 while( db_step(&q)==SQLITE_ROW ){
1970 Blob record;
1971 db_ephemeral_blob(&q, 0, &record);
1972 fast_insert_content(&record, 0, 0, 0, 1);
1973 import_reset(0);
1974 }
1975 db_finalize(&q);
1976 if( markfile_out ){
1977 int rid;
1978 Stmt q_marks;
1979 FILE *f;
1980 db_prepare(&q_marks, "SELECT DISTINCT trid FROM xmark");
1981 while( db_step(&q_marks)==SQLITE_ROW ){
1982 rid = db_column_int(&q_marks, 0);
1983 if( db_int(0, "SELECT count(objid) FROM event"
1984 " WHERE objid=%d AND type='ci'", rid)==0 ){
1985 /* Blob marks exported by git aren't saved between runs, so they need
1986 ** to be left free for git to re-use in the future.
1987 */
1988 }else{
1989 bag_insert(&vers, rid);
1990 }
1991 }
1992 db_finalize(&q_marks);
1993 f = fossil_fopen(markfile_out, "w");
1994 if( !f ){
1995 fossil_fatal("cannot open %s for writing", markfile_out);
1996 }
1997 export_marks(f, &blobs, &vers);
1998 fclose(f);
1999 bag_clear(&blobs);
2000 bag_clear(&vers);
2001 }
2002 manifest_crosslink_end(MC_NONE);
2003 }
2004
2005 verify_cancel();
2006 db_end_transaction(0);
2007 fossil_print(" \r");
2008 if( omitRebuild ){
2009 omitVacuum = 1;
2010 }else{
2011 db_begin_transaction();
2012 fossil_print("Rebuilding repository meta-data...\n");
2013 rebuild_db(0, 1, !incrFlag);
2014 verify_cancel();
2015 db_end_transaction(0);
2016 }
2017 if( !omitVacuum ){
2018 fossil_print("Vacuuming..."); fflush(stdout);
2019 db_multi_exec("VACUUM");
2020 }
2021 fossil_print(" ok\n");
2022 if( !incrFlag ){
2023 fossil_print("project-id: %s\n", db_get("project-code", 0));
2024 fossil_print("server-id: %s\n", db_get("server-code", 0));
2025 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
2026 fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword);
2027 }
2028 }
2029