/* ** Copyright (c) 2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file contains code used to implement the "diff" command */ #include "config.h" #include "diffcmd.h" #include /* includes needed to catch interrupts */ #ifdef _WIN32 # include #else # include #endif /* ** Use the right null device for the platform. */ #if defined(_WIN32) # define NULL_DEVICE "NUL" #else # define NULL_DEVICE "/dev/null" #endif /* ** Used when the name for the diff is unknown. */ #define DIFF_NO_NAME "(unknown)" /* ** Use the "exec-rel-paths" setting and the --exec-abs-paths and ** --exec-rel-paths command line options to determine whether ** certain external commands are executed using relative paths. */ static int determine_exec_relative_option(int force){ static int relativePaths = -1; if( force || relativePaths==-1 ){ int relPathOption = find_option("exec-rel-paths", 0, 0)!=0; int absPathOption = find_option("exec-abs-paths", 0, 0)!=0; #if defined(FOSSIL_ENABLE_EXEC_REL_PATHS) relativePaths = db_get_boolean("exec-rel-paths", 1); #else relativePaths = db_get_boolean("exec-rel-paths", 0); #endif if( relPathOption ){ relativePaths = 1; } if( absPathOption ){ relativePaths = 0; } } return relativePaths; } #if INTERFACE /* ** An array of FileDirList objects describe the files and directories listed ** on the command line of a "diff" command. Only those objects listed are ** actually diffed. */ struct FileDirList { int nUsed; /* Number of times each entry is used */ int nName; /* Length of the entry */ char *zName; /* Text of the entry */ }; #endif /* ** Return true if zFile is a file named on the azInclude[] list or is ** a file in a directory named on the azInclude[] list. ** ** if azInclude is NULL, then always include zFile. */ static int file_dir_match(FileDirList *p, const char *zFile){ if( p==0 || strcmp(p->zName,".")==0 ) return 1; if( filenames_are_case_sensitive() ){ while( p->zName ){ if( strcmp(zFile, p->zName)==0 || (strncmp(zFile, p->zName, p->nName)==0 && zFile[p->nName]=='/') ){ break; } p++; } }else{ while( p->zName ){ if( fossil_stricmp(zFile, p->zName)==0 || (fossil_strnicmp(zFile, p->zName, p->nName)==0 && zFile[p->nName]=='/') ){ break; } p++; } } if( p->zName ){ p->nUsed++; return 1; } return 0; } /* ** Print the "Index:" message that patches wants to see at the top of a diff. */ void diff_print_index(const char *zFile, DiffConfig *pCfg, Blob *pOut){ if( (pCfg->diffFlags & (DIFF_SIDEBYSIDE|DIFF_BRIEF|DIFF_NUMSTAT|DIFF_JSON| DIFF_WEBPAGE|DIFF_TCL))==0 ){ blob_appendf(pOut, "Index: %s\n%.66c\n", zFile, '='); } } /* ** Print the +++/--- filename lines or whatever filename information ** is appropriate for the output format. */ void diff_print_filenames( const char *zLeft, /* Name of the left file */ const char *zRight, /* Name of the right file */ DiffConfig *pCfg, /* Diff configuration */ Blob *pOut /* Write to this blob, or stdout of this is NULL */ ){ u64 diffFlags = pCfg->diffFlags; if( diffFlags & (DIFF_BRIEF|DIFF_RAW) ){ /* no-op */ }else if( diffFlags & DIFF_DEBUG ){ blob_appendf(pOut, "FILE-LEFT %s\nFILE-RIGHT %s\n", zLeft, zRight); }else if( diffFlags & DIFF_WEBPAGE ){ if( fossil_strcmp(zLeft,zRight)==0 ){ blob_appendf(pOut,"

%h

\n", zLeft); }else{ blob_appendf(pOut,"

%h ⇆ %h

