/* ** Load all file information out of the gg.zFrom check-in */ static void import_prior_files(void){ Manifest *p; int rid; ManifestFile *pOld; ImportFile *pNew; if( gg.fromLoaded ) return; gg.fromLoaded = 1; if( gg.zFrom==0 && gg.zPrevCheckin!=0 && fossil_strcmp(gg.zBranch, gg.zPrevBranch)==0 ){ gg.zFrom = gg.zPrevCheckin; gg.zPrevCheckin = 0; } if( gg.zFrom==0 ) return; rid = fast_uuid_to_rid(gg.zFrom); if( rid==0 ) return; p = manifest_get(rid, CFTYPE_MANIFEST, 0); 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; } manifest_destroy(p); }
/* ** COMMAND: leaves* ** ** Usage: %fossil leaves ?OPTIONS? ** ** Find leaves of all branches. By default show only open leaves. ** The -a|--all flag causes all leaves (closed and open) to be shown. ** The -c|--closed flag shows only closed leaves. ** ** The --recompute flag causes the content of the "leaf" table in the ** repository database to be recomputed. ** ** Options: ** -a|--all show ALL leaves ** -c|--closed show only closed leaves ** --bybranch order output by branch name ** --recompute recompute the "leaf" table in the repository DB ** ** See also: descendants, finfo, info, branch */ void leaves_cmd(void){ Stmt q; Blob sql; int showAll = find_option("all", "a", 0)!=0; int showClosed = find_option("closed", "c", 0)!=0; int recomputeFlag = find_option("recompute",0,0)!=0; int byBranch = find_option("bybranch",0,0)!=0; char *zLastBr = 0; int n; char zLineNo[10]; db_find_and_open_repository(0,0); if( recomputeFlag ) leaf_rebuild(); blob_zero(&sql); blob_append(&sql, timeline_query_for_tty(), -1); blob_appendf(&sql, " AND blob.rid IN leaf"); if( showClosed ){ blob_appendf(&sql," AND %z", leaf_is_closed_sql("blob.rid")); }else if( !showAll ){ blob_appendf(&sql," AND NOT %z", leaf_is_closed_sql("blob.rid")); } if( byBranch ){ db_prepare(&q, "%s ORDER BY nullif(branch,'trunk') COLLATE nocase," " event.mtime DESC", blob_str(&sql)); }else{ db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_str(&sql)); } blob_reset(&sql); n = 0; while( db_step(&q)==SQLITE_ROW ){ const char *zId = db_column_text(&q, 1); const char *zDate = db_column_text(&q, 2); const char *zCom = db_column_text(&q, 3); const char *zBr = db_column_text(&q, 7); char *z; if( byBranch && fossil_strcmp(zBr, zLastBr)!=0 ){ fossil_print("*** %s ***\n", zBr); fossil_free(zLastBr); zLastBr = fossil_strdup(zBr); } n++; sqlite3_snprintf(sizeof(zLineNo), zLineNo, "(%d)", n); fossil_print("%6s ", zLineNo); z = mprintf("%s [%.10s] %s", zDate, zId, zCom); comment_print(z, 7, 79); fossil_free(z); } fossil_free(zLastBr); db_finalize(&q); }
/* ** Create empty directories specified by the empty-dirs setting. */ void ensure_empty_dirs_created(void) { char *zEmptyDirs = db_get("empty-dirs", 0); if( zEmptyDirs!=0 ) { int i; Blob dirName; Blob dirsList; zEmptyDirs = fossil_strdup(zEmptyDirs); for(i=0; zEmptyDirs[i]; i++) { if( zEmptyDirs[i]==',' ) zEmptyDirs[i] = ' '; } blob_init(&dirsList, zEmptyDirs, -1); while( blob_token(&dirsList, &dirName) ) { char *zDir = blob_str(&dirName); char *zPath = mprintf("%s/%s", g.zLocalRoot, zDir); switch( file_wd_isdir(zPath) ) { case 0: { /* doesn't exist */ fossil_free(zPath); zPath = mprintf("%s/%s/x", g.zLocalRoot, zDir); if( file_mkfolder(zPath, 0, 1)!=0 ) { fossil_warning("couldn't create directory %s as " "required by empty-dirs setting", zDir); } break; } case 1: { /* exists, and is a directory */ /* do nothing - required directory exists already */ break; } case 2: { /* exists, but isn't a directory */ fossil_warning("file %s found, but a directory is required " "by empty-dirs setting", zDir); } } fossil_free(zPath); blob_reset(&dirName); } blob_reset(&dirsList); fossil_free(zEmptyDirs); } }
/* ** Insert an artifact into the BLOB table if it isn't there already. ** If zMark is not zero, create a cross-reference from that mark back ** to the newly inserted artifact. ** ** If saveUuid is true, then pContent is a commit record. Record its ** UUID in gg.zPrevCheckin. */ static int fast_insert_content(Blob *pContent, const char *zMark, int saveUuid){ Blob hash; Blob cmpr; int rid; sha1sum_blob(pContent, &hash); rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &hash); if( rid==0 ){ static Stmt ins; db_static_prepare(&ins, "INSERT INTO blob(uuid, size, content) VALUES(:uuid, :size, :content)" ); db_bind_text(&ins, ":uuid", blob_str(&hash)); db_bind_int(&ins, ":size", gg.nData); blob_compress(pContent, &cmpr); db_bind_blob(&ins, ":content", &cmpr); db_step(&ins); db_reset(&ins); blob_reset(&cmpr); rid = db_last_insert_rowid(); } if( zMark ){ db_multi_exec( "INSERT OR IGNORE INTO xmark(tname, trid, tuuid)" "VALUES(%Q,%d,%B)", zMark, rid, &hash ); db_multi_exec( "INSERT OR IGNORE INTO xmark(tname, trid, tuuid)" "VALUES(%B,%d,%B)", &hash, rid, &hash ); } if( saveUuid ){ fossil_free(gg.zPrevCheckin); gg.zPrevCheckin = fossil_strdup(blob_str(&hash)); } blob_reset(&hash); return rid; }
/* ** Impl of /json/dir. 98% of it was taken directly ** from browse.c::page_dir() */ static cson_value * json_page_dir_list(){ cson_object * zPayload = NULL; /* return value */ cson_array * zEntries = NULL; /* accumulated list of entries. */ cson_object * zEntry = NULL; /* a single dir/file entry. */ cson_array * keyStore = NULL; /* garbage collector for shared strings. */ cson_string * zKeyName = NULL; cson_string * zKeySize = NULL; cson_string * zKeyIsDir = NULL; cson_string * zKeyUuid = NULL; cson_string * zKeyTime = NULL; cson_string * zKeyRaw = NULL; char * zD = NULL; char const * zDX = NULL; int nD; char * zUuid = NULL; char const * zCI = NULL; Manifest * pM = NULL; Stmt q = empty_Stmt; int rid = 0; if( !g.perm.Read ){ json_set_err(FSL_JSON_E_DENIED, "Requires 'o' permissions."); return NULL; } zCI = json_find_option_cstr("checkin",NULL,"ci" ); /* If a specific check-in is requested, fetch and parse it. If the ** specific check-in does not exist, clear zCI. zCI==0 will cause all ** files from all check-ins to be displayed. */ if( zCI && *zCI ){ pM = manifest_get_by_name(zCI, &rid); if( pM ){ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); }else{ json_set_err(FSL_JSON_E_UNRESOLVED_UUID, "Checkin name [%s] is unresolved.", zCI); return NULL; } } /* Jump through some hoops to find the directory name... */ zDX = json_find_option_cstr("name",NULL,NULL); if(!zDX && !g.isHTTP){ zDX = json_command_arg(g.json.dispatchDepth+1); } if(zDX && (!*zDX || (0==strcmp(zDX,"/")))){ zDX = NULL; } zD = zDX ? fossil_strdup(zDX) : NULL; nD = zD ? strlen(zD)+1 : 0; while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, pathelementFunc, 0, 0); /* Compute the temporary table "localfiles" containing the names ** of all files and subdirectories in the zD[] directory. ** ** Subdirectory names begin with "/". This causes them to sort ** first and it also gives us an easy way to distinguish files ** from directories in the loop that follows. */ if( zCI ){ Stmt ins; ManifestFile *pFile; ManifestFile *pPrev = 0; int nPrev = 0; int c; db_multi_exec( "CREATE TEMP TABLE json_dir_files(" " n UNIQUE NOT NULL," /* file name */ " fn UNIQUE NOT NULL," /* full file name */ " u DEFAULT NULL," /* file uuid */ " sz DEFAULT -1," /* file size */ " mtime DEFAULT NULL" /* file mtime in unix epoch format */ ");" ); db_prepare(&ins, "INSERT OR IGNORE INTO json_dir_files (n,fn,u,sz,mtime) " "SELECT" " pathelement(:path,0)," " CASE WHEN %Q IS NULL THEN '' ELSE %Q||'/' END ||:abspath," " a.uuid," " a.size," " CAST(strftime('%%s',e.mtime) AS INTEGER) " "FROM" " mlink m, " " event e," " blob a," " blob b " "WHERE" " e.objid=m.mid" " AND a.rid=m.fid"/*FILE artifact*/ " AND b.rid=m.mid"/*CHECKIN artifact*/ " AND a.uuid=:uuid", zD, zD ); manifest_file_rewind(pM); while( (pFile = manifest_file_next(pM,0))!=0 ){ if( nD>0 && ((pFile->zName[nD-1]!='/') || (0!=memcmp(pFile->zName, zD, nD-1))) ){ continue; } /*printf("zD=%s, nD=%d, pFile->zName=%s\n", zD, nD, pFile->zName);*/ if( pPrev && memcmp(&pFile->zName[nD],&pPrev->zName[nD],nPrev)==0 && (pFile->zName[nD+nPrev]==0 || pFile->zName[nD+nPrev]=='/') ){ continue; } db_bind_text( &ins, ":path", &pFile->zName[nD] ); db_bind_text( &ins, ":abspath", &pFile->zName[nD] ); db_bind_text( &ins, ":uuid", pFile->zUuid ); db_step(&ins); db_reset(&ins); pPrev = pFile; for(nPrev=0; (c=pPrev->zName[nD+nPrev]) && c!='/'; nPrev++){} if( c=='/' ) nPrev++; } db_finalize(&ins); }else if( zD && *zD ){ db_multi_exec( "CREATE TEMP VIEW json_dir_files AS" " SELECT DISTINCT(pathelement(name,%d)) AS n," " %Q||'/'||name AS fn," " NULL AS u, NULL AS sz, NULL AS mtime" " FROM filename" " WHERE name GLOB '%q/*'" " GROUP BY n", nD, zD, zD ); }else{ db_multi_exec( "CREATE TEMP VIEW json_dir_files" " AS SELECT DISTINCT(pathelement(name,0)) AS n, NULL AS fn" " FROM filename" ); } if(zCI){ db_prepare( &q, "SELECT" " n as name," " fn as fullname," " u as uuid," " sz as size," " mtime as mtime " "FROM json_dir_files ORDER BY n"); }else{/* UUIDs are all NULL. */ db_prepare( &q, "SELECT n, fn FROM json_dir_files ORDER BY n"); } zKeyName = cson_new_string("name",4); zKeyUuid = cson_new_string("uuid",4); zKeyIsDir = cson_new_string("isDir",5); keyStore = cson_new_array(); cson_array_append( keyStore, cson_string_value(zKeyName) ); cson_array_append( keyStore, cson_string_value(zKeyUuid) ); cson_array_append( keyStore, cson_string_value(zKeyIsDir) ); if( zCI ){ zKeySize = cson_new_string("size",4); cson_array_append( keyStore, cson_string_value(zKeySize) ); zKeyTime = cson_new_string("timestamp",9); cson_array_append( keyStore, cson_string_value(zKeyTime) ); zKeyRaw = cson_new_string("downloadPath",12); cson_array_append( keyStore, cson_string_value(zKeyRaw) ); } zPayload = cson_new_object(); cson_object_set_s( zPayload, zKeyName, json_new_string((zD&&*zD) ? zD : "/") ); if( zUuid ){ cson_object_set( zPayload, "checkin", json_new_string(zUuid) ); } while( (SQLITE_ROW==db_step(&q)) ){ cson_value * name = NULL; char const * n = db_column_text(&q,0); char const isDir = ('/'==*n); zEntry = cson_new_object(); if(!zEntries){ zEntries = cson_new_array(); cson_object_set( zPayload, "entries", cson_array_value(zEntries) ); } cson_array_append(zEntries, cson_object_value(zEntry) ); if(isDir){ name = json_new_string( n+1 ); cson_object_set_s(zEntry, zKeyIsDir, cson_value_true() ); } else{ name = json_new_string( n ); } cson_object_set_s(zEntry, zKeyName, name ); if( zCI && !isDir){ /* Don't add the uuid/size for dir entries - that data refers to one of the files in that directory :/. Entries with no --checkin may refer to N versions, and therefore we cannot associate a single size and uuid with them (and fetching all would be overkill for most use cases). */ char const * fullName = db_column_text(&q,1); char const * u = db_column_text(&q,2); sqlite_int64 const sz = db_column_int64(&q,3); sqlite_int64 const ts = db_column_int64(&q,4); cson_object_set_s(zEntry, zKeyUuid, json_new_string( u ) ); cson_object_set_s(zEntry, zKeySize, cson_value_new_integer( (cson_int_t)sz )); cson_object_set_s(zEntry, zKeyTime, cson_value_new_integer( (cson_int_t)ts )); cson_object_set_s(zEntry, zKeyRaw, json_new_string_f("/raw/%T?name=%t", fullName, u)); } } db_finalize(&q); if(pM){ manifest_destroy(pM); } cson_free_array( keyStore ); free( zUuid ); free( zD ); return cson_object_value(zPayload); }
/* ** Read the git-fast-import format from pIn and insert the corresponding ** content into the database. */ static void git_fast_import(FILE *pIn){ ImportFile *pFile, *pNew; int i, mx; char *z; char *zUuid; char *zName; char *zPerm; char *zFrom; char *zTo; char zLine[1000]; gg.xFinish = finish_noop; while( fgets(zLine, sizeof(zLine), pIn) ){ if( zLine[0]=='\n' || zLine[0]=='#' ) continue; if( memcmp(zLine, "blob", 4)==0 ){ gg.xFinish(); gg.xFinish = finish_blob; }else if( memcmp(zLine, "commit ", 7)==0 ){ gg.xFinish(); gg.xFinish = finish_commit; trim_newline(&zLine[7]); z = &zLine[7]; /* The argument to the "commit" line might match either of these ** patterns: ** ** (A) refs/heads/BRANCHNAME ** (B) refs/tags/TAGNAME ** ** If pattern A is used, then the branchname used is as shown. ** Except, the "master" branch which is the default branch name in ** Git is changed to "trunk" which is the default name in Fossil. ** If the pattern is B, then the new commit should be on the same ** branch as its parent. And, we might need to add the TAGNAME ** tag to the new commit. However, if there are multiple instances ** of pattern B with the same TAGNAME, then only put the tag on the ** 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=strlen(z)-1; i>=0 && z[i]!='/'; i--){} gg.tagCommit = memcmp(&z[i-4], "tags", 4)==0; /* True for pattern B */ if( z[i+1]!=0 ) z += i+1; if( fossil_strcmp(z, "master")==0 ) z = "trunk"; gg.zBranch = fossil_strdup(z); gg.fromLoaded = 0; }else if( memcmp(zLine, "tag ", 4)==0 ){ gg.xFinish(); gg.xFinish = finish_tag; trim_newline(&zLine[4]); gg.zTag = fossil_strdup(&zLine[4]); }else if( memcmp(zLine, "reset ", 4)==0 ){ gg.xFinish(); }else if( memcmp(zLine, "checkpoint", 10)==0 ){ gg.xFinish(); }else if( memcmp(zLine, "feature", 7)==0 ){ gg.xFinish(); }else if( memcmp(zLine, "option", 6)==0 ){ gg.xFinish(); }else if( memcmp(zLine, "progress ", 9)==0 ){ gg.xFinish(); trim_newline(&zLine[9]); fossil_print("%s\n", &zLine[9]); fflush(stdout); }else if( memcmp(zLine, "data ", 5)==0 ){ fossil_free(gg.aData); gg.aData = 0; gg.nData = atoi(&zLine[5]); if( gg.nData ){ int got; gg.aData = fossil_malloc( gg.nData+1 ); got = fread(gg.aData, 1, gg.nData, pIn); if( got!=gg.nData ){ fossil_fatal("short read: got %d of %d bytes", got, gg.nData); } gg.aData[got] = 0; if( gg.zComment==0 && gg.xFinish==finish_commit ){ gg.zComment = gg.aData; gg.aData = 0; gg.nData = 0; } } }else if( memcmp(zLine, "author ", 7)==0 ){ /* No-op */ }else if( memcmp(zLine, "mark ", 5)==0 ){ trim_newline(&zLine[5]); fossil_free(gg.zMark); gg.zMark = fossil_strdup(&zLine[5]); }else if( memcmp(zLine, "tagger ", 7)==0 || memcmp(zLine, "committer ",10)==0 ){ sqlite3_int64 secSince1970; for(i=0; zLine[i] && zLine[i]!='<'; i++){} if( zLine[i]==0 ) goto malformed_line; z = &zLine[i+1]; for(i=i+1; zLine[i] && zLine[i]!='>'; i++){} if( zLine[i]==0 ) goto malformed_line; zLine[i] = 0; fossil_free(gg.zUser); gg.zUser = fossil_strdup(z); secSince1970 = 0; for(i=i+2; fossil_isdigit(zLine[i]); i++){ secSince1970 = secSince1970*10 + zLine[i] - '0'; } fossil_free(gg.zDate); gg.zDate = db_text(0, "SELECT datetime(%lld, 'unixepoch')", secSince1970); gg.zDate[10] = 'T'; }else if( memcmp(zLine, "from ", 5)==0 ){ trim_newline(&zLine[5]); fossil_free(gg.zFromMark); gg.zFromMark = fossil_strdup(&zLine[5]); fossil_free(gg.zFrom); gg.zFrom = resolve_committish(&zLine[5]); }else if( memcmp(zLine, "merge ", 6)==0 ){ trim_newline(&zLine[6]); if( gg.nMerge>=gg.nMergeAlloc ){ gg.nMergeAlloc = gg.nMergeAlloc*2 + 10; gg.azMerge = fossil_realloc(gg.azMerge, gg.nMergeAlloc*sizeof(char*)); } gg.azMerge[gg.nMerge] = resolve_committish(&zLine[6]); if( gg.azMerge[gg.nMerge] ) gg.nMerge++; }else if( memcmp(zLine, "M ", 2)==0 ){ import_prior_files(); z = &zLine[2]; zPerm = next_token(&z); zUuid = next_token(&z); zName = rest_of_line(&z); dequote_git_filename(zName); i = 0; pFile = import_find_file(zName, &i, gg.nFile); if( pFile==0 ){ pFile = import_add_file(); pFile->zName = fossil_strdup(zName); } 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; }else if( memcmp(zLine, "D ", 2)==0 ){ import_prior_files(); z = &zLine[2]; zName = rest_of_line(&z); dequote_git_filename(zName); 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); fossil_free(pFile->zUuid); *pFile = gg.aFile[--gg.nFile]; i--; } }else if( memcmp(zLine, "C ", 2)==0 ){ int nFrom; import_prior_files(); z = &zLine[2]; zFrom = next_token(&z); zTo = rest_of_line(&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]); }else{ pNew->zName = fossil_strdup(pFile->zName); } pNew->isExe = pFile->isExe; pNew->isLink = pFile->isLink; pNew->zUuid = fossil_strdup(pFile->zUuid); pNew->isFrom = 0; } }else if( memcmp(zLine, "R ", 2)==0 ){ int nFrom; import_prior_files(); z = &zLine[2]; zFrom = next_token(&z); zTo = rest_of_line(&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]); }else{ pNew->zName = fossil_strdup(pFile->zName); } pNew->zPrior = pFile->zName; pNew->isExe = pFile->isExe; pNew->isLink = pFile->isLink; pNew->zUuid = pFile->zUuid; pNew->isFrom = 0; gg.nFile--; *pFile = *pNew; memset(pNew, 0, sizeof(*pNew)); } 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 ){ /* No-op */ }else { goto malformed_line; } } gg.xFinish(); if( gg.hasLinks ){ db_set_int("allow-symlinks", 1, 0); } import_reset(1); return; malformed_line: trim_newline(zLine); fossil_fatal("bad fast-import line: [%s]", zLine); return; }