Fossil SCM

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

Keyboard Shortcuts

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