From 4f49b16079b27d7ce633619e4cddfdf2bffbc952 Mon Sep 17 00:00:00 2001 From: cashmere Date: Fri, 4 Sep 2026 09:05:24 +0200 Subject: initial commit --- src/diffcmd.c | 1595 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1595 insertions(+) create mode 100644 src/diffcmd.c (limited to 'src/diffcmd.c') diff --git a/src/diffcmd.c b/src/diffcmd.c new file mode 100644 index 0000000..ce295e2 --- /dev/null +++ b/src/diffcmd.c @@ -0,0 +1,1595 @@ +/* +** 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 details about the compared versions - possibly the working directory +** or the undo buffer. For check-ins, show hash and commit time. +** +** This is intended primarily to go into the "header garbage" that is ignored +** by patch(1). +** +** zFrom and zTo are interpreted as symbolic version names, unless they +** start with '(', in which case they are printed directly. +*/ +void diff_print_versions(const char *zFrom, const char *zTo, DiffConfig *pCfg){ + if( (pCfg->diffFlags & (DIFF_SIDEBYSIDE|DIFF_BRIEF|DIFF_NUMSTAT| + DIFF_HTML|DIFF_WEBPAGE|DIFF_BROWSER|DIFF_JSON|DIFF_TCL))==0 ){ + fossil_print("Fossil-Diff-From: %s\n", + zFrom[0]=='(' ? zFrom : mprintf("%S %s", + rid_to_uuid(symbolic_name_to_rid(zFrom, "ci")), + db_text("","SELECT datetime(%f)||' UTC'", + symbolic_name_to_mtime(zFrom, 0, 0)))); + fossil_print("Fossil-Diff-To: %s\n", + zTo[0]=='(' ? zTo : mprintf("%S %s", + rid_to_uuid(symbolic_name_to_rid(zTo, "ci")), + db_text("","SELECT datetime(%f)||' UTC'", + symbolic_name_to_mtime(zTo, 0, 1)))); + fossil_print("%.66c\n", '-'); + } +} + +/* +** 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; + /* Standardize on /dev/null, regardless of platform. */ + if( pCfg->diffFlags & DIFF_FILE_ADDED ) zLeft = "/dev/null"; + if( pCfg->diffFlags & DIFF_FILE_DELETED ) zRight = "/dev/null"; + if( pCfg->azLabel[0] ) zLeft = pCfg->azLabel[0]; + if( pCfg->azLabel[1] ) zRight = pCfg->azLabel[1]; + 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\t\n+++ %s\t\n", zLeft, zRight); + } +} + + +/* +** Default header texts for diff with --webpage +*/ +static const char zWebpageHdr[] = +@ +@ +@ +@ +@ +@ +@ +; +static const char zWebpageHdrDark[] = +@ +@ +@ +@ +@ +@ +@ +; +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",(pCfg->diffFlags & DIFF_DARKMODE)!=0 ? zWebpageHdrDark : + 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( pCfg->diffFlags & DIFF_FILE_DELETED || 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) && !(pCfg->diffFlags & DIFF_NUMSTAT) ){ + 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 ){ + if( !(pCfg->diffFlags & DIFF_BRIEF) ){ + 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 */ + int useTempfile = 1; + + 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"); +#if !defined(_WIN32) + /* On Unix, use /dev/null for added or deleted files. */ + if( pCfg->diffFlags & DIFF_FILE_ADDED ){ + blob_init(&nameFile1, NULL_DEVICE, -1); + useTempfile = 0; + }else if( pCfg->diffFlags & DIFF_FILE_DELETED ){ + zFile2 = NULL_DEVICE; + } +#endif + if( useTempfile ) 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 */ + if( useTempfile ) 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) && !(pCfg->diffFlags & DIFF_NUMSTAT) ){ + return; + } + if( pCfg->zDiffCmd==0 ){ + Blob out; /* Diff output text */ + + blob_zero(&out); + text_diff(pFile1, pFile2, &out, pCfg); + if( pCfg->diffFlags & DIFF_NUMSTAT ){ + if( !(pCfg->diffFlags & DIFF_BRIEF) ){ + 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; + int useTempfile1 = 1; + int useTempfile2 = 1; + + 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 temporary file names */ + file_tempname(&temp1, zName, "before"); + file_tempname(&temp2, zName, "after"); +#if !defined(_WIN32) + /* On Unix, use /dev/null for added or deleted files. */ + if( pCfg->diffFlags & DIFF_FILE_ADDED ){ + useTempfile1 = 0; + blob_init(&temp1, NULL_DEVICE, -1); + }else if( pCfg->diffFlags & DIFF_FILE_DELETED ){ + useTempfile2 = 0; + blob_init(&temp2, NULL_DEVICE, -1); + } +#endif + if( useTempfile1 ) blob_write_to_file(pFile1, blob_str(&temp1)); + if( useTempfile2 ) 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 */ + if( useTempfile1 ) file_delete(blob_str(&temp1)); + if( useTempfile2 ) file_delete(blob_str(&temp2)); + + blob_reset(&temp1); + blob_reset(&temp2); + blob_reset(&cmd); + } +} + +/* +** Return true if the disk file is identical to the Blob. Return zero +** if the files differ in any way. +*/ +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 in the current +** working checkout. 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_version_to_checkout( + 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-existent 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 + ); + } + if( (pCfg->diffFlags & DIFF_SHOW_VERS)!=0 ){ + diff_print_versions(zFrom ? zFrom : db_lget("checkout-hash", 0), + "(workdir)", pCfg); + } + 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); + pCfg->diffFlags &= (~DIFF_FILE_MASK); + if( isDeleted ){ + if( !isNumStat ){ fossil_print("DELETED %s\n", zPathname); } + pCfg->diffFlags |= DIFF_FILE_DELETED; + 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); } + pCfg->diffFlags |= DIFF_FILE_ADDED; + srcid = 0; + if( !asNewFile ){ showDiff = 0; } + }else if( isChnged==3 ){ + if( !isNumStat ){ fossil_print("ADDED_BY_MERGE %s\n", zPathname); } + pCfg->diffFlags |= DIFF_FILE_ADDED; + srcid = 0; + if( !asNewFile ){ showDiff = 0; } + }else if( isChnged==5 ){ + if( !isNumStat ){ fossil_print("ADDED_BY_INTEGRATE %s\n", zPathname); } + pCfg->diffFlags |= DIFF_FILE_ADDED; + 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 + || pCfg->diffFlags & DIFF_FILE_DELETED + || !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 from the undo buffer to 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_undo_to_checkout( + 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); + if( (pCfg->diffFlags & DIFF_SHOW_VERS)!=0 ){ + diff_print_versions("(undo)", "(workdir)", pCfg); + } + 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) && !(pCfg->diffFlags & DIFF_NUMSTAT) ){ + 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); + if( (pCfg->diffFlags & DIFF_SHOW_VERS)!=0 ){ + diff_print_versions(zFrom, zTo, pCfg); + } + while( pFromFile || pToFile ){ + int cmp; + if( pFromFile==0 ){ + cmp = +1; + }else if( pToFile==0 ){ + cmp = -1; + }else{ + cmp = fossil_strcmp(pFromFile->zName, pToFile->zName); + } + pCfg->diffFlags &= (~DIFF_FILE_MASK); + 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); + } + pCfg->diffFlags |= DIFF_FILE_DELETED; + 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); + } + pCfg->diffFlags |= DIFF_FILE_ADDED; + 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) && !(pCfg->diffFlags & DIFF_NUMSTAT)){ + 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); +} + +/* +** Compute the difference from an external tree of files to the current +** working checkout with its edits. +** +** To put it another way: Every managed file in the current working +** checkout is compared to the file with same name under zExternBase. The +** zExternBase files are on the left and the files in the current working +** directory are on the right. +*/ +void diff_externbase_to_checkout( + const char *zExternBase, /* Remote tree to use as the baseline */ + DiffConfig *pCfg, /* Diff settings */ + FileDirList *pFileDir /* Only look at these files */ +){ + int vid; + Stmt q; + + vid = db_lget_int("checkout",0); + if( file_isdir(zExternBase, ExtFILE)!=1 ){ + fossil_fatal("\"%s\" is not a directory", zExternBase); + } + db_prepare(&q, + "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", + vid + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zFile; /* Name of file in the repository */ + char *zLhs; /* Full name of left-hand side file */ + char *zRhs; /* Full name of right-hand side file */ + Blob rhs; /* Full text of RHS */ + Blob lhs; /* Full text of LHS */ + + zFile = db_column_text(&q,0); + if( !file_dir_match(pFileDir, zFile) ) continue; + zLhs = mprintf("%s/%s", zExternBase, zFile); + zRhs = mprintf("%s%s", g.zLocalRoot, zFile); + if( file_size(zLhs, ExtFILE)<0 ){ + blob_zero(&lhs); + }else{ + blob_read_from_file(&lhs, zLhs, ExtFILE); + } + blob_read_from_file(&rhs, zRhs, ExtFILE); + if( blob_size(&lhs)!=blob_size(&rhs) + || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0 + ){ + diff_print_index(zFile, pCfg, 0); + diff_file_mem(&lhs, &rhs, zFile, pCfg); + } + blob_reset(&lhs); + blob_reset(&rhs); + fossil_free(zLhs); + fossil_free(zRhs); + } + db_finalize(&q); +} + + +/* +** 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 *zName; + zName = guiDiff ? "gdiff-command" : "diff-command"; + return db_get(zName, 0); +} + +/* +** Return true if it reasonable to run "diff -tk" for "gdiff". +** +** Details: Return true if all of the following are true: +** +** (1) The isGDiff flags is true +** (2) The "gdiff-command" setting is undefined +** (3) There is a "tclsh" on PATH +** (4) There is a "wish" on PATH +*/ +int gdiff_using_tk(int isGdiff){ + if( isGdiff + && db_get("gdiff-command","")[0]==0 + && fossil_app_on_path("tclsh",0) + && fossil_app_on_path("wish",0) + ){ + return 1; + } + return 0; +} + +/* +** A TCL list is being constructed in pBlob. Append a single list +** element z[] to that list. Do all necessary quoting and escaping. +** The content of z[] might be controlled by an attacker. +*/ +static void diff_tcl_lappend(Blob *pBlob, const char *z, int isFilename){ + if( strchr(z,'{')==0 && strchr(z,'}')==0 ){ + if( isFilename ){ + blob_appendf(pBlob, " {%/}", z); + }else{ + blob_appendf(pBlob, " {%s}", z); + } + }else{ + int j; + blob_append_char(pBlob, ' '); + for(j=0; z[j]; j++){ + if( fossil_isalnum(z[j]) ){ + blob_append_char(pBlob, z[j]); + }else{ + blob_appendf(pBlob, "\\%03o", (unsigned char)z[j]); + } + } + } +} + +/* +** Return TRUE (non-zero) if zArg is a valid option to "diff" or +** "gdiff" that takes an argument that might be confused for a +** HOST:PATH or USER@HOST:PATH filename. +*/ +static int isDiffOptionWithArg(const char *zArg){ + int i; + const char *az[] = { + "binary", + "branch", + "ci", "checkin", + "r", "from", + "to" + }; + if( zArg[0]!='-' ) return 0; + zArg++; + if( zArg[0]=='-' ) zArg++; + for(i=0; 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