/* ** 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 /* ** Diff option flags */ #define DIFF_NEWFILE 0x01 /* Treat non-existing fails as empty files */ #define DIFF_NOEOLWS 0x02 /* Ignore whitespace at the end of lines */ /* ** Print the "Index:" message that patch wants to see at the top of a diff. */ void diff_print_index(const char *zFile){ printf("Index: %s\n=======================================" "============================\n", zFile); } /* ** 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. ** ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the ** command zDiffCmd to do the diffing. */ 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 */ const char *zDiffCmd, /* Command for comparison */ int ignoreEolWs /* Ignore whitespace at end of line */ ){ if( 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)<0 ){ zName2 = "/dev/null"; }else{ blob_read_from_file(&file2, zFile2); zName2 = zName; } /* Compute and output the differences */ blob_zero(&out); text_diff(pFile1, &file2, &out, 5, ignoreEolWs); printf("--- %s\n+++ %s\n", zName, zName2); printf("%s\n", blob_str(&out)); /* Release memory resources */ blob_reset(&file2); blob_reset(&out); }else{ int cnt = 0; Blob nameFile1; /* Name of temporary file to old pFile1 content */ Blob cmd; /* Text of command to run */ /* Construct a temporary file to hold pFile1 based on the name of ** zFile2 */ blob_zero(&nameFile1); do{ blob_reset(&nameFile1); blob_appendf(&nameFile1, "%s~%d", zFile2, cnt++); }while( access(blob_str(&nameFile1),0)==0 ); blob_write_to_file(pFile1, blob_str(&nameFile1)); /* Construct the external diff command */ blob_zero(&cmd); blob_appendf(&cmd, "%s ", zDiffCmd); shell_escape(&cmd, blob_str(&nameFile1)); blob_append(&cmd, " ", 1); shell_escape(&cmd, zFile2); /* Run the external diff command */ fossil_system(blob_str(&cmd)); /* Delete the temporary file and clean up memory used */ unlink(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. */ 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 */ const char *zDiffCmd, /* Command for comparison */ int ignoreEolWs /* Ignore whitespace at end of lines */ ){ if( zDiffCmd==0 ){ Blob out; /* Diff output text */ blob_zero(&out); text_diff(pFile1, pFile2, &out, 5, ignoreEolWs); printf("--- %s\n+++ %s\n", zName, zName); printf("%s\n", blob_str(&out)); /* Release memory resources */ blob_reset(&out); }else{ Blob cmd; char zTemp1[300]; char zTemp2[300]; /* Construct a temporary file names */ file_tempname(sizeof(zTemp1), zTemp1); file_tempname(sizeof(zTemp2), zTemp2); blob_write_to_file(pFile1, zTemp1); blob_write_to_file(pFile2, zTemp2); /* Construct the external diff command */ blob_zero(&cmd); blob_appendf(&cmd, "%s ", zDiffCmd); shell_escape(&cmd, zTemp1); blob_append(&cmd, " ", 1); shell_escape(&cmd, zTemp2); /* Run the external diff command */ fossil_system(blob_str(&cmd)); /* Delete the temporary file and clean up memory used */ unlink(zTemp1); unlink(zTemp2); blob_reset(&cmd); } } /* ** Do a diff against a single file named in g.argv[2] from version zFrom ** against the same file on disk. */ static void diff_one_against_disk( const char *zFrom, /* Name of file */ const char *zDiffCmd, /* Use this "diff" command */ int ignoreEolWs /* Ignore whitespace changes at end of lines */ ){ Blob fname; Blob content; file_tree_name(g.argv[2], &fname, 1); historical_version_of_file(zFrom, blob_str(&fname), &content, 0); diff_file(&content, g.argv[2], g.argv[2], zDiffCmd, ignoreEolWs); blob_reset(&content); blob_reset(&fname); } /* ** 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. */ static void diff_all_against_disk( const char *zFrom, /* Version to difference from */ const char *zDiffCmd, /* Use this diff command. NULL for built-in */ int diffFlags /* Flags controlling diff output */ ){ int vid; Blob sql; Stmt q; int ignoreEolWs; /* Ignore end-of-line whitespace */ int asNewFile; /* Treat non-existant files as empty files */ ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0; asNewFile = (diffFlags & DIFF_NEWFILE)!=0; vid = db_lget_int("checkout", 0); vfile_check_signature(vid, 1, 0); blob_zero(&sql); db_begin_transaction(); if( zFrom ){ int rid = name_to_rid(zFrom); if( !is_a_version(rid) ){ fossil_fatal("no such check-in: %s", zFrom); } load_vfile_from_rid(rid); blob_appendf(&sql, "SELECT v2.pathname, v2.deleted, v2.chnged, v2.rid==0, v1.rid" " 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" " 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" " 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", rid, vid, rid, vid, vid, rid ); }else{ blob_appendf(&sql, "SELECT pathname, deleted, chnged , rid==0, rid" " FROM vfile" " WHERE vid=%d" " AND (deleted OR chnged OR rid==0)" " ORDER BY pathname", vid ); } db_prepare(&q, blob_str(&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); char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname); char *zToFree = zFullName; int showDiff = 1; if( isDeleted ){ printf("DELETED %s\n", zPathname); if( !asNewFile ){ showDiff = 0; zFullName = "/dev/null"; } }else if( access(zFullName, 0) ){ printf("MISSING %s\n", zPathname); if( !asNewFile ){ showDiff = 0; } }else if( isNew ){ printf("ADDED %s\n", zPathname); srcid = 0; if( !asNewFile ){ showDiff = 0; } }else if( isChnged==3 ){ printf("ADDED_BY_MERGE %s\n", zPathname); srcid = 0; if( !asNewFile ){ showDiff = 0; } } if( showDiff ){ Blob content; if( srcid>0 ){ content_get(srcid, &content); }else{ blob_zero(&content); } diff_print_index(zPathname); diff_file(&content, zFullName, zPathname, zDiffCmd, ignoreEolWs); blob_reset(&content); } free(zToFree); } db_finalize(&q); db_end_transaction(1); /* ROLLBACK */ } /* ** Output the differences between two versions of a single file. ** zFrom and zTo are the check-ins containing the two file versions. ** The filename is contained in g.argv[2]. */ static void diff_one_two_versions( const char *zFrom, const char *zTo, const char *zDiffCmd, int ignoreEolWs ){ char *zName; Blob fname; Blob v1, v2; file_tree_name(g.argv[2], &fname, 1); zName = blob_str(&fname); historical_version_of_file(zFrom, zName, &v1, 0); historical_version_of_file(zTo, zName, &v2, 0); diff_file_mem(&v1, &v2, zName, zDiffCmd, ignoreEolWs); blob_reset(&v1); blob_reset(&v2); blob_reset(&fname); } /* ** Show the difference between two files identified by ManifestFile ** entries. */ static void diff_manifest_entry( struct ManifestFile *pFrom, struct ManifestFile *pTo, const char *zDiffCmd, int ignoreEolWs ){ Blob f1, f2; int rid; const char *zName = pFrom ? pFrom->zName : pTo->zName; diff_print_index(zName); 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, zDiffCmd, ignoreEolWs); blob_reset(&f1); blob_reset(&f2); } /* ** Output the differences between two check-ins. */ static void diff_all_two_versions( const char *zFrom, const char *zTo, const char *zDiffCmd, int diffFlags ){ Manifest *pFrom, *pTo; ManifestFile *pFromFile, *pToFile; int ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0 ? 1 : 0; int asNewFlag = (diffFlags & DIFF_NEWFILE)!=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 ){ printf("DELETED %s\n", pFromFile->zName); if( asNewFlag ){ diff_manifest_entry(pFromFile, 0, zDiffCmd, ignoreEolWs); } pFromFile = manifest_file_next(pFrom,0); }else if( cmp>0 ){ printf("ADDED %s\n", pToFile->zName); if( asNewFlag ){ diff_manifest_entry(0, pToFile, zDiffCmd, ignoreEolWs); } pToFile = manifest_file_next(pTo,0); }else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){ /* No changes */ pFromFile = manifest_file_next(pFrom,0); pToFile = manifest_file_next(pTo,0); }else{ printf("CHANGED %s\n", pFromFile->zName); diff_manifest_entry(pFromFile, pToFile, zDiffCmd, ignoreEolWs); pFromFile = manifest_file_next(pFrom,0); pToFile = manifest_file_next(pTo,0); } } manifest_destroy(pFrom); manifest_destroy(pTo); } /* ** COMMAND: diff ** COMMAND: gdiff ** ** Usage: %fossil diff|gdiff ?options? ?FILE? ** ** Show the difference between the current version of FILE (as it ** exists on disk) and that same file as it was checked out. Or ** if the FILE argument is omitted, show the unsaved changed currently ** in the working check-out. ** ** If the "--from VERSION" or "-r VERSION" option is used it specifies ** the source check-in for the diff operation. If not specified, the ** source check-in is the base check-in for the current check-out. ** ** If the "--to VERSION" option appears, it specifies the check-in from ** which the second version of the file or files is taken. If there is ** no "--to" option then the (possibly edited) files in the current check-out ** are used. ** ** The "-i" command-line option forces the use of the internal diff logic ** rather than any external diff program that might be configured using ** the "setting" command. If no external diff program is configured, then ** the "-i" option is a no-op. The "-i" option converts "gdiff" into "diff". ** ** The "-N" or "--new-file" option causes the complete text of added or ** deleted files to be displayed. */ void diff_cmd(void){ int isGDiff; /* True for gdiff. False for normal diff */ int isInternDiff; /* True for internal diff */ int hasNFlag; /* True if -N or --new-file flag is used */ const char *zFrom; /* Source version number */ const char *zTo; /* Target version number */ const char *zDiffCmd = 0; /* External diff command. NULL for internal diff */ int diffFlags = 0; /* Flags to control the DIFF */ isGDiff = g.argv[1][0]=='g'; isInternDiff = find_option("internal","i",0)!=0; zFrom = find_option("from", "r", 1); zTo = find_option("to", 0, 1); hasNFlag = find_option("new-file","N",0)!=0; if( hasNFlag ) diffFlags |= DIFF_NEWFILE; if( zTo==0 ){ db_must_be_within_tree(); verify_all_options(); if( !isInternDiff ){ zDiffCmd = db_get(isGDiff ? "gdiff-command" : "diff-command", 0); } if( g.argc==3 ){ diff_one_against_disk(zFrom, zDiffCmd, 0); }else{ diff_all_against_disk(zFrom, zDiffCmd, diffFlags); } }else if( zFrom==0 ){ fossil_fatal("must use --from if --to is present"); }else{ db_find_and_open_repository(0, 0); verify_all_options(); if( !isInternDiff ){ zDiffCmd = db_get(isGDiff ? "gdiff-command" : "diff-command", 0); } if( g.argc==3 ){ diff_one_two_versions(zFrom, zTo, zDiffCmd, 0); }else{ diff_all_two_versions(zFrom, zTo, zDiffCmd, diffFlags); } } }