Fossil SCM

fossil-scm / src / export.c
Blame History Raw 1907 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 export the content of a Fossil
18
** repository in the git-fast-import format.
19
*/
20
#include "config.h"
21
#include "export.h"
22
#include <assert.h>
23
24
/*
25
** State information common to all export types.
26
*/
27
static struct {
28
const char *zTrunkName; /* Name of trunk branch */
29
} gexport;
30
31
#if INTERFACE
32
/*
33
** Each line in a git-fast-export "mark" file is an instance of
34
** this object.
35
*/
36
struct mark_t {
37
char *name; /* Name of the mark. Also starts with ":" */
38
int rid; /* Corresponding object in the BLOB table */
39
char uuid[65]; /* The GIT hash name for this object */
40
};
41
#endif
42
43
/*
44
** Output a "committer" record for the given user.
45
** NOTE: the given user name may be an email itself.
46
*/
47
static void print_person(const char *zUser){
48
static Stmt q;
49
const char *zContact;
50
char *zName;
51
char *zEmail;
52
int i, j;
53
int isBracketed, atEmailFirst, atEmailLast;
54
55
if( zUser==0 ){
56
printf(" <unknown>");
57
return;
58
}
59
db_static_prepare(&q, "SELECT info FROM user WHERE login=:user");
60
db_bind_text(&q, ":user", zUser);
61
if( db_step(&q)!=SQLITE_ROW ){
62
db_reset(&q);
63
zName = fossil_strdup(zUser);
64
for(i=j=0; zName[i]; i++){
65
if( zName[i]!='<' && zName[i]!='>' && zName[i]!='"' ){
66
zName[j++] = zName[i];
67
}
68
}
69
zName[j] = 0;
70
printf(" %s <%s>", zName, zName);
71
free(zName);
72
return;
73
}
74
75
/*
76
** We have contact information.
77
** It may or may not contain an email address.
78
**
79
** ASSUME:
80
** - General case:"Name Unicoded" <[email protected]> other info
81
** - If contact information contains more than an email address,
82
** then the email address is enclosed between <>
83
** - When only email address is specified, then it's stored verbatim
84
** - When name part is absent or all-blanks, use zUser instead
85
*/
86
zName = NULL;
87
zEmail = NULL;
88
zContact = db_column_text(&q, 0);
89
atEmailFirst = -1;
90
atEmailLast = -1;
91
isBracketed = 0;
92
for(i=0; zContact[i] && zContact[i]!='@'; i++){
93
if( zContact[i]=='<' ){
94
isBracketed = 1;
95
atEmailFirst = i+1;
96
}
97
else if( zContact[i]=='>' ){
98
isBracketed = 0;
99
atEmailFirst = i+1;
100
}
101
else if( zContact[i]==' ' && !isBracketed ){
102
atEmailFirst = i+1;
103
}
104
}
105
if( zContact[i]==0 ){
106
/* No email address found. Take as user info if not empty */
107
zName = fossil_strdup(zContact[0] ? zContact : zUser);
108
for(i=j=0; zName[i]; i++){
109
if( zName[i]!='<' && zName[i]!='>' && zName[i]!='"' ){
110
zName[j++] = zName[i];
111
}
112
}
113
zName[j] = 0;
114
115
printf(" %s <%s>", zName, zName);
116
free(zName);
117
db_reset(&q);
118
return;
119
}
120
for(j=i+1; zContact[j] && zContact[j]!=' '; j++){
121
if( zContact[j]=='>' )
122
atEmailLast = j-1;
123
}
124
if ( atEmailLast==-1 ) atEmailLast = j-1;
125
if ( atEmailFirst==-1 ) atEmailFirst = 0; /* Found only email */
126
127
/*
128
** Found beginning and end of email address.
129
** Extract the address (trimmed and sanitized).
130
*/
131
for(j=atEmailFirst; zContact[j] && zContact[j]==' '; j++){}
132
zEmail = mprintf("%.*s", atEmailLast-j+1, &zContact[j]);
133
134
for(i=j=0; zEmail[i]; i++){
135
if( zEmail[i]!='<' && zEmail[i]!='>' ){
136
zEmail[j++] = zEmail[i];
137
}
138
}
139
zEmail[j] = 0;
140
141
/*
142
** When bracketed email, extract the string _before_
143
** email as user name (may be enquoted).
144
** If missing or all-blank name, use zUser.
145
*/
146
if( isBracketed && (atEmailFirst-1) > 0){
147
for(i=atEmailFirst-2; i>=0 && zContact[i] && zContact[i]==' '; i--){}
148
if( i>=0 ){
149
for(j=0; j<i && zContact[j] && zContact[j]==' '; j++){}
150
zName = mprintf("%.*s", i-j+1, &zContact[j]);
151
}
152
}
153
154
if( zName==NULL ) zName = fossil_strdup(zUser);
155
for(i=j=0; zName[i]; i++){
156
if( zName[i]!='<' && zName[i]!='>' && zName[i]!='"' ){
157
zName[j++] = zName[i];
158
}
159
}
160
zName[j] = 0;
161
162
printf(" %s <%s>", zName, zEmail);
163
free(zName);
164
free(zEmail);
165
db_reset(&q);
166
}
167
168
#define REFREPLACEMENT '_'
169
170
/*
171
** Output a sanitized git named reference.
172
** https://git-scm.com/docs/git-check-ref-format
173
** This implementation assumes we are only printing
174
** the branch or tag part of the reference.
175
*/
176
static void print_ref(const char *zRef){
177
char *zEncoded = fossil_strdup(zRef);
178
int i, w;
179
if (zEncoded[0]=='@' && zEncoded[1]=='\0'){
180
putchar(REFREPLACEMENT);
181
return;
182
}
183
for(i=0, w=0; zEncoded[i]; i++, w++){
184
if( i!=0 ){ /* Two letter tests */
185
if( (zEncoded[i-1]=='.' && zEncoded[i]=='.') ||
186
(zEncoded[i-1]=='@' && zEncoded[i]=='{') ){
187
zEncoded[w]=zEncoded[w-1]=REFREPLACEMENT;
188
continue;
189
}
190
if( zEncoded[i-1]=='/' && zEncoded[i]=='/' ){
191
w--; /* Normalise to a single / by rolling back w */
192
continue;
193
}
194
}
195
/* No control characters */
196
if( (unsigned)zEncoded[i]<0x20 || zEncoded[i]==0x7f ){
197
zEncoded[w]=REFREPLACEMENT;
198
continue;
199
}
200
switch( zEncoded[i] ){
201
case ' ':
202
case '^':
203
case ':':
204
case '?':
205
case '*':
206
case '[':
207
case '\\':
208
zEncoded[w]=REFREPLACEMENT;
209
break;
210
}
211
}
212
/* Cannot begin with a . or / */
213
if( zEncoded[0]=='.' || zEncoded[0] == '/' ) zEncoded[0]=REFREPLACEMENT;
214
if( i>0 ){
215
i--; w--;
216
/* Or end with a . or / */
217
if( zEncoded[i]=='.' || zEncoded[i] == '/' ) zEncoded[w]=REFREPLACEMENT;
218
/* Cannot end with .lock */
219
if ( i>4 && strcmp((zEncoded+i)-5, ".lock")==0 )
220
memset((zEncoded+w)-5, REFREPLACEMENT, 5);
221
}
222
printf("%s", zEncoded);
223
free(zEncoded);
224
}
225
226
#define BLOBMARK(rid) ((rid) * 2)
227
#define COMMITMARK(rid) ((rid) * 2 + 1)
228
229
/*
230
** insert_commit_xref()
231
** Insert a new (mark,rid,uuid) entry into the 'xmark' table.
232
** zName and zUuid must be non-null and must point to NULL-terminated strings.
233
*/
234
void insert_commit_xref(int rid, const char *zName, const char *zUuid){
235
db_multi_exec(
236
"INSERT OR IGNORE INTO xmark(tname, trid, tuuid)"
237
"VALUES(%Q,%d,%Q)",
238
zName, rid, zUuid
239
);
240
}
241
242
/*
243
** create_mark()
244
** Create a new (mark,rid,uuid) entry for the given rid in the 'xmark' table,
245
** and return that information as a struct mark_t in *mark.
246
** *unused_mark is a value representing a mark that is free for use--that is,
247
** it does not appear in the marks file, and has not been used during this
248
** export run. Specifically, it is the supremum of the set of used marks
249
** plus one.
250
** This function returns -1 in the case where 'rid' does not exist, otherwise
251
** it returns 0.
252
** mark->name is dynamically allocated and is owned by the caller upon return.
253
*/
254
int create_mark(int rid, struct mark_t *mark, unsigned int *unused_mark){
255
char sid[13];
256
char *zUuid = rid_to_uuid(rid);
257
if( !zUuid ){
258
fossil_trace("Undefined rid=%d\n", rid);
259
return -1;
260
}
261
mark->rid = rid;
262
sqlite3_snprintf(sizeof(sid), sid, ":%d", *unused_mark);
263
*unused_mark += 1;
264
mark->name = fossil_strdup(sid);
265
sqlite3_snprintf(sizeof(mark->uuid), mark->uuid, "%s", zUuid);
266
free(zUuid);
267
insert_commit_xref(mark->rid, mark->name, mark->uuid);
268
return 0;
269
}
270
271
/*
272
** mark_name_from_rid()
273
** Find the mark associated with the given rid. Mark names always start
274
** with ':', and are pulled from the 'xmark' temporary table.
275
** If the given rid doesn't have a mark associated with it yet, one is
276
** created with a value of *unused_mark.
277
** *unused_mark functions exactly as in create_mark().
278
** This function returns NULL if the rid does not have an associated UUID,
279
** (i.e. is not valid). Otherwise, it returns the name of the mark, which is
280
** dynamically allocated and is owned by the caller of this function.
281
*/
282
char * mark_name_from_rid(int rid, unsigned int *unused_mark){
283
char *zMark = db_text(0, "SELECT tname FROM xmark WHERE trid=%d", rid);
284
if( zMark==NULL ){
285
struct mark_t mark;
286
if( create_mark(rid, &mark, unused_mark)==0 ){
287
zMark = mark.name;
288
}else{
289
return NULL;
290
}
291
}
292
return zMark;
293
}
294
295
/*
296
** Parse a single line of the mark file. Store the result in the mark object.
297
**
298
** "line" is a single line of input.
299
** This function returns -1 in the case that the line is blank, malformed, or
300
** the rid/uuid named in 'line' does not match what is in the repository
301
** database. Otherwise, 0 is returned.
302
**
303
** mark->name is dynamically allocated, and owned by the caller.
304
*/
305
int parse_mark(char *line, struct mark_t *mark){
306
char *cur_tok;
307
char type_;
308
cur_tok = strtok(line, " \t");
309
if( !cur_tok || strlen(cur_tok)<2 ){
310
return -1;
311
}
312
mark->rid = atoi(&cur_tok[1]);
313
type_ = cur_tok[0];
314
if( type_!='c' && type_!='b' ){
315
/* This is probably a blob mark */
316
mark->name = NULL;
317
return 0;
318
}
319
320
cur_tok = strtok(NULL, " \t");
321
if( !cur_tok ){
322
/* This mark was generated by an older version of Fossil and doesn't
323
** include the mark name and uuid. create_mark() will name the new mark
324
** exactly as it was when exported to git, so that we should have a
325
** valid mapping from git hash<->mark name<->fossil hash. */
326
unsigned int mid;
327
if( type_=='c' ){
328
mid = COMMITMARK(mark->rid);
329
}
330
else{
331
mid = BLOBMARK(mark->rid);
332
}
333
return create_mark(mark->rid, mark, &mid);
334
}else{
335
mark->name = fossil_strdup(cur_tok);
336
}
337
338
cur_tok = strtok(NULL, "\n");
339
if( !cur_tok || (strlen(cur_tok)!=40 && strlen(cur_tok)!=64) ){
340
free(mark->name);
341
fossil_trace("Invalid SHA-1/SHA-3 in marks file: %s\n", cur_tok);
342
return -1;
343
}else{
344
sqlite3_snprintf(sizeof(mark->uuid), mark->uuid, "%s", cur_tok);
345
}
346
347
/* make sure that rid corresponds to UUID */
348
if( fast_uuid_to_rid(mark->uuid)!=mark->rid ){
349
free(mark->name);
350
fossil_trace("Non-existent SHA-1/SHA-3 in marks file: %s\n", mark->uuid);
351
return -1;
352
}
353
354
/* insert a cross-ref into the 'xmark' table */
355
insert_commit_xref(mark->rid, mark->name, mark->uuid);
356
return 0;
357
}
358
359
/*
360
** Import the marks specified in file 'f';
361
** If 'blobs' is non-null, insert all blob marks into it.
362
** If 'vers' is non-null, insert all commit marks into it.
363
** If 'unused_marks' is non-null, upon return of this function, all values
364
** x >= *unused_marks are free to use as marks, i.e. they do not clash with
365
** any marks appearing in the marks file.
366
**
367
** Each line in the file must be at most 100 characters in length. This
368
** seems like a reasonable maximum for a 40-character uuid, and 1-13
369
** character rid.
370
**
371
** The function returns -1 if any of the lines in file 'f' are malformed,
372
** or the rid/uuid information doesn't match what is in the repository
373
** database. Otherwise, 0 is returned.
374
*/
375
int import_marks(FILE* f, Bag *blobs, Bag *vers, unsigned int *unused_mark){
376
char line[101];
377
while(fgets(line, sizeof(line), f)){
378
struct mark_t mark;
379
if( strlen(line)==100 && line[99]!='\n' ){
380
/* line too long */
381
return -1;
382
}
383
if( parse_mark(line, &mark)<0 ){
384
return -1;
385
}else if( line[0]=='b' ){
386
if( blobs!=NULL ){
387
bag_insert(blobs, mark.rid);
388
}
389
}else{
390
if( vers!=NULL ){
391
bag_insert(vers, mark.rid);
392
}
393
}
394
if( unused_mark!=NULL ){
395
unsigned int mid = atoi(mark.name + 1);
396
if( mid>=*unused_mark ){
397
*unused_mark = mid + 1;
398
}
399
}
400
free(mark.name);
401
}
402
return 0;
403
}
404
405
void export_mark(FILE* f, int rid, char obj_type)
406
{
407
unsigned int z = 0;
408
char *zUuid = rid_to_uuid(rid);
409
char *zMark;
410
if( zUuid==NULL ){
411
fossil_trace("No uuid matching rid=%d when exporting marks\n", rid);
412
return;
413
}
414
/* Since rid is already in the 'xmark' table, the value of z won't be
415
** used, but pass in a valid pointer just to be safe. */
416
zMark = mark_name_from_rid(rid, &z);
417
fprintf(f, "%c%d %s %s\n", obj_type, rid, zMark, zUuid);
418
free(zMark);
419
free(zUuid);
420
}
421
422
/*
423
** If 'blobs' is non-null, it must point to a Bag of blob rids to be
424
** written to disk. Blob rids are written as 'b<rid>'.
425
** If 'vers' is non-null, it must point to a Bag of commit rids to be
426
** written to disk. Commit rids are written as 'c<rid> :<mark> <uuid>'.
427
** All commit (mark,rid,uuid) tuples are stored in 'xmark' table.
428
** This function does not fail, but may produce errors if a uuid cannot
429
** be found for an rid in 'vers'.
430
*/
431
void export_marks(FILE* f, Bag *blobs, Bag *vers){
432
int rid;
433
434
if( blobs!=NULL ){
435
rid = bag_first(blobs);
436
if( rid!=0 ){
437
do{
438
export_mark(f, rid, 'b');
439
}while( (rid = bag_next(blobs, rid))!=0 );
440
}
441
}
442
if( vers!=NULL ){
443
rid = bag_first(vers);
444
if( rid!=0 ){
445
do{
446
export_mark(f, rid, 'c');
447
}while( (rid = bag_next(vers, rid))!=0 );
448
}
449
}
450
}
451
452
/* This is the original header command (and hence documentation) for
453
** the "fossil export" command:
454
**
455
** Usage: %fossil export --git ?OPTIONS? ?REPOSITORY?
456
**
457
** Write an export of all check-ins to standard output. The export is
458
** written in the git-fast-export file format assuming the --git option is
459
** provided. The git-fast-export format is currently the only VCS
460
** interchange format supported, though other formats may be added in
461
** the future.
462
**
463
** Run this command within a check-out. Or use the -R or --repository
464
** option to specify a Fossil repository to be exported.
465
**
466
** Only check-ins are exported using --git. Git does not support tickets
467
** or wiki or tech notes or attachments, so none of those are exported.
468
**
469
** If the "--import-marks FILE" option is used, it contains a list of
470
** rids to skip.
471
**
472
** If the "--export-marks FILE" option is used, the rid of all commits and
473
** blobs written on exit for use with "--import-marks" on the next run.
474
**
475
** Options:
476
** --export-marks FILE Export rids of exported data to FILE
477
** --import-marks FILE Read rids of data to ignore from FILE
478
** --rename-trunk NAME Use NAME as name of exported trunk branch
479
** -R|--repository REPO Export the given REPOSITORY
480
**
481
** See also: import
482
*/
483
/*
484
** COMMAND: export*
485
**
486
** Usage: %fossil export --git [REPOSITORY]
487
**
488
** This command is deprecated. Use "fossil git export" instead.
489
*/
490
void export_cmd(void){
491
Stmt q, q2, q3;
492
Bag blobs, vers;
493
unsigned int unused_mark = 1;
494
const char *markfile_in;
495
const char *markfile_out;
496
const char *zMainBranch = db_main_branch();
497
498
bag_init(&blobs);
499
bag_init(&vers);
500
501
find_option("git", 0, 0); /* Ignore the --git option for now */
502
markfile_in = find_option("import-marks", 0, 1);
503
markfile_out = find_option("export-marks", 0, 1);
504
505
if( !(gexport.zTrunkName = find_option("rename-trunk", 0, 1)) ){
506
gexport.zTrunkName = fossil_strdup(zMainBranch);
507
}
508
509
db_find_and_open_repository(0, 2);
510
verify_all_options();
511
if( g.argc!=2 && g.argc!=3 ){ usage("--git ?REPOSITORY?"); }
512
513
db_multi_exec("CREATE TEMPORARY TABLE oldblob(rid INTEGER PRIMARY KEY)");
514
db_multi_exec("CREATE TEMPORARY TABLE oldcommit(rid INTEGER PRIMARY KEY)");
515
db_multi_exec("CREATE TEMP TABLE xmark(tname TEXT UNIQUE, trid INT,"
516
" tuuid TEXT)");
517
db_multi_exec("CREATE INDEX xmark_trid ON xmark(trid)");
518
if( markfile_in!=0 ){
519
Stmt qb,qc;
520
FILE *f;
521
int rid;
522
523
f = fossil_fopen(markfile_in, "r");
524
if( f==0 ){
525
fossil_fatal("cannot open %s for reading", markfile_in);
526
}
527
if( import_marks(f, &blobs, &vers, &unused_mark)<0 ){
528
fossil_fatal("error importing marks from file: %s", markfile_in);
529
}
530
db_prepare(&qb, "INSERT OR IGNORE INTO oldblob VALUES (:rid)");
531
db_prepare(&qc, "INSERT OR IGNORE INTO oldcommit VALUES (:rid)");
532
rid = bag_first(&blobs);
533
if( rid!=0 ){
534
do{
535
db_bind_int(&qb, ":rid", rid);
536
db_step(&qb);
537
db_reset(&qb);
538
}while((rid = bag_next(&blobs, rid))!=0);
539
}
540
rid = bag_first(&vers);
541
if( rid!=0 ){
542
do{
543
db_bind_int(&qc, ":rid", rid);
544
db_step(&qc);
545
db_reset(&qc);
546
}while((rid = bag_next(&vers, rid))!=0);
547
}
548
db_finalize(&qb);
549
db_finalize(&qc);
550
fclose(f);
551
}
552
553
/* Step 1: Generate "blob" records for every artifact that is part
554
** of a check-in
555
*/
556
fossil_binary_mode(stdout);
557
db_multi_exec("CREATE TEMP TABLE newblob(rid INTEGER KEY, srcid INTEGER)");
558
db_multi_exec("CREATE INDEX newblob_src ON newblob(srcid)");
559
db_multi_exec(
560
"INSERT INTO newblob"
561
" SELECT DISTINCT fid,"
562
" CASE WHEN EXISTS(SELECT 1 FROM delta"
563
" WHERE rid=fid"
564
" AND NOT EXISTS(SELECT 1 FROM oldblob"
565
" WHERE srcid=fid))"
566
" THEN (SELECT srcid FROM delta WHERE rid=fid)"
567
" ELSE 0"
568
" END"
569
" FROM mlink"
570
" WHERE fid>0 AND NOT EXISTS(SELECT 1 FROM oldblob WHERE rid=fid)");
571
db_prepare(&q,
572
"SELECT DISTINCT fid FROM mlink"
573
" WHERE fid>0 AND NOT EXISTS(SELECT 1 FROM oldblob WHERE rid=fid)");
574
db_prepare(&q2, "INSERT INTO oldblob VALUES (:rid)");
575
db_prepare(&q3, "SELECT rid FROM newblob WHERE srcid= (:srcid)");
576
while( db_step(&q)==SQLITE_ROW ){
577
int rid = db_column_int(&q, 0);
578
Blob content;
579
580
while( !bag_find(&blobs, rid) ){
581
char *zMark;
582
content_get(rid, &content);
583
db_bind_int(&q2, ":rid", rid);
584
db_step(&q2);
585
db_reset(&q2);
586
zMark = mark_name_from_rid(rid, &unused_mark);
587
printf("blob\nmark %s\ndata %d\n", zMark, blob_size(&content));
588
free(zMark);
589
bag_insert(&blobs, rid);
590
fwrite(blob_buffer(&content), 1, blob_size(&content), stdout);
591
printf("\n");
592
blob_reset(&content);
593
594
db_bind_int(&q3, ":srcid", rid);
595
if( db_step(&q3) != SQLITE_ROW ){
596
db_reset(&q3);
597
break;
598
}
599
rid = db_column_int(&q3, 0);
600
db_reset(&q3);
601
}
602
}
603
db_finalize(&q);
604
db_finalize(&q2);
605
db_finalize(&q3);
606
607
/* Output the commit records.
608
*/
609
topological_sort_checkins(0);
610
db_prepare(&q,
611
"SELECT strftime('%%s',mtime), objid, coalesce(ecomment,comment),"
612
" coalesce(euser,user),"
613
" (SELECT value FROM tagxref WHERE rid=objid AND tagid=%d)"
614
" FROM toponode, event"
615
" WHERE toponode.tid=event.objid"
616
" AND event.type='ci'"
617
" AND NOT EXISTS (SELECT 1 FROM oldcommit WHERE toponode.tid=rid)"
618
" ORDER BY toponode.tseq ASC",
619
TAG_BRANCH
620
);
621
db_prepare(&q2, "INSERT INTO oldcommit VALUES (:rid)");
622
while( db_step(&q)==SQLITE_ROW ){
623
Stmt q4;
624
const char *zSecondsSince1970 = db_column_text(&q, 0);
625
int ckinId = db_column_int(&q, 1);
626
const char *zComment = db_column_text(&q, 2);
627
const char *zUser = db_column_text(&q, 3);
628
const char *zBranch = db_column_text(&q, 4);
629
char *zMark;
630
631
bag_insert(&vers, ckinId);
632
db_bind_int(&q2, ":rid", ckinId);
633
db_step(&q2);
634
db_reset(&q2);
635
if( zBranch==0 || fossil_strcmp(zBranch, zMainBranch)==0 ){
636
zBranch = gexport.zTrunkName;
637
}
638
zMark = mark_name_from_rid(ckinId, &unused_mark);
639
printf("commit refs/heads/");
640
print_ref(zBranch);
641
printf("\nmark %s\n", zMark);
642
free(zMark);
643
printf("committer");
644
print_person(zUser);
645
printf(" %s +0000\n", zSecondsSince1970);
646
if( zComment==0 ) zComment = "null comment";
647
printf("data %d\n%s\n", (int)strlen(zComment), zComment);
648
db_prepare(&q3,
649
"SELECT pid FROM plink"
650
" WHERE cid=%d AND isprim"
651
" AND pid IN (SELECT objid FROM event)",
652
ckinId
653
);
654
if( db_step(&q3) == SQLITE_ROW ){
655
int pid = db_column_int(&q3, 0);
656
zMark = mark_name_from_rid(pid, &unused_mark);
657
printf("from %s\n", zMark);
658
free(zMark);
659
db_prepare(&q4,
660
"SELECT pid FROM plink"
661
" WHERE cid=%d AND NOT isprim"
662
" AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
663
" ORDER BY pid",
664
ckinId);
665
while( db_step(&q4)==SQLITE_ROW ){
666
zMark = mark_name_from_rid(db_column_int(&q4, 0), &unused_mark);
667
printf("merge %s\n", zMark);
668
free(zMark);
669
}
670
db_finalize(&q4);
671
}else{
672
printf("deleteall\n");
673
}
674
675
db_prepare(&q4,
676
"SELECT filename.name, mlink.fid, mlink.mperm FROM mlink"
677
" JOIN filename ON filename.fnid=mlink.fnid"
678
" WHERE mlink.mid=%d",
679
ckinId
680
);
681
while( db_step(&q4)==SQLITE_ROW ){
682
const char *zName = db_column_text(&q4,0);
683
int zNew = db_column_int(&q4,1);
684
int mPerm = db_column_int(&q4,2);
685
if( zNew==0 ){
686
printf("D %s\n", zName);
687
}else if( bag_find(&blobs, zNew) ){
688
const char *zPerm;
689
zMark = mark_name_from_rid(zNew, &unused_mark);
690
switch( mPerm ){
691
case PERM_LNK: zPerm = "120000"; break;
692
case PERM_EXE: zPerm = "100755"; break;
693
default: zPerm = "100644"; break;
694
}
695
printf("M %s %s %s\n", zPerm, zMark, zName);
696
free(zMark);
697
}
698
}
699
db_finalize(&q4);
700
db_finalize(&q3);
701
printf("\n");
702
}
703
db_finalize(&q2);
704
db_finalize(&q);
705
manifest_cache_clear();
706
707
708
/* Output tags */
709
db_prepare(&q,
710
"SELECT tagname, rid, strftime('%%s',mtime),"
711
" (SELECT coalesce(euser, user) FROM event WHERE objid=rid),"
712
" value"
713
" FROM tagxref JOIN tag USING(tagid)"
714
" WHERE tagtype=1 AND tagname GLOB 'sym-*'"
715
);
716
while( db_step(&q)==SQLITE_ROW ){
717
const char *zTagname = db_column_text(&q, 0);
718
int rid = db_column_int(&q, 1);
719
char *zMark = mark_name_from_rid(rid, &unused_mark);
720
const char *zSecSince1970 = db_column_text(&q, 2);
721
const char *zUser = db_column_text(&q, 3);
722
const char *zValue = db_column_text(&q, 4);
723
if( rid==0 || !bag_find(&vers, rid) ) continue;
724
zTagname += 4;
725
printf("tag ");
726
print_ref(zTagname);
727
printf("\nfrom %s\n", zMark);
728
free(zMark);
729
printf("tagger");
730
print_person(zUser);
731
printf(" %s +0000\n", zSecSince1970);
732
printf("data %d\n", zValue==NULL?0:(int)strlen(zValue)+1);
733
if( zValue!=NULL ) printf("%s\n",zValue);
734
}
735
db_finalize(&q);
736
737
if( markfile_out!=0 ){
738
FILE *f;
739
f = fossil_fopen(markfile_out, "w");
740
if( f == 0 ){
741
fossil_fatal("cannot open %s for writing", markfile_out);
742
}
743
export_marks(f, &blobs, &vers);
744
if( ferror(f)!=0 || fclose(f)!=0 ){
745
fossil_fatal("error while writing %s", markfile_out);
746
}
747
}
748
bag_clear(&blobs);
749
bag_clear(&vers);
750
}
751
752
/*
753
** Construct the temporary table toposort as follows:
754
**
755
** CREATE TEMP TABLE toponode(
756
** tid INTEGER PRIMARY KEY, -- Check-in id
757
** tseq INT -- integer total order on check-ins.
758
** );
759
**
760
** This table contains all check-ins of the repository in topological
761
** order. "Topological order" means that every parent check-in comes
762
** before all of its children. Topological order is *almost* the same
763
** thing as "ORDER BY event.mtime". Differences only arise when there
764
** are timewarps. Inasmuch as Git hates timewarps, we have to compute
765
** a correct topological order when doing an export.
766
**
767
** Since mtime is a usually already nearly in topological order, the
768
** algorithm is to start with mtime, then make adjustments as necessary
769
** for timewarps. This is not a great algorithm for the general case,
770
** but it is very fast for the overwhelmingly common case where there
771
** are few timewarps.
772
*/
773
int topological_sort_checkins(int bVerbose){
774
int nChange = 0;
775
Stmt q1;
776
Stmt chng;
777
db_multi_exec(
778
"CREATE TEMP TABLE toponode(\n"
779
" tid INTEGER PRIMARY KEY,\n"
780
" tseq INT\n"
781
");\n"
782
"INSERT INTO toponode(tid,tseq) "
783
" SELECT objid, CAST(mtime*8640000 AS int) FROM event WHERE type='ci';\n"
784
"CREATE TEMP TABLE topolink(\n"
785
" tparent INT,\n"
786
" tchild INT,\n"
787
" PRIMARY KEY(tparent,tchild)\n"
788
") WITHOUT ROWID;"
789
"INSERT INTO topolink(tparent,tchild)"
790
" SELECT pid, cid FROM plink;\n"
791
"CREATE INDEX topolink_child ON topolink(tchild);\n"
792
);
793
794
/* Find a timewarp instance */
795
db_prepare(&q1,
796
"SELECT P.tseq, C.tid, C.tseq\n"
797
" FROM toponode P, toponode C, topolink X\n"
798
" WHERE X.tparent=P.tid\n"
799
" AND X.tchild=C.tid\n"
800
" AND P.tseq>=C.tseq;"
801
);
802
803
/* Update the timestamp on :tid to have value :tseq */
804
db_prepare(&chng,
805
"UPDATE toponode SET tseq=:tseq WHERE tid=:tid"
806
);
807
808
while( db_step(&q1)==SQLITE_ROW ){
809
i64 iParentTime = db_column_int64(&q1, 0);
810
int iChild = db_column_int(&q1, 1);
811
i64 iChildTime = db_column_int64(&q1, 2);
812
nChange++;
813
if( nChange>10000 ){
814
fossil_fatal("failed to fix all timewarps after 100000 attempts");
815
}
816
db_reset(&q1);
817
db_bind_int64(&chng, ":tid", iChild);
818
db_bind_int64(&chng, ":tseq", iParentTime+1);
819
db_step(&chng);
820
db_reset(&chng);
821
if( bVerbose ){
822
fossil_print("moving %d from %lld to %lld\n",
823
iChild, iChildTime, iParentTime+1);
824
}
825
}
826
827
db_finalize(&q1);
828
db_finalize(&chng);
829
return nChange;
830
}
831
832
/*
833
** COMMAND: test-topological-sort
834
**
835
** Invoke the topological_sort_checkins() interface for testing
836
** purposes.
837
*/
838
void test_topological_sort(void){
839
int n;
840
db_find_and_open_repository(0, 0);
841
n = topological_sort_checkins(1);
842
fossil_print("%d reorderings required\n", n);
843
}
844
845
/***************************************************************************
846
** Implementation of the "fossil git" command follows. We hope that the
847
** new code that follows will largely replace the legacy "fossil export"
848
** and "fossil import" code above.
849
*/
850
851
/* Verbosity level. Higher means more output.
852
**
853
** 0 print nothing at all
854
** 1 Errors only
855
** 2 Progress information (This is the default)
856
** 3 Extra details
857
*/
858
#define VERB_ERROR 1
859
#define VERB_NORMAL 2
860
#define VERB_EXTRA 3
861
static int gitmirror_verbosity = VERB_NORMAL;
862
863
/* The main branch in the Git repository. The main branch of the
864
** Fossil repository (usually "trunk") is renamed to be this branch name.
865
*/
866
static const char *gitmirror_mainbranch = 0;
867
868
/*
869
** Output routine that depends on verbosity
870
*/
871
static void gitmirror_message(int iLevel, const char *zFormat, ...){
872
va_list ap;
873
if( iLevel>gitmirror_verbosity ) return;
874
va_start(ap, zFormat);
875
fossil_vprint(zFormat, ap);
876
va_end(ap);
877
}
878
879
/*
880
** Convert characters of z[] that are not allowed to be in branch or
881
** tag names into "_".
882
*/
883
static void gitmirror_sanitize_name(char *z){
884
static unsigned char aSafe[] = {
885
/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
886
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */
887
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */
888
0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, /* 2x */
889
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, /* 3x */
890
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */
891
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, /* 5x */
892
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */
893
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, /* 7x */
894
};
895
unsigned char *zu = (unsigned char*)z;
896
int i;
897
for(i=0; zu[i]; i++){
898
if( zu[i]>0x7f || !aSafe[zu[i]] ){
899
zu[i] = '_';
900
}else if( zu[i]=='/' && (i==0 || zu[i+1]==0 || zu[i+1]=='/') ){
901
zu[i] = '_';
902
}else if( zu[i]=='.' && (zu[i+1]==0 || zu[i+1]=='.'
903
|| (i>0 && zu[i-1]=='.')) ){
904
zu[i] = '_';
905
}
906
}
907
}
908
909
/*
910
** COMMAND: test-sanitize-name
911
**
912
** Usage: %fossil ARG...
913
**
914
** This sanitizes each argument and make it part of an "echo" command
915
** run by the shell.
916
*/
917
void test_sanitize_name_cmd(void){
918
sqlite3_str *pStr;
919
int i;
920
char *zCmd;
921
pStr = sqlite3_str_new(0);
922
sqlite3_str_appendall(pStr, "echo");
923
for(i=2; i<g.argc; i++){
924
char *z = fossil_strdup(g.argv[i]);
925
gitmirror_sanitize_name(z);
926
sqlite3_str_appendf(pStr, " \"%s\"", z);
927
fossil_free(z);
928
}
929
zCmd = sqlite3_str_finish(pStr);
930
fossil_print("Command: %s\n", zCmd);
931
fossil_system(zCmd);
932
sqlite3_free(zCmd);
933
}
934
935
/*
936
** Quote a filename as a C-style string using \\ and \" if necessary.
937
** If quoting is not necessary, just return a copy of the input string.
938
**
939
** The return value is a held in memory obtained from fossil_malloc()
940
** and must be freed by the caller.
941
*/
942
static char *gitmirror_quote_filename_if_needed(const char *zIn){
943
int i, j;
944
char c;
945
int nSpecial = 0;
946
char *zOut;
947
for(i=0; (c = zIn[i])!=0; i++){
948
if( c=='\\' || c=='"' || c=='\n' ){
949
nSpecial++;
950
}
951
}
952
if( nSpecial==0 ){
953
return fossil_strdup(zIn);
954
}
955
zOut = fossil_malloc( i+nSpecial+3 );
956
zOut[0] = '"';
957
for(i=0, j=1; (c = zIn[i])!=0; i++){
958
if( c=='\\' || c=='"' || c=='\n' ){
959
zOut[j++] = '\\';
960
if( c=='\n' ){
961
zOut[j++] = 'n';
962
}else{
963
zOut[j++] = c;
964
}
965
}else{
966
zOut[j++] = c;
967
}
968
}
969
zOut[j++] = '"';
970
zOut[j] = 0;
971
return zOut;
972
}
973
974
/*
975
** Find the Git-name corresponding to the Fossil-name zUuid.
976
**
977
** If the mark does not exist and if the bCreate flag is false, then
978
** return NULL. If the mark does not exist and the bCreate flag is true,
979
** then create the mark.
980
**
981
** The string returned is obtained from fossil_malloc() and should
982
** be freed by the caller.
983
*/
984
static char *gitmirror_find_mark(const char *zUuid, int isFile, int bCreate){
985
static Stmt sFind, sIns;
986
db_static_prepare(&sFind,
987
"SELECT coalesce(githash,printf(':%%d',id))"
988
" FROM mirror.mmark WHERE uuid=:uuid AND isfile=:isfile"
989
);
990
db_bind_text(&sFind, ":uuid", zUuid);
991
db_bind_int(&sFind, ":isfile", isFile!=0);
992
if( db_step(&sFind)==SQLITE_ROW ){
993
char *zMark = fossil_strdup(db_column_text(&sFind, 0));
994
db_reset(&sFind);
995
return zMark;
996
}
997
db_reset(&sFind);
998
if( !bCreate ){
999
return 0;
1000
}
1001
db_static_prepare(&sIns,
1002
"INSERT INTO mirror.mmark(uuid,isfile) VALUES(:uuid,:isfile)"
1003
);
1004
db_bind_text(&sIns, ":uuid", zUuid);
1005
db_bind_int(&sIns, ":isfile", isFile!=0);
1006
db_step(&sIns);
1007
db_reset(&sIns);
1008
return mprintf(":%d", db_last_insert_rowid());
1009
}
1010
1011
/* This is the SHA3-256 hash of an empty file */
1012
static const char zEmptySha3[] =
1013
"a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a";
1014
1015
/*
1016
** Export a single file named by zUuid.
1017
**
1018
** Return 0 on success and non-zero on any failure.
1019
**
1020
** If zUuid is a shunned file, then treat it as if it were any empty file.
1021
** But files that are missing from the repository but have not been officially
1022
** shunned cause an error return. Except, if bPhantomOk is true, then missing
1023
** files are replaced by an empty file.
1024
*/
1025
static int gitmirror_send_file(FILE *xCmd, const char *zUuid, int bPhantomOk){
1026
char *zMark;
1027
int rid;
1028
int rc;
1029
Blob data;
1030
rid = fast_uuid_to_rid(zUuid);
1031
if( rid<0 ){
1032
if( bPhantomOk || uuid_is_shunned(zUuid) ){
1033
gitmirror_message(VERB_EXTRA, "missing file: %s\n", zUuid);
1034
zUuid = zEmptySha3;
1035
}else{
1036
return 1;
1037
}
1038
}else{
1039
rc = content_get(rid, &data);
1040
if( rc==0 ){
1041
if( bPhantomOk ){
1042
blob_init(&data, 0, 0);
1043
gitmirror_message(VERB_EXTRA, "missing file: %s\n", zUuid);
1044
zUuid = zEmptySha3;
1045
}else{
1046
return 1;
1047
}
1048
}
1049
}
1050
zMark = gitmirror_find_mark(zUuid, 1, 1);
1051
if( zMark[0]==':' ){
1052
fprintf(xCmd, "blob\nmark %s\ndata %d\n", zMark, blob_size(&data));
1053
fwrite(blob_buffer(&data), 1, blob_size(&data), xCmd);
1054
fprintf(xCmd, "\n");
1055
}
1056
fossil_free(zMark);
1057
blob_reset(&data);
1058
return 0;
1059
}
1060
1061
/*
1062
** Transfer a check-in over to the mirror. "rid" is the BLOB.RID for
1063
** the check-in to export.
1064
**
1065
** If any ancestor of the check-in has not yet been exported, then
1066
** invoke this routine recursively to export the ancestor first.
1067
** This can only happen on a timewarp, so deep nesting is unlikely.
1068
**
1069
** Before sending the check-in, first make sure all associated files
1070
** have already been exported, and send "blob" records for any that
1071
** have not been. Update the MIRROR.MMARK table so that it holds the
1072
** marks for the exported files.
1073
**
1074
** Return zero on success and non-zero if the export should be stopped.
1075
*/
1076
static int gitmirror_send_checkin(
1077
FILE *xCmd, /* Write fast-import text on this pipe */
1078
int rid, /* BLOB.RID for the check-in to export */
1079
const char *zUuid, /* BLOB.UUID for the check-in to export */
1080
int *pnLimit /* Stop when the counter reaches zero */
1081
){
1082
Manifest *pMan; /* The check-in to be output */
1083
int i; /* Loop counter */
1084
int iParent; /* Which immediate ancestor is primary. -1 for none */
1085
Stmt q; /* An SQL query */
1086
char *zBranch; /* The branch of the check-in */
1087
char *zMark; /* The Git-name of the check-in */
1088
Blob sql; /* String of SQL for part of the query */
1089
Blob comment; /* The comment text for the check-in */
1090
int nErr = 0; /* Number of errors */
1091
int bPhantomOk; /* True if phantom files should be ignored */
1092
char buf[24];
1093
char *zEmail; /* Contact info for Git committer field */
1094
int fManifest; /* Should the manifest files be included? */
1095
int fPManifest = 0; /* OR of the manifest files for all parents */
1096
const char *zMainBranch;
1097
1098
pMan = manifest_get(rid, CFTYPE_MANIFEST, 0);
1099
if( pMan==0 ){
1100
/* Must be a phantom. Return without doing anything, and in particular
1101
** without creating a mark for this check-in. */
1102
gitmirror_message(VERB_NORMAL, "missing check-in: %s\n", zUuid);
1103
return 0;
1104
}
1105
1106
/* Check to see if any parent logins have not yet been processed, and
1107
** if so, create them */
1108
for(i=0; i<pMan->nParent; i++){
1109
char *zPMark = gitmirror_find_mark(pMan->azParent[i], 0, 0);
1110
if( zPMark==0 ){
1111
int prid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q",
1112
pMan->azParent[i]);
1113
int rc = gitmirror_send_checkin(xCmd, prid, pMan->azParent[i],
1114
pnLimit);
1115
if( rc || *pnLimit<=0 ){
1116
manifest_destroy(pMan);
1117
return 1;
1118
}
1119
}
1120
fossil_free(zPMark);
1121
}
1122
1123
/* Ignore phantom files on check-ins that are over one year old */
1124
bPhantomOk = db_int(0, "SELECT %.6f<julianday('now','-1 year')",
1125
pMan->rDate);
1126
1127
/* Make sure all necessary files have been exported */
1128
db_prepare(&q,
1129
"SELECT uuid FROM files_of_checkin(%Q)"
1130
" WHERE uuid NOT IN (SELECT uuid FROM mirror.mmark)",
1131
zUuid
1132
);
1133
while( db_step(&q)==SQLITE_ROW ){
1134
const char *zFUuid = db_column_text(&q, 0);
1135
int n = gitmirror_send_file(xCmd, zFUuid, bPhantomOk);
1136
nErr += n;
1137
if( n ) gitmirror_message(VERB_ERROR, "missing file: %s\n", zFUuid);
1138
}
1139
db_finalize(&q);
1140
1141
/* If some required files could not be exported, abandon the check-in
1142
** export */
1143
if( nErr ){
1144
gitmirror_message(VERB_ERROR,
1145
"export of %s abandoned due to missing files\n", zUuid);
1146
*pnLimit = 0;
1147
manifest_destroy(pMan);
1148
return 1;
1149
}
1150
1151
/* Figure out which branch this check-in is a member of */
1152
zBranch = db_text(0,
1153
"SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=%d",
1154
TAG_BRANCH, rid
1155
);
1156
zMainBranch = db_main_branch();
1157
if( fossil_strcmp(zBranch, zMainBranch)==0 ){
1158
assert( gitmirror_mainbranch!=0 );
1159
fossil_free(zBranch);
1160
zBranch = fossil_strdup(gitmirror_mainbranch);
1161
}else if( zBranch==0 ){
1162
zBranch = mprintf("unknown");
1163
}else{
1164
gitmirror_sanitize_name(zBranch);
1165
}
1166
1167
/* Export the check-in */
1168
fprintf(xCmd, "commit refs/heads/%s\n", zBranch);
1169
fossil_free(zBranch);
1170
zMark = gitmirror_find_mark(zUuid,0,1);
1171
fprintf(xCmd, "mark %s\n", zMark);
1172
fossil_free(zMark);
1173
sqlite3_snprintf(sizeof(buf), buf, "%lld",
1174
(sqlite3_int64)((pMan->rDate-2440587.5)*86400.0)
1175
);
1176
1177
/*
1178
** Check for 'fx_' table from previous Git import, otherwise take contact info
1179
** from user table for <emailaddr> in committer field. If no emailaddr, check
1180
** if username is in email form, otherwise use generic '[email protected]'.
1181
*/
1182
if (db_table_exists("repository", "fx_git")) {
1183
zEmail = db_text(0, "SELECT email FROM fx_git WHERE user=%Q", pMan->zUser);
1184
} else {
1185
zEmail = db_text(0, "SELECT info FROM user WHERE login=%Q", pMan->zUser);
1186
}
1187
1188
/* Some repo 'info' fields return an empty string hence the second check */
1189
if( zEmail==0 ){
1190
/* If username is in emailaddr form, don't append '@noemail.net' */
1191
if( pMan->zUser==0 || strchr(pMan->zUser, '@')==0 ){
1192
zEmail = mprintf("%[email protected]", pMan->zUser);
1193
} else {
1194
zEmail = fossil_strdup(pMan->zUser);
1195
}
1196
}else{
1197
char *zTmp = strchr(zEmail, '<');
1198
if( zTmp ){
1199
char *zTmpEnd = strchr(zTmp+1, '>');
1200
char *zNew;
1201
int i;
1202
if( zTmpEnd ) *(zTmpEnd) = 0;
1203
zNew = fossil_strdup(zTmp+1);
1204
fossil_free(zEmail);
1205
zEmail = zNew;
1206
for(i=0; zEmail[i] && !fossil_isspace(zEmail[i]); i++){}
1207
zEmail[i] = 0;
1208
}
1209
}
1210
fprintf(xCmd, "# rid=%d\n", rid);
1211
fprintf(xCmd, "committer %s <%s> %s +0000\n", pMan->zUser, zEmail, buf);
1212
fossil_free(zEmail);
1213
blob_init(&comment, pMan->zComment, -1);
1214
if( blob_size(&comment)==0 ){
1215
blob_append(&comment, "(no comment)", -1);
1216
}
1217
blob_appendf(&comment, "\n\nFossilOrigin-Name: %s", zUuid);
1218
fprintf(xCmd, "data %d\n%s\n", blob_strlen(&comment), blob_str(&comment));
1219
blob_reset(&comment);
1220
iParent = -1; /* Which ancestor is the primary parent */
1221
for(i=0; i<pMan->nParent; i++){
1222
char *zOther = gitmirror_find_mark(pMan->azParent[i],0,0);
1223
if( zOther==0 ) continue;
1224
fPManifest |= db_get_manifest_setting(pMan->azParent[i]);
1225
if( iParent<0 ){
1226
iParent = i;
1227
fprintf(xCmd, "from %s\n", zOther);
1228
}else{
1229
fprintf(xCmd, "merge %s\n", zOther);
1230
}
1231
fossil_free(zOther);
1232
}
1233
if( iParent>=0 ){
1234
db_prepare(&q,
1235
"SELECT filename FROM files_of_checkin(%Q)"
1236
" EXCEPT SELECT filename FROM files_of_checkin(%Q)",
1237
pMan->azParent[iParent], zUuid
1238
);
1239
while( db_step(&q)==SQLITE_ROW ){
1240
fprintf(xCmd, "D %s\n", db_column_text(&q,0));
1241
}
1242
db_finalize(&q);
1243
}
1244
blob_init(&sql, 0, 0);
1245
blob_append_sql(&sql,
1246
"SELECT filename, uuid, perm FROM files_of_checkin(%Q)",
1247
zUuid
1248
);
1249
if( pMan->nParent ){
1250
blob_append_sql(&sql,
1251
" EXCEPT SELECT filename, uuid, perm FROM files_of_checkin(%Q)",
1252
pMan->azParent[0]);
1253
}
1254
db_prepare(&q,
1255
"SELECT x.filename, x.perm,"
1256
" coalesce(mmark.githash,printf(':%%d',mmark.id))"
1257
" FROM (%s) AS x, mirror.mmark"
1258
" WHERE mmark.uuid=x.uuid AND isfile",
1259
blob_sql_text(&sql)
1260
);
1261
blob_reset(&sql);
1262
while( db_step(&q)==SQLITE_ROW ){
1263
const char *zFilename = db_column_text(&q,0);
1264
const char *zMode = db_column_text(&q,1);
1265
const char *zMark = db_column_text(&q,2);
1266
const char *zGitMode = "100644";
1267
char *zFNQuoted = 0;
1268
if( zMode ){
1269
if( strchr(zMode,'x') ) zGitMode = "100755";
1270
if( strchr(zMode,'l') ) zGitMode = "120000";
1271
}
1272
zFNQuoted = gitmirror_quote_filename_if_needed(zFilename);
1273
fprintf(xCmd,"M %s %s %s\n", zGitMode, zMark, zFNQuoted);
1274
fossil_free(zFNQuoted);
1275
}
1276
db_finalize(&q);
1277
manifest_destroy(pMan);
1278
pMan = 0;
1279
1280
/* Include Fossil-generated auxiliary files in the check-in */
1281
fManifest = db_get_manifest_setting(zUuid);
1282
if( fManifest & MFESTFLG_RAW ){
1283
Blob manifest;
1284
content_get(rid, &manifest);
1285
sterilize_manifest(&manifest, CFTYPE_MANIFEST);
1286
fprintf(xCmd,"M 100644 inline manifest\ndata %d\n%s\n",
1287
blob_strlen(&manifest), blob_str(&manifest));
1288
blob_reset(&manifest);
1289
}else if( fPManifest & MFESTFLG_RAW ){
1290
fprintf(xCmd, "D manifest\n");
1291
}
1292
if( fManifest & MFESTFLG_UUID ){
1293
int n = (int)strlen(zUuid);
1294
fprintf(xCmd,"M 100644 inline manifest.uuid\ndata %d\n%s\n\n", n+1, zUuid);
1295
}else if( fPManifest & MFESTFLG_UUID ){
1296
fprintf(xCmd, "D manifest.uuid\n");
1297
}
1298
if( fManifest & MFESTFLG_TAGS ){
1299
Blob tagslist;
1300
blob_init(&tagslist, 0, 0);
1301
get_checkin_taglist(rid, &tagslist);
1302
fprintf(xCmd,"M 100644 inline manifest.tags\ndata %d\n%s\n",
1303
blob_strlen(&tagslist), blob_str(&tagslist));
1304
blob_reset(&tagslist);
1305
}else if( fPManifest & MFESTFLG_TAGS ){
1306
fprintf(xCmd, "D manifest.tags\n");
1307
}
1308
1309
/* The check-in is finished, so decrement the counter */
1310
(*pnLimit)--;
1311
return 0;
1312
}
1313
1314
/*
1315
** Create a new Git repository at zMirror to use as the mirror.
1316
** Try to make zMainBr be the main branch for the new repository.
1317
**
1318
** A side-effect of this routine is that current-working directory
1319
** is changed to zMirror.
1320
**
1321
** If zMainBr is initially NULL, then the return value will be the
1322
** name of the default branch to be used by Git. If zMainBr is
1323
** initially non-NULL, then the return value will be a copy of zMainBr.
1324
*/
1325
static char *gitmirror_init(
1326
const char *zMirror,
1327
char *zMainBr
1328
){
1329
char *zCmd;
1330
int rc;
1331
1332
/* Create a new Git repository at zMirror */
1333
zCmd = mprintf("git init %$", zMirror);
1334
gitmirror_message(VERB_NORMAL, "%s\n", zCmd);
1335
rc = fossil_system(zCmd);
1336
if( rc ){
1337
fossil_fatal("cannot initialize git repository using: %s", zCmd);
1338
}
1339
fossil_free(zCmd);
1340
1341
/* Must be in the new Git repository directory for subsequent commands */
1342
rc = file_chdir(zMirror, 0);
1343
if( rc ){
1344
fossil_fatal("cannot change to directory \"%s\"", zMirror);
1345
}
1346
1347
if( zMainBr ){
1348
/* Set the current branch to zMainBr */
1349
zCmd = mprintf("git symbolic-ref HEAD refs/heads/%s", zMainBr);
1350
gitmirror_message(VERB_NORMAL, "%s\n", zCmd);
1351
rc = fossil_system(zCmd);
1352
if( rc ){
1353
fossil_fatal("git command failed: %s", zCmd);
1354
}
1355
fossil_free(zCmd);
1356
}else{
1357
/* If zMainBr is not specified, then check to see what branch
1358
** name Git chose for itself */
1359
char *z;
1360
char zLine[1000];
1361
FILE *xCmd;
1362
int i;
1363
zCmd = "git symbolic-ref --short HEAD";
1364
gitmirror_message(VERB_NORMAL, "%s\n", zCmd);
1365
xCmd = popen(zCmd, "r");
1366
if( xCmd==0 ){
1367
fossil_fatal("git command failed: %s", zCmd);
1368
}
1369
1370
z = fgets(zLine, sizeof(zLine), xCmd);
1371
pclose(xCmd);
1372
if( z==0 ){
1373
fossil_fatal("no output from \"%s\"", zCmd);
1374
}
1375
for(i=0; z[i] && !fossil_isspace(z[i]); i++){}
1376
z[i] = 0;
1377
zMainBr = fossil_strdup(z);
1378
}
1379
return zMainBr;
1380
}
1381
1382
/*
1383
** Implementation of the "fossil git export" command.
1384
*/
1385
void gitmirror_export_command(void){
1386
const char *zLimit; /* Text of the --limit flag */
1387
int nLimit = 0x7fffffff; /* Numeric value of the --limit flag */
1388
int nTotal = 0; /* Total number of check-ins to export */
1389
char *zMirror; /* Name of the mirror */
1390
char *z; /* Generic string */
1391
char *zCmd; /* git command to run as a subprocess */
1392
const char *zDebug = 0; /* Value of the --debug flag */
1393
const char *zAutoPush = 0; /* Value of the --autopush flag */
1394
char *zMainBr = 0; /* Value of the --mainbranch flag */
1395
char *zPushUrl; /* URL to sync the mirror to */
1396
double rEnd; /* time of most recent export */
1397
int rc; /* Result code */
1398
int bForce; /* Do the export and sync even if no changes*/
1399
int bNeedRepack = 0; /* True if we should run repack at the end */
1400
int bIfExists; /* The --if-mirrored flag */
1401
FILE *xCmd; /* Pipe to the "git fast-import" command */
1402
FILE *pMarks; /* Git mark files */
1403
Stmt q; /* Queries */
1404
char zLine[200]; /* One line of a mark file */
1405
1406
zDebug = find_option("debug",0,1);
1407
db_find_and_open_repository(0, 0);
1408
zLimit = find_option("limit", 0, 1);
1409
if( zLimit ){
1410
nLimit = (unsigned int)atoi(zLimit);
1411
if( nLimit<=0 ) fossil_fatal("--limit must be positive");
1412
}
1413
zAutoPush = find_option("autopush",0,1);
1414
zMainBr = (char*)find_option("mainbranch",0,1);
1415
bForce = find_option("force","f",0)!=0;
1416
bIfExists = find_option("if-mirrored",0,0)!=0;
1417
gitmirror_verbosity = VERB_NORMAL;
1418
if( g.fQuiet ){ gitmirror_verbosity--; } /* Global option not repeatable. */
1419
while( find_option("quiet","q",0)!=0 ){ gitmirror_verbosity--; }
1420
while( find_option("verbose","v",0)!=0 ){ gitmirror_verbosity++; }
1421
verify_all_options();
1422
if( g.argc!=4 && g.argc!=3 ){ usage("export ?MIRROR?"); }
1423
if( g.argc==4 ){
1424
Blob mirror;
1425
file_canonical_name(g.argv[3], &mirror, 0);
1426
db_set("last-git-export-repo", blob_str(&mirror), 0);
1427
blob_reset(&mirror);
1428
}
1429
zMirror = db_get("last-git-export-repo", 0);
1430
if( zMirror==0 ){
1431
if( bIfExists ) return;
1432
fossil_fatal("no Git repository specified");
1433
}
1434
1435
if( zMainBr ){
1436
z = fossil_strdup(zMainBr);
1437
gitmirror_sanitize_name(z);
1438
if( strcmp(z, zMainBr) ){
1439
fossil_fatal("\"%s\" is not a legal branch name for Git", zMainBr);
1440
}
1441
fossil_free(z);
1442
}
1443
1444
/* Make sure the GIT repository directory exists */
1445
rc = file_mkdir(zMirror, ExtFILE, 0);
1446
if( rc ) fossil_fatal("cannot create directory \"%s\"", zMirror);
1447
1448
/* Make sure GIT has been initialized */
1449
z = mprintf("%s/.git", zMirror);
1450
if( !file_isdir(z, ExtFILE) ){
1451
zMainBr = gitmirror_init(zMirror, zMainBr);
1452
bNeedRepack = 1;
1453
}
1454
fossil_free(z);
1455
1456
/* Make sure the .mirror_state subdirectory exists */
1457
z = mprintf("%s/.mirror_state", zMirror);
1458
rc = file_mkdir(z, ExtFILE, 0);
1459
if( rc ) fossil_fatal("cannot create directory \"%s\"", z);
1460
fossil_free(z);
1461
1462
/* Attach the .mirror_state/db database */
1463
db_multi_exec("ATTACH '%q/.mirror_state/db' AS mirror;", zMirror);
1464
db_begin_write();
1465
db_multi_exec(
1466
"CREATE TABLE IF NOT EXISTS mirror.mconfig(\n"
1467
" key TEXT PRIMARY KEY,\n"
1468
" Value ANY\n"
1469
") WITHOUT ROWID;\n"
1470
"CREATE TABLE IF NOT EXISTS mirror.mmark(\n"
1471
" id INTEGER PRIMARY KEY,\n"
1472
" uuid TEXT,\n"
1473
" isfile BOOLEAN,\n"
1474
" githash TEXT,\n"
1475
" UNIQUE(uuid,isfile)\n"
1476
");"
1477
);
1478
if( !db_table_has_column("mirror","mmark","isfile") ){
1479
db_multi_exec(
1480
"ALTER TABLE mirror.mmark RENAME TO mmark_old;"
1481
"CREATE TABLE IF NOT EXISTS mirror.mmark(\n"
1482
" id INTEGER PRIMARY KEY,\n"
1483
" uuid TEXT,\n"
1484
" isfile BOOLEAN,\n"
1485
" githash TEXT,\n"
1486
" UNIQUE(uuid,isfile)\n"
1487
");"
1488
"INSERT OR IGNORE INTO mirror.mmark(id,uuid,githash,isfile)"
1489
" SELECT id,uuid,githash,"
1490
" NOT EXISTS(SELECT 1 FROM repository.event, repository.blob"
1491
" WHERE event.objid=blob.rid"
1492
" AND blob.uuid=mmark_old.uuid)"
1493
" FROM mirror.mmark_old;\n"
1494
"DROP TABLE mirror.mmark_old;\n"
1495
);
1496
}
1497
1498
/* Change the autopush setting if the --autopush flag is present */
1499
if( zAutoPush ){
1500
if( is_false(zAutoPush) ){
1501
db_multi_exec("DELETE FROM mirror.mconfig WHERE key='autopush'");
1502
}else{
1503
db_multi_exec(
1504
"REPLACE INTO mirror.mconfig(key,value)"
1505
"VALUES('autopush',%Q)",
1506
zAutoPush
1507
);
1508
}
1509
}
1510
1511
/* Change the mainbranch setting if the --mainbranch flag is present */
1512
if( zMainBr && zMainBr[0] ){
1513
db_multi_exec(
1514
"REPLACE INTO mirror.mconfig(key,value)"
1515
"VALUES('mainbranch',%Q)",
1516
zMainBr
1517
);
1518
gitmirror_mainbranch = fossil_strdup(zMainBr);
1519
}else{
1520
/* Recover the saved name of the main branch */
1521
gitmirror_mainbranch = db_text("master",
1522
"SELECT value FROM mconfig WHERE key='mainbranch'");
1523
}
1524
1525
1526
/* See if there is any work to be done. Exit early if not, before starting
1527
** the "git fast-import" command. */
1528
if( !bForce
1529
&& !db_exists("SELECT 1 FROM event WHERE type IN ('ci','t')"
1530
" AND mtime>coalesce((SELECT value FROM mconfig"
1531
" WHERE key='start'),0.0)")
1532
){
1533
gitmirror_message(VERB_NORMAL, "no changes\n");
1534
db_commit_transaction();
1535
return;
1536
}
1537
1538
/* Change to the MIRROR directory so that the Git commands will work */
1539
rc = file_chdir(zMirror, 0);
1540
if( rc ) fossil_fatal("cannot change the working directory to \"%s\"",
1541
zMirror);
1542
1543
/* Start up the git fast-import command */
1544
if( zDebug ){
1545
if( fossil_strcmp(zDebug,"stdout")==0 ){
1546
xCmd = stdout;
1547
}else{
1548
xCmd = fopen(zDebug, "wb");
1549
if( xCmd==0 ) fossil_fatal("cannot open file \"%s\" for writing", zDebug);
1550
}
1551
}else{
1552
zCmd = mprintf("git fast-import"
1553
" --export-marks=.mirror_state/marks.txt"
1554
" --quiet --done");
1555
gitmirror_message(VERB_NORMAL, "%s\n", zCmd);
1556
#ifdef _WIN32
1557
xCmd = popen(zCmd, "wb");
1558
#else
1559
xCmd = popen(zCmd, "w");
1560
#endif
1561
if( xCmd==0 ){
1562
fossil_fatal("cannot start the \"git fast-import\" command");
1563
}
1564
fossil_free(zCmd);
1565
}
1566
1567
/* Run the export */
1568
rEnd = 0.0;
1569
db_multi_exec(
1570
"CREATE TEMP TABLE tomirror(objid,mtime,uuid);\n"
1571
"INSERT INTO tomirror "
1572
"SELECT objid, mtime, blob.uuid FROM event, blob\n"
1573
" WHERE type='ci'"
1574
" AND mtime>coalesce((SELECT value FROM mconfig WHERE key='start'),0.0)"
1575
" AND blob.rid=event.objid"
1576
" AND blob.uuid NOT IN (SELECT uuid FROM mirror.mmark WHERE NOT isfile)"
1577
" AND NOT EXISTS (SELECT 1 FROM private WHERE rid=blob.rid);"
1578
);
1579
nTotal = db_int(0, "SELECT count(*) FROM tomirror");
1580
if( nLimit<nTotal ){
1581
nTotal = nLimit;
1582
}else if( nLimit>nTotal ){
1583
nLimit = nTotal;
1584
}
1585
db_prepare(&q,
1586
"SELECT objid, mtime, uuid FROM tomirror ORDER BY mtime"
1587
);
1588
while( nLimit && db_step(&q)==SQLITE_ROW ){
1589
int rid = db_column_int(&q, 0);
1590
double rMTime = db_column_double(&q, 1);
1591
const char *zUuid = db_column_text(&q, 2);
1592
if( rMTime>rEnd ) rEnd = rMTime;
1593
rc = gitmirror_send_checkin(xCmd, rid, zUuid, &nLimit);
1594
if( rc ) break;
1595
gitmirror_message(VERB_NORMAL,"%d/%d \r", nTotal-nLimit, nTotal);
1596
fflush(stdout);
1597
}
1598
db_finalize(&q);
1599
fprintf(xCmd, "done\n");
1600
if( zDebug ){
1601
if( xCmd!=stdout ) fclose(xCmd);
1602
}else{
1603
pclose(xCmd);
1604
}
1605
gitmirror_message(VERB_NORMAL, "%d check-ins added to the %s\n",
1606
nTotal-nLimit, zMirror);
1607
1608
/* Read the export-marks file. Transfer the new marks over into
1609
** the import-marks file.
1610
*/
1611
pMarks = fopen(".mirror_state/marks.txt", "rb");
1612
if( pMarks ){
1613
db_prepare(&q, "UPDATE mirror.mmark SET githash=:githash WHERE id=:id");
1614
while( fgets(zLine, sizeof(zLine), pMarks) ){
1615
int j, k;
1616
if( zLine[0]!=':' ) continue;
1617
db_bind_int(&q, ":id", atoi(zLine+1));
1618
for(j=1; zLine[j] && zLine[j]!=' '; j++){}
1619
if( zLine[j]!=' ' ) continue;
1620
j++;
1621
if( zLine[j]==0 ) continue;
1622
for(k=j; fossil_isalnum(zLine[k]); k++){}
1623
zLine[k] = 0;
1624
db_bind_text(&q, ":githash", &zLine[j]);
1625
db_step(&q);
1626
db_reset(&q);
1627
}
1628
db_finalize(&q);
1629
fclose(pMarks);
1630
file_delete(".mirror_state/marks.txt");
1631
}else{
1632
fossil_fatal("git fast-import didn't generate a marks file!");
1633
}
1634
db_multi_exec(
1635
"CREATE INDEX IF NOT EXISTS mirror.mmarkx1 ON mmark(githash);"
1636
);
1637
1638
/* Do any tags that have been created since the start time */
1639
db_prepare(&q,
1640
"SELECT substr(tagname,5), githash"
1641
" FROM (SELECT tagxref.tagid AS xtagid, tagname, rid, max(mtime) AS mtime"
1642
" FROM tagxref JOIN tag ON tag.tagid=tagxref.tagid"
1643
" WHERE tag.tagname GLOB 'sym-*'"
1644
" AND tagxref.tagtype=1"
1645
" AND tagxref.mtime > coalesce((SELECT value FROM mconfig"
1646
" WHERE key='start'),0.0)"
1647
" GROUP BY tagxref.tagid) AS tx"
1648
" JOIN blob ON tx.rid=blob.rid"
1649
" JOIN mmark ON mmark.uuid=blob.uuid;"
1650
);
1651
while( db_step(&q)==SQLITE_ROW ){
1652
char *zTagname = fossil_strdup(db_column_text(&q,0));
1653
const char *zObj = db_column_text(&q,1);
1654
char *zTagCmd;
1655
gitmirror_sanitize_name(zTagname);
1656
zTagCmd = mprintf("git tag -f %$ %$", zTagname, zObj);
1657
fossil_free(zTagname);
1658
gitmirror_message(VERB_NORMAL, "%s\n", zTagCmd);
1659
fossil_system(zTagCmd);
1660
fossil_free(zTagCmd);
1661
}
1662
db_finalize(&q);
1663
1664
/* Update all references that might have changed since the start time */
1665
db_prepare(&q,
1666
"SELECT"
1667
" tagxref.value AS name,"
1668
" max(event.mtime) AS mtime,"
1669
" mmark.githash AS gitckin"
1670
" FROM tagxref, tag, event, blob, mmark"
1671
" WHERE tagxref.tagid=tag.tagid"
1672
" AND tagxref.tagtype>0"
1673
" AND tag.tagname='branch'"
1674
" AND event.objid=tagxref.rid"
1675
" AND event.mtime > coalesce((SELECT value FROM mconfig"
1676
" WHERE key='start'),0.0)"
1677
" AND blob.rid=tagxref.rid"
1678
" AND mmark.uuid=blob.uuid"
1679
" GROUP BY 1"
1680
);
1681
while( db_step(&q)==SQLITE_ROW ){
1682
char *zBrname = fossil_strdup(db_column_text(&q,0));
1683
const char *zObj = db_column_text(&q,2);
1684
char *zRefCmd;
1685
if( fossil_strcmp(zBrname,"trunk")==0 ){
1686
fossil_free(zBrname);
1687
zBrname = fossil_strdup(gitmirror_mainbranch);
1688
}else{
1689
gitmirror_sanitize_name(zBrname);
1690
}
1691
zRefCmd = mprintf("git update-ref \"refs/heads/%s\" %$", zBrname, zObj);
1692
fossil_free(zBrname);
1693
gitmirror_message(VERB_NORMAL, "%s\n", zRefCmd);
1694
fossil_system(zRefCmd);
1695
fossil_free(zRefCmd);
1696
}
1697
db_finalize(&q);
1698
1699
/* Update the start time */
1700
if( rEnd>0.0 ){
1701
db_prepare(&q, "REPLACE INTO mirror.mconfig(key,value) VALUES('start',:x)");
1702
db_bind_double(&q, ":x", rEnd);
1703
db_step(&q);
1704
db_finalize(&q);
1705
}
1706
db_commit_transaction();
1707
1708
/* Maybe run a git repack */
1709
if( bNeedRepack ){
1710
const char *zRepack = "git repack -adf";
1711
gitmirror_message(VERB_NORMAL, "%s\n", zRepack);
1712
fossil_system(zRepack);
1713
}
1714
1715
/* Optionally do a "git push" */
1716
zPushUrl = db_text(0, "SELECT value FROM mconfig WHERE key='autopush'");
1717
if( zPushUrl ){
1718
char *zPushCmd;
1719
UrlData url;
1720
if( sqlite3_strglob("http*", zPushUrl)==0 ){
1721
url_parse_local(zPushUrl, 0, &url);
1722
zPushCmd = mprintf("git push --mirror %s", url.canonical);
1723
}else{
1724
zPushCmd = mprintf("git push --mirror %s", zPushUrl);
1725
}
1726
gitmirror_message(VERB_NORMAL, "%s\n", zPushCmd);
1727
fossil_free(zPushCmd);
1728
zPushCmd = mprintf("git push --mirror %$", zPushUrl);
1729
rc = fossil_system(zPushCmd);
1730
if( rc ){
1731
fossil_fatal("cannot push content using: %s", zPushCmd);
1732
}else if( db_is_writeable("repository") ){
1733
db_unprotect(PROTECT_CONFIG);
1734
db_multi_exec("REPLACE INTO config(name,value,mtime)"
1735
"VALUES('gitpush:%q','{}',now())", zPushUrl);
1736
db_protect_pop();
1737
}
1738
fossil_free(zPushCmd);
1739
}
1740
}
1741
1742
/*
1743
** Implementation of the "fossil git status" command.
1744
**
1745
** Show the status of a "git export".
1746
*/
1747
void gitmirror_status_command(void){
1748
char *zMirror;
1749
char *z;
1750
int n, k;
1751
int rc;
1752
char *zSql;
1753
int bQuiet = 0;
1754
int bByAll = 0; /* Undocumented option meaning this command was invoked
1755
** from "fossil all" and should modify output accordingly */
1756
1757
db_find_and_open_repository(0, 0);
1758
bQuiet = g.fQuiet;
1759
bByAll = find_option("by-all",0,0)!=0;
1760
verify_all_options();
1761
zMirror = db_get("last-git-export-repo", 0);
1762
if( zMirror==0 ){
1763
if( bQuiet ) return;
1764
if( bByAll ) return;
1765
fossil_print("Git mirror: none\n");
1766
return;
1767
}
1768
zSql = sqlite3_mprintf("ATTACH '%q/.mirror_state/db' AS mirror", zMirror);
1769
if( zSql==0 ) fossil_fatal("out of memory");
1770
g.dbIgnoreErrors++;
1771
rc = sqlite3_exec(g.db, zSql, 0, 0, 0);
1772
g.dbIgnoreErrors--;
1773
sqlite3_free(zSql);
1774
if( rc ){
1775
if( bQuiet ) return;
1776
if( bByAll ) return;
1777
fossil_print("Git mirror: %s (Inactive)\n", zMirror);
1778
return;
1779
}
1780
if( bByAll ){
1781
size_t len = strlen(g.zRepositoryName);
1782
int n;
1783
if( len>60 ) len = 60;
1784
n = (int)(65 - len);
1785
fossil_print("%.12c %s %.*c\n", '*', g.zRepositoryName, n, '*');
1786
}
1787
fossil_print("Git mirror: %s\n", zMirror);
1788
z = db_text(0, "SELECT datetime(value) FROM mconfig WHERE key='start'");
1789
if( z ){
1790
double rAge = db_double(0.0, "SELECT julianday('now') - value"
1791
" FROM mconfig WHERE key='start'");
1792
if( rAge>1.0/86400.0 ){
1793
fossil_print("Last export: %s (%z ago)\n", z, human_readable_age(rAge));
1794
}else{
1795
fossil_print("Last export: %s (moments ago)\n", z);
1796
}
1797
}
1798
z = db_text(0, "SELECT value FROM mconfig WHERE key='autopush'");
1799
if( z==0 ){
1800
fossil_print("Autopush: off\n");
1801
}else{
1802
UrlData url;
1803
if( sqlite3_strglob("http*", z)==0 ){
1804
url_parse_local(z, 0, &url);
1805
fossil_print("Autopush: %s\n", url.canonical);
1806
}else{
1807
fossil_print("Autopush: %s\n", z);
1808
}
1809
fossil_free(z);
1810
}
1811
n = db_int(0,
1812
"SELECT count(*) FROM event"
1813
" WHERE type='ci'"
1814
" AND mtime>coalesce((SELECT value FROM mconfig"
1815
" WHERE key='start'),0.0)"
1816
);
1817
z = db_text("master", "SELECT value FROM mconfig WHERE key='mainbranch'");
1818
fossil_print("Main-Branch: %s\n",z);
1819
if( n==0 ){
1820
fossil_print("Status: up-to-date\n");
1821
}else{
1822
fossil_print("Status: %d check-in%s awaiting export\n",
1823
n, n==1 ? "" : "s");
1824
}
1825
n = db_int(0, "SELECT count(*) FROM mmark WHERE isfile");
1826
k = db_int(0, "SELECT count(*) FROM mmark WHERE NOT isfile");
1827
fossil_print("Exported: %d check-ins and %d file blobs\n", k, n);
1828
}
1829
1830
/*
1831
** COMMAND: git*
1832
**
1833
** Usage: %fossil git SUBCOMMAND
1834
**
1835
** Do incremental import or export operations between Fossil and Git.
1836
** Subcommands:
1837
**
1838
** > fossil git export [MIRROR] [OPTIONS]
1839
**
1840
** Write content from the Fossil repository into the Git repository
1841
** in directory MIRROR. The Git repository is created if it does not
1842
** already exist. If the Git repository does already exist, then
1843
** new content added to fossil since the previous export is appended.
1844
**
1845
** Repeat this command whenever new check-ins are added to the Fossil
1846
** repository in order to reflect those changes into the mirror. If
1847
** the MIRROR option is omitted, the repository from the previous
1848
** invocation is used.
1849
**
1850
** The MIRROR directory will contain a subdirectory named
1851
** ".mirror_state" that contains information that Fossil needs to
1852
** do incremental exports. Do not attempt to manage or edit the files
1853
** in that directory since doing so can disrupt future incremental
1854
** exports.
1855
**
1856
** Options:
1857
** --autopush URL Automatically do a 'git push' to URL. The
1858
** URL is remembered and used on subsequent exports
1859
** to the same repository. Or if URL is "off" the
1860
** auto-push mechanism is disabled
1861
** --debug FILE Write fast-export text to FILE rather than
1862
** piping it into "git fast-import"
1863
** -f|--force Do the export even if nothing has changed
1864
** --if-mirrored No-op if the mirror does not already exist
1865
** --limit N Add no more than N new check-ins to MIRROR.
1866
** Useful for debugging
1867
** --mainbranch NAME Use NAME as the name of the main branch in Git.
1868
** The "trunk" branch of the Fossil repository is
1869
** mapped into this name. "master" is used if
1870
** this option is omitted.
1871
** -q|--quiet Reduce output. Repeat for even less output.
1872
** -v|--verbose More output
1873
**
1874
** > fossil git import MIRROR
1875
**
1876
** TBD...
1877
**
1878
** > fossil git status
1879
**
1880
** Show the status of the current Git mirror, if there is one.
1881
**
1882
** -q|--quiet No output if there is nothing to report
1883
*/
1884
void gitmirror_command(void){
1885
char *zCmd;
1886
int nCmd;
1887
if( g.argc<3 ){
1888
usage("SUBCOMMAND ...");
1889
}
1890
zCmd = g.argv[2];
1891
nCmd = (int)strlen(zCmd);
1892
if( nCmd>2 && strncmp(zCmd,"export",nCmd)==0 ){
1893
gitmirror_export_command();
1894
}else
1895
if( nCmd>2 && strncmp(zCmd,"import",nCmd)==0 ){
1896
fossil_fatal("not yet implemented - check back later");
1897
}else
1898
if( nCmd>2 && strncmp(zCmd,"status",nCmd)==0 ){
1899
gitmirror_status_command();
1900
}else
1901
{
1902
fossil_fatal("unknown subcommand \"%s\": should be one of "
1903
"\"export\", \"import\", \"status\"",
1904
zCmd);
1905
}
1906
}
1907

Keyboard Shortcuts

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