Index: src/import.c ================================================================== --- src/import.c +++ src/import.c @@ -28,13 +28,13 @@ */ struct ImportFile { char *zName; /* Name of a file */ char *zUuid; /* UUID of the file */ char *zPrior; /* Prior name if the name was changed */ - char isFrom; /* True if obtained from the parent */ char isExe; /* True if executable */ char isLink; /* True if symlink */ + char hasChanged; /* True when different than baseline */ }; #endif /* @@ -41,10 +41,11 @@ ** State information about an on-going fast-import parse. */ static struct { void (*xFinish)(void); /* Function to finish a prior record */ int nData; /* Bytes of data */ + char *zBaseline; /* Baseline manifest. The B card. */ char *zTag; /* Name of a tag */ char *zBranch; /* Name of a branch for a commit */ char *zPrevBranch; /* The branch of the previous check-in */ char *aData; /* Data content */ char *zMark; /* The current mark */ @@ -57,14 +58,17 @@ int nMerge; /* Number of merge values */ int nMergeAlloc; /* Number of slots in azMerge[] */ char **azMerge; /* Merge values */ int nFile; /* Number of aFile values */ int nFileAlloc; /* Number of slots in aFile[] */ + int nFileEffective; /* Number of aFile items with zUuid != 0 */ + int nChanged; /* Number of aFile that differ from baseline */ ImportFile *aFile; /* Information about files in a commit */ int fromLoaded; /* True zFrom content loaded into aFile[] */ int hasLinks; /* True if git repository contains symlinks */ int tagCommit; /* True if the commit adds a tag */ + int tryDelta; /* Attempt to generate delta manifests */ } gg; /* ** Duplicate a string. */ @@ -90,10 +94,11 @@ ** retained unless the freeAll flag is set. */ static void import_reset(int freeAll){ int i; gg.xFinish = 0; + fossil_free(gg.zBaseline); gg.zBaseline = 0; fossil_free(gg.zTag); gg.zTag = 0; fossil_free(gg.zBranch); gg.zBranch = 0; fossil_free(gg.aData); gg.aData = 0; fossil_free(gg.zMark); gg.zMark = 0; fossil_free(gg.zDate); gg.zDate = 0; @@ -107,12 +112,15 @@ gg.nMerge = 0; for(i=0; izUuid==0) || (delta && !pFile->hasChanged) ) continue; + blob_appendf(&record, "F %F", pFile->zName); + if( pFile->zUuid!=0 ) { + blob_appendf(&record, " %s", pFile->zUuid); + if( pFile->isExe ){ + blob_append(&record, " x", 2); + }else if( pFile->isLink ){ + blob_append(&record, " l", 2); + } + if( pFile->zPrior!=0 ){ + if( !pFile->isExe && !pFile->isLink ){ + blob_append(&record, " w", 2); + } + blob_appendf(&record, " %F", pFile->zPrior); + } + } + blob_append(&record, "\n", 1); } if( gg.zFrom ){ blob_appendf(&record, "P %s", gg.zFrom); for(i=0; i '7' ) + fossil_fatal("Invalid octal digit '%c' in sequence", c); + v |= (c - '0') << 3; + c = z[++i]; + if( c < '0' || c > '7' ) + fossil_fatal("Invalid octal digit '%c' in sequence", c); + v |= (c - '0'); + c = v; + break; + default: + fossil_fatal("Unrecognized escape sequence \"\\%c\"", c); + } + z[j] = c; + } + } + if( z[i]=='"' ) z[i++] = 0; + }else{ + /* Unquoted path name or generic token */ + for(i=0; z[i] && z[i]!=' ' && z[i]!='\n'; i++){} + } + if( z[i] ){ + z[i] = 0; *pzIn = &z[i+1]; }else{ *pzIn = &z[i]; } return z; @@ -422,17 +464,55 @@ rid = fast_uuid_to_rid(gg.zFrom); if( rid==0 ) return; p = manifest_get(rid, CFTYPE_MANIFEST); if( p==0 ) return; manifest_file_rewind(p); - while( (pOld = manifest_file_next(p, 0))!=0 ){ - pNew = import_add_file(); - pNew->zName = fossil_strdup(pOld->zName); - pNew->isExe = pOld->zPerm && strstr(pOld->zPerm, "x")!=0; - pNew->isLink = pOld->zPerm && strstr(pOld->zPerm, "l")!=0; - pNew->zUuid = fossil_strdup(pOld->zUuid); - pNew->isFrom = 1; + if( gg.tryDelta && p->pBaseline ){ + /* + ** The manifest_file_next() iterator skips deletion "F" cards in delta + ** manifests. But, in order to build more delta manifests, this information + ** is necessary because it propagates. Therefore, in this case, the + ** manifest has to be traversed "manually". + */ + Manifest *pB = p->pBaseline; + gg.zBaseline = fossil_strdup(p->zBaseline); + while( p->iFilenFile || pB->iFilenFile ){ + pNew = import_add_file(); + if( p->iFile>=p->nFile ){ + /* No more "F" cards in delta manifest, finish the baseline */ + pOld = &pB->aFile[pB->iFile++]; + }else if( pB->iFile>=pB->nFile ){ + /* No more "F" cards in baseline, finish the delta manifest */ + pOld = &p->aFile[p->iFile++]; + pNew->hasChanged = 1; + }else{ + int cmp = fossil_strcmp(pB->aFile[pB->iFile].zName, + p->aFile[p->iFile].zName); + if( cmp < 0 ){ + pOld = &pB->aFile[pB->iFile++]; + }else if( cmp >= 0 ){ + pOld = &p->aFile[p->iFile++]; + pNew->hasChanged = 1; + if( cmp==0 ) pB->iFile++; + } + } + pNew->zName = fossil_strdup(pOld->zName); + pNew->isExe = pOld->zPerm && strstr(pOld->zPerm, "x")!=0; + pNew->isLink = pOld->zPerm && strstr(pOld->zPerm, "l")!=0; + pNew->zUuid = fossil_strdup(pOld->zUuid); + gg.nChanged += pNew->hasChanged; + if( pNew->zUuid!=0 ) gg.nFileEffective++; + } + }else{ + while( (pOld = manifest_file_next(p, 0))!=0 ){ + pNew = import_add_file(); + pNew->zName = fossil_strdup(pOld->zName); + pNew->isExe = pOld->zPerm && strstr(pOld->zPerm, "x")!=0; + pNew->isLink = pOld->zPerm && strstr(pOld->zPerm, "l")!=0; + pNew->zUuid = fossil_strdup(pOld->zUuid); + if( pNew->zUuid!=0 ) gg.nFileEffective++; + } } manifest_destroy(p); } /* @@ -452,35 +532,18 @@ i++; } return 0; } -/* -** Dequote a fast-export filename. Filenames are normally unquoted. But -** if the contain some obscure special characters, quotes might be added. -*/ -static void dequote_git_filename(char *zName){ - int n, i, j; - if( zName==0 || zName[0]!='"' ) return; - n = (int)strlen(zName); - if( zName[n-1]!='"' ) return; - for(i=0, j=1; jzName = fossil_strdup(zName); + gg.nFileEffective++; } pFile->isExe = (fossil_strcmp(zPerm, "100755")==0); pFile->isLink = (fossil_strcmp(zPerm, "120000")==0); fossil_free(pFile->zUuid); pFile->zUuid = resolve_committish(zUuid); - pFile->isFrom = 0; + /* + ** This trick avoids counting multiple changes on the same filename as + ** changes to multiple filenames. When an entry in gg.aFile is already + ** different from its baseline, it is not counted again. + */ + gg.nChanged += 1 - pFile->hasChanged; + pFile->hasChanged = 1; }else if( memcmp(zLine, "D ", 2)==0 ){ import_prior_files(); z = &zLine[2]; - zName = rest_of_line(&z); - dequote_git_filename(zName); + zName = next_token(&z); i = 0; - while( (pFile = import_find_file(zName, &i, gg.nFile))!=0 ){ - if( pFile->isFrom==0 ) continue; - fossil_free(pFile->zName); - fossil_free(pFile->zPrior); + pFile = import_find_file(zName, &i, gg.nFile); + if( pFile!=0 ){ + /* Do not remove the item from gg.aFile, just mark as deleted */ fossil_free(pFile->zUuid); - *pFile = gg.aFile[--gg.nFile]; - i--; + pFile->zUuid = 0; + gg.nChanged += 1 - pFile->hasChanged; + pFile->hasChanged = 1; + gg.nFileEffective--; } }else if( memcmp(zLine, "C ", 2)==0 ){ - int nFrom; import_prior_files(); z = &zLine[2]; zFrom = next_token(&z); - zTo = rest_of_line(&z); + zTo = next_token(&z); i = 0; - mx = gg.nFile; - nFrom = strlen(zFrom); - while( (pFile = import_find_file(zFrom, &i, mx))!=0 ){ - if( pFile->isFrom==0 ) continue; - pNew = import_add_file(); - pFile = &gg.aFile[i-1]; - if( strlen(pFile->zName)>nFrom ){ - pNew->zName = mprintf("%s%s", zTo, pFile->zName[nFrom]); + pFile = import_find_file(zFrom, &i, gg.nFile); + if( pFile!=0 ){ + int j = 0; + pNew = import_find_file(zTo, &j, gg.nFile); + if( pNew==0 ){ + pNew = import_add_file(); + pFile = &gg.aFile[i-1]; /* gg.aFile may have been realloc()-ed */ + pNew->zName = fossil_strdup(zTo); + gg.nFileEffective++; }else{ - pNew->zName = fossil_strdup(pFile->zName); + fossil_free(pNew->zUuid); } pNew->isExe = pFile->isExe; pNew->isLink = pFile->isLink; pNew->zUuid = fossil_strdup(pFile->zUuid); - pNew->isFrom = 0; + gg.nChanged += 1 - pNew->hasChanged; + pNew->hasChanged = 1; } }else if( memcmp(zLine, "R ", 2)==0 ){ - int nFrom; import_prior_files(); z = &zLine[2]; zFrom = next_token(&z); - zTo = rest_of_line(&z); + zTo = next_token(&z); i = 0; - nFrom = strlen(zFrom); - while( (pFile = import_find_file(zFrom, &i, gg.nFile))!=0 ){ - if( pFile->isFrom==0 ) continue; - pNew = import_add_file(); - pFile = &gg.aFile[i-1]; - if( strlen(pFile->zName)>nFrom ){ - pNew->zName = mprintf("%s%s", zTo, pFile->zName[nFrom]); + pFile = import_find_file(zFrom, &i, gg.nFile); + if( pFile!=0 ){ + /* + ** File renames in delta manifests require two "F" cards: one to + ** delete the old file (without UUID) and another with the rename + ** (with prior name equals to the name in the other card). + ** + ** This forces us to also lookup by the destination name, as it may + ** already exist in the form of a delta manifest deletion "F" card. + */ + int j = 0; + pNew = import_find_file(zTo, &j, gg.nFile); + if( pNew==0 ){ + pNew = import_add_file(); + pFile = &gg.aFile[i-1]; + pNew->zName = fossil_strdup(zTo); }else{ - pNew->zName = fossil_strdup(pFile->zName); + fossil_free(pNew->zUuid); /* Just in case */ } - pNew->zPrior = pFile->zName; pNew->isExe = pFile->isExe; pNew->isLink = pFile->isLink; + pNew->zPrior = fossil_strdup(zFrom); pNew->zUuid = pFile->zUuid; - pNew->isFrom = 0; - gg.nFile--; - *pFile = *pNew; - memset(pNew, 0, sizeof(*pNew)); + pFile->zUuid = 0; + gg.nChanged += 2 - (pNew->hasChanged + pFile->hasChanged); + pNew->hasChanged = 1; + pFile->hasChanged = 1; } - fossil_fatal("cannot handle R records, use --full-tree"); }else if( memcmp(zLine, "deleteall", 9)==0 ){ gg.fromLoaded = 1; }else if( memcmp(zLine, "N ", 2)==0 ){ @@ -720,25 +797,35 @@ } /* ** COMMAND: import ** -** Usage: %fossil import --git ?OPTIONS? NEW-REPOSITORY +** Usage: %fossil import --git ?OPTIONS? NEW-REPOSITORY ?FILE? ** ** Read text generated by the git-fast-export command and use it to ** construct a new Fossil repository named by the NEW-REPOSITORY -** argument. The git-fast-export text is read from standard input. +** argument. If given, the git-fast-export text is read from the FILE argument, +** otherwise text is read from standard input. ** ** The git-fast-export file format is currently the only VCS interchange ** format that is understood, though other interchange formats may be added ** in the future. ** ** The --incremental option allows an existing repository to be extended -** with new content. +** with new content. Otherwise, if a file with the same name as NEW-REPOSITORY +** is found, the command fails unless the --force option is used. +** +** When the --delta option is used, delta manifests will be generated when they +** are smaller than the equivalent baseline manifest. Please beware that delta +** manifests are not understood by older versions of Fossil. Therefore, only +** use this option when it can be assured that only newer clients will pull or +** read from it. ** ** Options: -** --incremental allow importing into an existing repository +** -d|--delta enable delta manifest generation +** -f|--force remove existing file +** -i|--incremental allow importing into an existing repository ** ** See also: export */ void git_import_cmd(void){ char *zPassword; @@ -746,10 +833,11 @@ Stmt q; int forceFlag = find_option("force", "f", 0)!=0; int incrFlag = find_option("incremental", "i", 0)!=0; find_option("git",0,0); /* Skip the --git option for now */ + gg.tryDelta = find_option("delta", "d", 0)!=0; verify_all_options(); if( g.argc!=3 && g.argc!=4 ){ usage("REPOSITORY-NAME"); } if( g.argc==4 ){