/*
** Copyright (c) 2007 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License version 2 as published by the Free Software Foundation.
**
** 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. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** 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 <assert.h>
/*
** Shell-escape the given string. Append the result to a blob.
*/
static void shell_escape(Blob *pBlob, const char *zIn){
int n = blob_size(pBlob);
int k = strlen(zIn);
int i, c;
char *z;
for(i=0; (c = zIn[i])!=0; i++){
if( isspace(c) || c=='"' || (c=='\\' && zIn[i+1]!=0) ){
blob_appendf(pBlob, "\"%s\"", zIn);
z = blob_buffer(pBlob);
for(i=n+1; i<=n+k; i++){
if( z[i]=='"' ) z[i] = '_';
}
return;
}
}
blob_append(pBlob, zIn, -1);
}
/*
** Run the fossil diff command separately for every file in the current
** checkout that has changed.
*/
static void diff_all(int internalDiff, const char *zRevision){
Stmt q;
Blob cmd;
int nCmdBase;
int vid;
vid = db_lget_int("checkout", 0);
vfile_check_signature(vid);
blob_zero(&cmd);
shell_escape(&cmd, g.argv[0]);
blob_append(&cmd, " diff ", -1);
if( internalDiff ){
blob_append(&cmd, "-i ", -1);
}
if( zRevision ){
blob_append(&cmd, "-r ", -1);
shell_escape(&cmd, zRevision);
blob_append(&cmd, " ", 1);
}
nCmdBase = blob_size(&cmd);
db_prepare(&q,
"SELECT pathname, deleted, chnged, rid FROM vfile "
"WHERE chnged OR deleted OR rid=0 ORDER BY 1"
);
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)==0;
char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
cmd.nUsed = nCmdBase;
if( isDeleted ){
printf("DELETED %s\n", zPathname);
}else if( access(zFullName, 0) ){
printf("MISSING %s\n", zPathname);
}else if( isNew ){
printf("ADDED %s\n", zPathname);
}else if( isDeleted ){
printf("DELETED %s\n", zPathname);
}else if( isChnged==3 ){
printf("ADDED_BY_MERGE %s\n", zPathname);
}else{
printf("Index: %s\n======================================="
"============================\n",
zPathname
);
shell_escape(&cmd, zFullName);
printf("%s\n", blob_str(&cmd));
fflush(stdout);
system(blob_str(&cmd));
}
free(zFullName);
}
db_finalize(&q);
}
/*
** COMMAND: diff
** COMMAND: gdiff
**
** Usage: %fossil diff|gdiff ?-i? ?-r REVISION? FILE...
**
** Show the difference between the current version of a file (as it
** exists on disk) and that same file as it was checked out.
**
** diff will show a textual diff while gdiff will attempt to run a
** graphical diff command that you have setup. If the choosen command
** is not yet configured, the internal textual diff command will be
** used.
**
** If -i is supplied for either diff or gdiff, the internal textual
** diff command will be executed.
**
** Here are a few external diff command settings, for example:
**
** %fossil setting diff-command diff
**
** %fossil setting gdiff-command tkdiff
** %fossil setting gdiff-command eskill22
** %fossil setting gdiff-command tortoisemerge
** %fossil setting gdiff-command meld
** %fossil setting gdiff-command xxdiff
** %fossil setting gdiff-command kdiff3
*/
void diff_cmd(void){
int isGDiff; /* True for gdiff. False for normal diff */
const char *zFile; /* Name of file to diff */
const char *zRevision; /* Version of file to diff against current */
Blob cmd; /* The diff command-line for external diff */
Blob fname; /* */
Blob vname;
Blob record;
int cnt=0;
int internalDiff; /* True to use the internal diff engine */
isGDiff = g.argv[1][0]=='g';
internalDiff = find_option("internal","i",0)!=0;
zRevision = find_option("revision", "r", 1);
verify_all_options();
db_must_be_within_tree();
if( !isGDiff && g.argc==2 ){
diff_all(internalDiff, zRevision);
return;
}
if( g.argc<3 ){
usage("?OPTIONS? FILE");
}
if( internalDiff==0 ){
const char *zExternalCommand;
if( !isGDiff ){
zExternalCommand = db_get("diff-command", 0);
}else{
zExternalCommand = db_get("gdiff-command", 0);
}
if( zExternalCommand==0 ){
internalDiff=1;
}
blob_zero(&cmd);
blob_appendf(&cmd, "%s ", zExternalCommand);
}
zFile = g.argv[g.argc-1];
file_tree_name(zFile, &fname, 1);
blob_zero(&vname);
do{
blob_reset(&vname);
blob_appendf(&vname, "%s~%d", zFile, cnt++);
}while( access(blob_str(&vname),0)==0 );
if( zRevision==0 ){
int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B", &fname);
if( rid==0 ){
fossil_fatal("no history for file: %b", &fname);
}
content_get(rid, &record);
}else{
historical_version_of_file(zRevision, blob_str(&fname), &record);
}
if( internalDiff ){
Blob out;
Blob current;
blob_zero(¤t);
blob_read_from_file(¤t, zFile);
blob_zero(&out);
text_diff(&record, ¤t, &out, 5);
printf("--- %s\n+++ %s\n", blob_str(&fname), blob_str(&fname));
printf("%s\n", blob_str(&out));
blob_reset(¤t);
blob_reset(&out);
}else{
blob_write_to_file(&record, blob_str(&vname));
blob_reset(&record);
shell_escape(&cmd, blob_str(&vname));
blob_appendf(&cmd, " ");
shell_escape(&cmd, zFile);
system(blob_str(&cmd));
unlink(blob_str(&vname));
blob_reset(&vname);
blob_reset(&cmd);
}
blob_reset(&fname);
}