Fossil SCM

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

Keyboard Shortcuts

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