Index: src/export.c ================================================================== --- src/export.c +++ src/export.c @@ -19,10 +19,17 @@ */ #include "config.h" #include "export.h" #include +/* +** State information common to all export types. +*/ +static struct { + const char *zTrunkName; /* Name of trunk branch */ +} gexport; + #if INTERFACE /* ** struct mark_t ** holds information for translating between git commits ** and fossil commits. @@ -161,10 +168,68 @@ printf(" %s <%s>", zName, zEmail); free(zName); free(zEmail); db_reset(&q); } + +#define REFREPLACEMENT '_' + +/* +** Output a sanitized git named reference. +** https://git-scm.com/docs/git-check-ref-format +** This implementation assumes we are only printing +** the branch or tag part of the reference. +*/ +static void print_ref(const char *zRef){ + char *zEncoded = mprintf("%s", zRef); + int i, w; + if (zEncoded[0]=='@' && zEncoded[1]=='\0'){ + putchar(REFREPLACEMENT); + return; + } + for(i=0, w=0; zEncoded[i]; i++, w++){ + if( i!=0 ){ /* Two letter tests */ + if( (zEncoded[i-1]=='.' && zEncoded[i]=='.') || + (zEncoded[i-1]=='@' && zEncoded[i]=='{') ){ + zEncoded[w]=zEncoded[w-1]=REFREPLACEMENT; + continue; + } + if( zEncoded[i-1]=='/' && zEncoded[i]=='/' ){ + w--; /* Normalise to a single / by rolling back w */ + continue; + } + } + /* No control characters */ + if( (unsigned)zEncoded[i]<0x20 || zEncoded[i]==0x7f ){ + zEncoded[w]=REFREPLACEMENT; + continue; + } + switch( zEncoded[i] ){ + case ' ': + case '^': + case ':': + case '?': + case '*': + case '[': + case '\\': + zEncoded[w]=REFREPLACEMENT; + break; + } + } + /* Cannot begin with a . or / */ + if( zEncoded[0]=='.' || zEncoded[0] == '/' ) zEncoded[0]=REFREPLACEMENT; + if( i>0 ){ + i--; w--; + /* Or end with a . or / */ + if( zEncoded[i]=='.' || zEncoded[i] == '/' ) zEncoded[w]=REFREPLACEMENT; + /* Cannot end with .lock */ + if ( i>4 && strcmp((zEncoded+i)-5, ".lock")==0 ) + memset((zEncoded+w)-5, REFREPLACEMENT, 5); + } + printf("%s", zEncoded); + free(zEncoded); +} #define BLOBMARK(rid) ((rid) * 2) #define COMMITMARK(rid) ((rid) * 2 + 1) /* @@ -413,10 +478,11 @@ ** blobs written on exit for use with "--import-marks" on the next run. ** ** Options: ** --export-marks FILE export rids of exported data to FILE ** --import-marks FILE read rids of data to ignore from FILE +** --rename-trunk NAME use NAME as name of exported trunk branch ** --repository|-R REPOSITORY export the given REPOSITORY ** ** See also: import */ void export_cmd(void){ @@ -431,10 +497,14 @@ bag_init(&vers); find_option("git", 0, 0); /* Ignore the --git option for now */ markfile_in = find_option("import-marks", 0, 1); markfile_out = find_option("export-marks", 0, 1); + + if( !(gexport.zTrunkName = find_option("rename-trunk", 0, 1)) ){ + gexport.zTrunkName = "trunk"; + } db_find_and_open_repository(0, 2); verify_all_options(); if( g.argc!=2 && g.argc!=3 ){ usage("--git ?REPOSITORY?"); } @@ -547,26 +617,22 @@ const char *zSecondsSince1970 = db_column_text(&q, 0); int ckinId = db_column_int(&q, 1); const char *zComment = db_column_text(&q, 2); const char *zUser = db_column_text(&q, 3); const char *zBranch = db_column_text(&q, 4); - char *zBr; char *zMark; bag_insert(&vers, ckinId); db_bind_int(&q2, ":rid", ckinId); db_step(&q2); db_reset(&q2); - if( zBranch==0 ) zBranch = "trunk"; - zBr = mprintf("%s", zBranch); - for(i=0; zBr[i]; i++){ - if( !fossil_isalnum(zBr[i]) ) zBr[i] = '_'; - } + if( zBranch==0 || fossil_strcmp(zBranch, "trunk")==0 ) zBranch = gexport.zTrunkName; zMark = mark_name_from_rid(ckinId, &unused_mark); - printf("commit refs/heads/%s\nmark %s\n", zBr, zMark); + printf("commit refs/heads/"); + print_ref(zBranch); + printf("\nmark %s\n", zMark); free(zMark); - free(zBr); printf("committer"); print_person(zUser); printf(" %s +0000\n", zSecondsSince1970); if( zComment==0 ) zComment = "null comment"; printf("data %d\n%s\n", (int)strlen(zComment), zComment); @@ -630,33 +696,34 @@ manifest_cache_clear(); /* Output tags */ db_prepare(&q, - "SELECT tagname, rid, strftime('%%s',mtime)" + "SELECT tagname, rid, strftime('%%s',mtime)," + " (SELECT coalesce(euser, user) FROM event WHERE objid=rid)," + " value" " FROM tagxref JOIN tag USING(tagid)" " WHERE tagtype=1 AND tagname GLOB 'sym-*'" ); while( db_step(&q)==SQLITE_ROW ){ const char *zTagname = db_column_text(&q, 0); - char *zEncoded = 0; int rid = db_column_int(&q, 1); char *zMark = mark_name_from_rid(rid, &unused_mark); const char *zSecSince1970 = db_column_text(&q, 2); - int i; + const char *zUser = db_column_text(&q, 3); + const char *zValue = db_column_text(&q, 4); if( rid==0 || !bag_find(&vers, rid) ) continue; zTagname += 4; - zEncoded = mprintf("%s", zTagname); - for(i=0; zEncoded[i]; i++){ - if( !fossil_isalnum(zEncoded[i]) ) zEncoded[i] = '_'; - } - printf("tag %s\n", zEncoded); - printf("from %s\n", zMark); + printf("tag "); + print_ref(zTagname); + printf("\nfrom %s\n", zMark); free(zMark); - printf("tagger %s +0000\n", zSecSince1970); - printf("data 0\n"); - fossil_free(zEncoded); + printf("tagger"); + print_person(zUser); + printf(" %s +0000\n", zSecSince1970); + printf("data %d\n", zValue==NULL?0:strlen(zValue)+1); + if( zValue!=NULL ) printf("%s\n",zValue); } db_finalize(&q); if( markfile_out!=0 ){ FILE *f; Index: src/import.c ================================================================== --- src/import.c +++ src/import.c @@ -216,13 +216,16 @@ static void finish_tag(void){ Blob record, cksum; if( gg.zDate && gg.zTag && gg.zFrom && gg.zUser ){ blob_zero(&record); blob_appendf(&record, "D %s\n", gg.zDate); - blob_appendf(&record, "T +%F%F%F %s\n", gimport.zTagPre, gg.zTag, + blob_appendf(&record, "T +sym-%F%F%F %s", gimport.zTagPre, gg.zTag, gimport.zTagSuf, gg.zFrom); - blob_appendf(&record, "U %F\n", gg.zUser); + if( gg.zComment ){ + blob_appendf(&record, " %F", gg.zComment); + } + blob_appendf(&record, "\nU %F\n", gg.zUser); md5sum_blob(&record, &cksum); blob_appendf(&record, "Z %b\n", &cksum); fast_insert_content(&record, 0, 0, 1); blob_reset(&cksum); } @@ -513,10 +516,14 @@ } zName[i] = 0; } +static struct{ + const char *zMasterName; /* Name of master branch */ +} ggit; + /* ** Read the git-fast-import format from pIn and insert the corresponding ** content into the database. */ static void git_fast_import(FILE *pIn){ @@ -536,14 +543,15 @@ if( strncmp(zLine, "blob", 4)==0 ){ gg.xFinish(); gg.xFinish = finish_blob; }else if( strncmp(zLine, "commit ", 7)==0 ){ + const char *zRefName; gg.xFinish(); gg.xFinish = finish_commit; trim_newline(&zLine[7]); - z = &zLine[7]; + zRefName = &zLine[7]; /* The argument to the "commit" line might match either of these ** patterns: ** ** (A) refs/heads/BRANCHNAME @@ -559,15 +567,15 @@ ** last commit that holds that tag. ** ** None of the above is explained in the git-fast-export ** documentation. We had to figure it out via trial and error. */ - for(i=5; i'))==NULL ) goto malformed_line; + *(++zTo) = '\0'; + /* Lookup user by contact info. */ fossil_free(gg.zUser); - gg.zUser = fossil_strdup(z); + gg.zUser = db_text(0, "SELECT login FROM user WHERE info=%Q", z); + if( gg.zUser==NULL ){ + /* If there is no user with this contact info, + * then use the email address as the username. */ + if ( (z=strchr(z, '<'))==NULL ) goto malformed_line; + z++; + *(zTo-1) = '\0'; + gg.zUser = fossil_strdup(z); + } secSince1970 = 0; - for(i=i+2; fossil_isdigit(zLine[i]); i++){ - secSince1970 = secSince1970*10 + zLine[i] - '0'; + for(zTo++; fossil_isdigit(*zTo); zTo++){ + secSince1970 = secSince1970*10 + *zTo - '0'; } fossil_free(gg.zDate); gg.zDate = db_text(0, "SELECT datetime(%lld, 'unixepoch')", secSince1970); gg.zDate[10] = 'T'; }else @@ -1545,12 +1564,13 @@ ** ** The following formats are currently understood by this command ** ** --git Import from the git-fast-export file format (default) ** Options: -** --import-marks FILE Restore marks table from FILE -** --export-marks FILE Save marks table to FILE +** --import-marks FILE Restore marks table from FILE +** --export-marks FILE Save marks table to FILE +** --rename-master NAME Renames the master branch to NAME ** ** --svn Import from the svnadmin-dump file format. The default ** behaviour (unless overridden by --flat) is to treat 3 ** folders in the SVN root as special, following the ** common layout of SVN repositories. These are (by @@ -1668,10 +1688,13 @@ gsvn.revFlag = find_option("rev-tags", 0, 0) || (incrFlag && !find_option("no-rev-tags", 0, 0)); }else if( gitFlag ){ markfile_in = find_option("import-marks", 0, 1); markfile_out = find_option("export-marks", 0, 1); + if( !(ggit.zMasterName = find_option("rename-master", 0, 1)) ){ + ggit.zMasterName = "master"; + } } verify_all_options(); if( g.argc!=3 && g.argc!=4 ){ usage("--git|--svn ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?"); @@ -1688,11 +1711,14 @@ } db_open_repository(g.argv[2]); db_open_config(0, 0); db_begin_transaction(); - if( !incrFlag ) db_initial_setup(0, 0, 0); + if( !incrFlag ){ + db_initial_setup(0, 0, 0); + db_set("main-branch", gimport.zTrunkName, 0); + } if( svnFlag ){ db_multi_exec( "CREATE TEMP TABLE xrevisions(" " trev INTEGER, tbranch INT, trid INT, tparent INT DEFAULT 0,"