\n", zLeft, zRight); } }else if( diffFlags & (DIFF_TCL|DIFF_JSON) ){ if( diffFlags & DIFF_TCL ){ blob_append(pOut, "FILE ", 5); blob_append_tcl_literal(pOut, zLeft, (int)strlen(zLeft)); blob_append_char(pOut, ' '); blob_append_tcl_literal(pOut, zRight, (int)strlen(zRight)); blob_append_char(pOut, '\n'); }else{ if( pOut ) blob_trim(pOut); blob_append(pOut, (pCfg->nFile==0 ? "[{" : ",\n{"), -1); pCfg->nFile++; blob_append(pOut, "\n \"leftname\":", -1); blob_append_json_literal(pOut, zLeft, (int)strlen(zLeft)); blob_append(pOut, ",\n \"rightname\":", -1); blob_append_json_literal(pOut, zRight, (int)strlen(zRight)); blob_append(pOut, ",\n \"diff\":\n", -1); } }else if( diffFlags & DIFF_SIDEBYSIDE ){ int w = diff_width(pCfg); int n1 = strlen(zLeft); int n2 = strlen(zRight); int x; if( n1==n2 && fossil_strcmp(zLeft,zRight)==0 ){ if( n1>w*2 ) n1 = w*2; x = w*2+17 - (n1+2); blob_appendf(pOut, "%.*c %.*s %.*c\n", x/2, '=', n1, zLeft, (x+1)/2, '='); }else{ if( w<20 ) w = 20; if( n1>w-10 ) n1 = w - 10; if( n2>w-10 ) n2 = w - 10; blob_appendf(pOut, "%.*c %.*s %.*c versus %.*c %.*s %.*c\n", (w-n1+10)/2, '=', n1, zLeft, (w-n1+1)/2, '=', (w-n2)/2, '=', n2, zRight, (w-n2+1)/2, '='); } }else{ blob_appendf(pOut, "--- %s\n+++ %s\n", zLeft, zRight); } } /* ** Default header text for diff with --webpage */ static const char zWebpageHdr[] = @ @ @ @ @ @ @ ; const char zWebpageEnd[] = @ @ ; /* ** State variables used by the --browser option for diff. These must ** be static variables, not elements of DiffConfig, since they are ** used by the interrupt handler. */ static char *tempDiffFilename; /* File holding the diff HTML */ static FILE *diffOut; /* Open to write into tempDiffFilename */ /* Amount of delay (in milliseconds) between launching the ** web browser and deleting the temporary file used by --browser */ #ifndef FOSSIL_BROWSER_DIFF_DELAY # define FOSSIL_BROWSER_DIFF_DELAY 5000 /* 5 seconds by default */ #endif /* ** If we catch a single while writing the temporary file for the --browser ** diff output, then delete the temporary file and exit. */ static void diff_www_interrupt(int NotUsed){ (void)NotUsed; if( diffOut ) fclose(diffOut); if( tempDiffFilename ) file_delete(tempDiffFilename); exit(1); } #ifdef _WIN32 static BOOL WINAPI diff_console_ctrl_handler(DWORD dwCtrlType){ if( dwCtrlType==CTRL_C_EVENT ) diff_www_interrupt(0); return FALSE; } #endif /* ** Do preliminary setup and output before computing a diff. ** ** For --browser, redirect stdout to a temporary file that will ** hold the result. Make arrangements to delete that temporary ** file if the diff is interrupted. ** ** For --browser and --webpage, output the HTML header. */ void diff_begin(DiffConfig *pCfg){ if( (pCfg->diffFlags & DIFF_BROWSER)!=0 ){ tempDiffFilename = fossil_temp_filename(); tempDiffFilename = sqlite3_mprintf("%z.html", tempDiffFilename); diffOut = fossil_freopen(tempDiffFilename,"wb",stdout); if( diffOut==0 ){ fossil_fatal("unable to create temporary file \"%s\"", tempDiffFilename); } #ifndef _WIN32 signal(SIGINT, diff_www_interrupt); #else SetConsoleCtrlHandler(diff_console_ctrl_handler, TRUE); #endif } if( (pCfg->diffFlags & DIFF_WEBPAGE)!=0 ){ fossil_print("%s",zWebpageHdr); fflush(stdout); } } /* Do any final output required by a diff and complete the diff ** process. ** ** For --browser and --webpage, output any javascript required by ** the diff. (Currently JS is only needed for side-by-side diffs). ** ** For --browser, close the connection to the temporary file, then ** launch a web browser to view the file. After a delay ** of FOSSIL_BROWSER_DIFF_DELAY milliseconds, delete the temp file. */ void diff_end(DiffConfig *pCfg, int nErr){ if( (pCfg->diffFlags & DIFF_WEBPAGE)!=0 ){ if( pCfg->diffFlags & DIFF_SIDEBYSIDE ){ const unsigned char *zJs = builtin_file("diff.js", 0); fossil_print("\n", zJs); } fossil_print("%s", zWebpageEnd); } if( (pCfg->diffFlags & DIFF_BROWSER)!=0 && nErr==0 ){ char *zCmd = mprintf("%s %$", fossil_web_browser(), tempDiffFilename); fclose(diffOut); diffOut = fossil_freopen(NULL_DEVICE, "wb", stdout); fossil_system(zCmd); fossil_free(zCmd); diffOut = 0; sqlite3_sleep(FOSSIL_BROWSER_DIFF_DELAY); file_delete(tempDiffFilename); sqlite3_free(tempDiffFilename); tempDiffFilename = 0; } if( (pCfg->diffFlags & DIFF_JSON)!=0 && pCfg->nFile>0 ){ fossil_print("]\n"); } } /* ** Show the difference between two files, one in memory and one on disk. ** ** The difference is the set of edits needed to transform pFile1 into ** zFile2. The content of pFile1 is in memory. zFile2 exists on disk. */ void diff_file( Blob *pFile1, /* In memory content to compare from */ const char *zFile2, /* On disk content to compare to */ const char *zName, /* Display name of the file */ DiffConfig *pCfg, /* Flags to control the diff */ Blob *pOut /* Blob to store diff output */ ){ if( pCfg->zDiffCmd==0 ){ Blob out; /* Diff output text */ Blob file2; /* Content of zFile2 */ const char *zName2; /* Name of zFile2 for display */ /* Read content of zFile2 into memory */ blob_zero(&file2); if( file_size(zFile2, ExtFILE)<0 ){ zName2 = NULL_DEVICE; }else{ blob_read_from_file(&file2, zFile2, ExtFILE); zName2 = zName; } /* Compute and output the differences */ if( pCfg->diffFlags & DIFF_BRIEF ){ if( blob_compare(pFile1, &file2) ){ fossil_print("CHANGED %s\n", zName); } }else{ blob_zero(&out); text_diff(pFile1, &file2, &out, pCfg); if( blob_size(&out) ){ if( pCfg->diffFlags & DIFF_NUMSTAT ){ blob_appendf(pOut, "%s %s\n", blob_str(&out), zName); }else{ diff_print_filenames(zName, zName2, pCfg, pOut); blob_appendf(pOut, "%s\n", blob_str(&out)); } } blob_reset(&out); } /* Release memory resources */ blob_reset(&file2); }else{ Blob nameFile1; /* Name of temporary file to old pFile1 content */ Blob cmd; /* Text of command to run */ if( (pCfg->diffFlags & DIFF_INCBINARY)==0 ){ Blob file2; if( looks_like_binary(pFile1) ){ fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY); return; } if( pCfg->zBinGlob ){ Glob *pBinary = glob_create(pCfg->zBinGlob); if( glob_match(pBinary, zName) ){ fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY); glob_free(pBinary); return; } glob_free(pBinary); } blob_zero(&file2); if( file_size(zFile2, ExtFILE)>=0 ){ blob_read_from_file(&file2, zFile2, ExtFILE); } if( looks_like_binary(&file2) ){ fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY); blob_reset(&file2); return; } blob_reset(&file2); } /* Construct a temporary file to hold pFile1 based on the name of ** zFile2 */ file_tempname(&nameFile1, zFile2, "orig"); blob_write_to_file(pFile1, blob_str(&nameFile1)); /* Construct the external diff command */ blob_zero(&cmd); blob_append(&cmd, pCfg->zDiffCmd, -1); if( pCfg->diffFlags & DIFF_INVERT ){ blob_append_escaped_arg(&cmd, zFile2, 1); blob_append_escaped_arg(&cmd, blob_str(&nameFile1), 1); }else{ blob_append_escaped_arg(&cmd, blob_str(&nameFile1), 1); blob_append_escaped_arg(&cmd, zFile2, 1); } /* Run the external diff command */ fossil_system(blob_str(&cmd)); /* Delete the temporary file and clean up memory used */ file_delete(blob_str(&nameFile1)); blob_reset(&nameFile1); blob_reset(&cmd); } } /* ** Show the difference between two files, both in memory. ** ** The difference is the set of edits needed to transform pFile1 into ** pFile2. ** ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the ** command zDiffCmd to do the diffing. ** ** When using an external diff program, zBinGlob contains the GLOB patterns ** for file names to treat as binary. If fIncludeBinary is zero, these files ** will be skipped in addition to files that may contain binary content. */ void diff_file_mem( Blob *pFile1, /* In memory content to compare from */ Blob *pFile2, /* In memory content to compare to */ const char *zName, /* Display name of the file */ DiffConfig *pCfg /* Diff flags */ ){ if( pCfg->diffFlags & DIFF_BRIEF ) return; if( pCfg->zDiffCmd==0 ){ Blob out; /* Diff output text */ blob_zero(&out); text_diff(pFile1, pFile2, &out, pCfg); if( pCfg->diffFlags & DIFF_NUMSTAT ){ fossil_print("%s %s\n", blob_str(&out), zName); }else{ diff_print_filenames(zName, zName, pCfg, 0); fossil_print("%s\n", blob_str(&out)); } /* Release memory resources */ blob_reset(&out); }else{ Blob cmd; Blob temp1; Blob temp2; if( (pCfg->diffFlags & DIFF_INCBINARY)==0 ){ if( looks_like_binary(pFile1) || looks_like_binary(pFile2) ){ fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY); return; } if( pCfg->zBinGlob ){ Glob *pBinary = glob_create(pCfg->zBinGlob); if( glob_match(pBinary, zName) ){ fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY); glob_free(pBinary); return; } glob_free(pBinary); } } /* Construct a temporary file names */ file_tempname(&temp1, zName, "before"); file_tempname(&temp2, zName, "after"); blob_write_to_file(pFile1, blob_str(&temp1)); blob_write_to_file(pFile2, blob_str(&temp2)); /* Construct the external diff command */ blob_zero(&cmd); blob_append(&cmd, pCfg->zDiffCmd, -1); blob_append_escaped_arg(&cmd, blob_str(&temp1), 1); blob_append_escaped_arg(&cmd, blob_str(&temp2), 1); /* Run the external diff command */ fossil_system(blob_str(&cmd)); /* Delete the temporary file and clean up memory used */ file_delete(blob_str(&temp1)); file_delete(blob_str(&temp2)); blob_reset(&temp1); blob_reset(&temp2); blob_reset(&cmd); } } /* ** Return true the disk file is identical to the Blob. Return zero ** if the files differ in any way. */ static int file_same_as_blob(Blob *blob, const char *zDiskFile){ Blob file; int rc = 0; if( blob_size(blob)!=file_size(zDiskFile, ExtFILE) ) return 0; blob_zero(&file); blob_read_from_file(&file, zDiskFile, ExtFILE); if( blob_size(&file)!=blob_size(blob) ){ rc = 0; }else{ rc = memcmp(blob_buffer(&file), blob_buffer(blob), blob_size(&file))==0; } blob_reset(&file); return rc; } /* ** Run a diff between the version zFrom and files on disk. zFrom might ** be NULL which means to simply show the difference between the edited ** files on disk and the check-out on which they are based. ** ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the ** command zDiffCmd to do the diffing. ** ** When using an external diff program, zBinGlob contains the GLOB patterns ** for file names to treat as binary. If fIncludeBinary is zero, these files ** will be skipped in addition to files that may contain binary content. */ void diff_against_disk( const char *zFrom, /* Version to difference from */ DiffConfig *pCfg, /* Flags controlling diff output */ FileDirList *pFileDir, /* Which files to diff */ Blob *pOut /* Blob to output diff instead of stdout */ ){ int vid; Blob sql; Stmt q; int asNewFile; /* Treat non-existant files as empty files */ int isNumStat; /* True for --numstat */ asNewFile = (pCfg->diffFlags & (DIFF_VERBOSE|DIFF_NUMSTAT|DIFF_HTML))!=0; isNumStat = (pCfg->diffFlags & (DIFF_NUMSTAT|DIFF_TCL|DIFF_HTML))!=0; vid = db_lget_int("checkout", 0); vfile_check_signature(vid, CKSIG_ENOTFILE); blob_zero(&sql); db_begin_transaction(); if( zFrom ){ int rid = name_to_typed_rid(zFrom, "ci"); if( !is_a_version(rid) ){ fossil_fatal("no such check-in: %s", zFrom); } load_vfile_from_rid(rid); blob_append_sql(&sql, "SELECT v2.pathname, v2.deleted, v2.chnged, v2.rid==0, v1.rid, v1.islink" " FROM vfile v1, vfile v2 " " WHERE v1.pathname=v2.pathname AND v1.vid=%d AND v2.vid=%d" " AND (v2.deleted OR v2.chnged OR v1.mrid!=v2.rid)" "UNION " "SELECT pathname, 1, 0, 0, 0, islink" " FROM vfile v1" " WHERE v1.vid=%d" " AND NOT EXISTS(SELECT 1 FROM vfile v2" " WHERE v2.vid=%d AND v2.pathname=v1.pathname)" "UNION " "SELECT pathname, 0, 0, 1, 0, islink" " FROM vfile v2" " WHERE v2.vid=%d" " AND NOT EXISTS(SELECT 1 FROM vfile v1" " WHERE v1.vid=%d AND v1.pathname=v2.pathname)" " ORDER BY 1 /*scan*/", rid, vid, rid, vid, vid, rid ); }else{ blob_append_sql(&sql, "SELECT pathname, deleted, chnged , rid==0, rid, islink" " FROM vfile" " WHERE vid=%d" " AND (deleted OR chnged OR rid==0)" " ORDER BY pathname /*scan*/", vid ); } db_prepare(&q, "%s", blob_sql_text(&sql)); blob_reset(&sql); while( db_step(&q)==SQLITE_ROW ){ const char *zPathname = db_column_text(&q,0); int isDeleted = db_column_int(&q, 1); int isChnged = db_column_int(&q,2); int isNew = db_column_int(&q,3); int srcid = db_column_int(&q, 4); int isLink = db_column_int(&q, 5); const char *zFullName; int showDiff = 1; Blob fname; if( !file_dir_match(pFileDir, zPathname) ) continue; if( determine_exec_relative_option(0) ){ blob_zero(&fname); file_relative_name(zPathname, &fname, 1); }else{ blob_set(&fname, g.zLocalRoot); blob_append(&fname, zPathname, -1); } zFullName = blob_str(&fname); if( isDeleted ){ if( !isNumStat ){ fossil_print("DELETED %s\n", zPathname); } if( !asNewFile ){ showDiff = 0; zFullName = NULL_DEVICE; } }else if( file_access(zFullName, F_OK) ){ if( !isNumStat ){ fossil_print("MISSING %s\n", zPathname); } if( !asNewFile ){ showDiff = 0; } }else if( isNew ){ if( !isNumStat ){ fossil_print("ADDED %s\n", zPathname); } srcid = 0; if( !asNewFile ){ showDiff = 0; } }else if( isChnged==3 ){ if( !isNumStat ){ fossil_print("ADDED_BY_MERGE %s\n", zPathname); } srcid = 0; if( !asNewFile ){ showDiff = 0; } }else if( isChnged==5 ){ if( !isNumStat ){ fossil_print("ADDED_BY_INTEGRATE %s\n", zPathname); } srcid = 0; if( !asNewFile ){ showDiff = 0; } } if( showDiff ){ Blob content; if( !isLink != !file_islink(zFullName) ){ diff_print_index(zPathname, pCfg, 0); diff_print_filenames(zPathname, zPathname, pCfg, 0); fossil_print("%s",DIFF_CANNOT_COMPUTE_SYMLINK); continue; } if( srcid>0 ){ content_get(srcid, &content); }else{ blob_zero(&content); } if( isChnged==0 || !file_same_as_blob(&content, zFullName) ){ diff_print_index(zPathname, pCfg, pOut); diff_file(&content, zFullName, zPathname, pCfg, pOut); } blob_reset(&content); } blob_reset(&fname); } db_finalize(&q); db_end_transaction(1); /* ROLLBACK */ } /* ** Run a diff between the undo buffer and files on disk. ** ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the ** command zDiffCmd to do the diffing. ** ** When using an external diff program, zBinGlob contains the GLOB patterns ** for file names to treat as binary. If fIncludeBinary is zero, these files ** will be skipped in addition to files that may contain binary content. */ static void diff_against_undo( DiffConfig *pCfg, /* Flags controlling diff output */ FileDirList *pFileDir /* List of files and directories to diff */ ){ Stmt q; Blob content; db_prepare(&q, "SELECT pathname, content FROM undo"); blob_init(&content, 0, 0); while( db_step(&q)==SQLITE_ROW ){ char *zFullName; const char *zFile = (const char*)db_column_text(&q, 0); if( !file_dir_match(pFileDir, zFile) ) continue; zFullName = mprintf("%s%s", g.zLocalRoot, zFile); db_column_blob(&q, 1, &content); diff_file(&content, zFullName, zFile, pCfg, 0); fossil_free(zFullName); blob_reset(&content); } db_finalize(&q); } /* ** Show the difference between two files identified by ManifestFile ** entries. ** ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the ** command zDiffCmd to do the diffing. ** ** When using an external diff program, zBinGlob contains the GLOB patterns ** for file names to treat as binary. If fIncludeBinary is zero, these files ** will be skipped in addition to files that may contain binary content. */ static void diff_manifest_entry( struct ManifestFile *pFrom, struct ManifestFile *pTo, DiffConfig *pCfg ){ Blob f1, f2; int rid; const char *zName; if( pFrom ){ zName = pFrom->zName; }else if( pTo ){ zName = pTo->zName; }else{ zName = DIFF_NO_NAME; } if( pCfg->diffFlags & DIFF_BRIEF ) return; diff_print_index(zName, pCfg, 0); if( pFrom ){ rid = uuid_to_rid(pFrom->zUuid, 0); content_get(rid, &f1); }else{ blob_zero(&f1); } if( pTo ){ rid = uuid_to_rid(pTo->zUuid, 0); content_get(rid, &f2); }else{ blob_zero(&f2); } diff_file_mem(&f1, &f2, zName, pCfg); blob_reset(&f1); blob_reset(&f2); } /* ** Output the differences between two check-ins. ** ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the ** command zDiffCmd to do the diffing. ** ** When using an external diff program, zBinGlob contains the GLOB patterns ** for file names to treat as binary. If fIncludeBinary is zero, these files ** will be skipped in addition to files that may contain binary content. */ static void diff_two_versions( const char *zFrom, const char *zTo, DiffConfig *pCfg, FileDirList *pFileDir ){ Manifest *pFrom, *pTo; ManifestFile *pFromFile, *pToFile; int asNewFlag = (pCfg->diffFlags & (DIFF_VERBOSE|DIFF_NUMSTAT))!=0 ? 1 : 0; pFrom = manifest_get_by_name(zFrom, 0); manifest_file_rewind(pFrom); pFromFile = manifest_file_next(pFrom,0); pTo = manifest_get_by_name(zTo, 0); manifest_file_rewind(pTo); pToFile = manifest_file_next(pTo,0); while( pFromFile || pToFile ){ int cmp; if( pFromFile==0 ){ cmp = +1; }else if( pToFile==0 ){ cmp = -1; }else{ cmp = fossil_strcmp(pFromFile->zName, pToFile->zName); } if( cmp<0 ){ if( file_dir_match(pFileDir, pFromFile->zName) ){ if( (pCfg->diffFlags & (DIFF_NUMSTAT|DIFF_HTML))==0 ){ fossil_print("DELETED %s\n", pFromFile->zName); } if( asNewFlag ){ diff_manifest_entry(pFromFile, 0, pCfg); } } pFromFile = manifest_file_next(pFrom,0); }else if( cmp>0 ){ if( file_dir_match(pFileDir, pToFile->zName) ){ if( (pCfg->diffFlags & (DIFF_NUMSTAT|DIFF_HTML|DIFF_TCL|DIFF_JSON))==0 ){ fossil_print("ADDED %s\n", pToFile->zName); } if( asNewFlag ){ diff_manifest_entry(0, pToFile, pCfg); } } pToFile = manifest_file_next(pTo,0); }else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){ /* No changes */ (void)file_dir_match(pFileDir, pFromFile->zName); /* Record name usage */ pFromFile = manifest_file_next(pFrom,0); pToFile = manifest_file_next(pTo,0); }else{ if( file_dir_match(pFileDir, pToFile->zName) ){ if( pCfg->diffFlags & DIFF_BRIEF ){ fossil_print("CHANGED %s\n", pFromFile->zName); }else{ diff_manifest_entry(pFromFile, pToFile, pCfg); } } pFromFile = manifest_file_next(pFrom,0); pToFile = manifest_file_next(pTo,0); } } manifest_destroy(pFrom); manifest_destroy(pTo); } /* ** Return the name of the external diff command, or return NULL if ** no external diff command is defined. */ const char *diff_command_external(int guiDiff){ const char *zDefault; const char *zName; if( guiDiff ){ #if defined(_WIN32) zDefault = "WinDiff.exe"; #else zDefault = 0; #endif zName = "gdiff-command"; }else{ zDefault = 0; zName = "diff-command"; } return db_get(zName, zDefault); } /* ** Show diff output in a Tcl/Tk window, in response to the --tk option ** to the diff command. ** ** If fossil has direct access to a Tcl interpreter (either loaded ** dynamically through stubs or linked in statically), we can use it ** directly. Otherwise: ** (1) Write the Tcl/Tk script used for rendering into a temp file. ** (2) Invoke "tclsh" on the temp file using fossil_system(). ** (3) Delete the temp file. */ void diff_tk(const char *zSubCmd, int firstArg){ int i; Blob script; const char *zTempFile = 0; char *zCmd; const char *zTclsh; blob_zero(&script); blob_appendf(&script, "set fossilcmd {| \"%/\" %s -tcl -i -v", g.nameOfExe, zSubCmd); find_option("tcl",0,0); find_option("html",0,0); find_option("side-by-side","y",0); find_option("internal","i",0); find_option("verbose","v",0); zTclsh = find_option("tclsh",0,1); if( zTclsh==0 ){ zTclsh = db_get("tclsh",0); } /* The undocumented --script FILENAME option causes the Tk script to ** be written into the FILENAME instead of being run. This is used ** for testing and debugging. */ zTempFile = find_option("script",0,1); for(i=firstArg; i=3 ){ int i; Blob fname; pFileDir = fossil_malloc( sizeof(*pFileDir) * (g.argc-1) ); memset(pFileDir, 0, sizeof(*pFileDir) * (g.argc-1)); for(i=2; i