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