/*
* Copyright (c) 1992, Brian Berliner and Jeff Polk
* Copyright (c) 1989-1992, Brian Berliner
* Copyright (c) 2004, Tony Hoyle
*
* You may distribute under the terms of the GNU General Public License as
* specified in the README file that comes with the CVS source distribution.
*
* Call external diff on a file version
*/
#include "cvs.h"
#include "../cvsapi/TagDate.h"
#include "../cvsapi/TokenLine.h"
#define XDIFF_EXPORT
#include "../xdiff/xdiff.h"
enum xdiff_file
{
XDIFF_ERROR,
XDIFF_ADDED,
XDIFF_REMOVED,
XDIFF_DIFFERENT,
XDIFF_SAME
};
static int xdiff_fileproc(void *callerdat, struct file_info *finfo);
static Dtype xdiff_direntproc(void *callerdat, char *dir,
char *repos, char *update_dir,
List *entries, const char *virtual_repository, Dtype hint);
static int xdiff_filesdoneproc (void *callerdat, int err, char *repos, char *update_dir, List *entries);
static int xdiff_dirleaveproc (void *callerdat, char *dir, int err, char *update_dir, List *entries);
static enum xdiff_file xdiff_file_nodiff(struct file_info *finfo, Vers_TS *vers, enum xdiff_file empty_file, const char *default_branch);
static int xdiff_exec(const char *name, const char *file1, const char *file2, const char *label1, const char *label2);
/* Revision of the user file, if it is unchanged from something in the
repository and we want to use that fact. */
static char *user_file_rev;
static int empty_files;
static int diff_errors;
static CTagDate tags(false);
static char *use_rev1, *use_rev2;
static int have_rev1_label, have_rev2_label;
static const char *xdiff_options;
static const char *const xdiff_usage[] =
{
"Usage: %s %s [-lNR] [-o xdiff-options]\n",
" [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...]\n",
"\t-D d1\tDiff revision for date against working file.\n",
"\t-D d2\tDiff rev1/date1 against date2.\n",
"\t-N\tinclude diffs for added and removed files.\n",
"\t-R\tProcess directories recursively.\n",
"\t-l\tLocal directory only, not recursive\n",
"\t-o\tPass extra options to xdiff\n",
"\t-r rev1\tDiff revision for rev1 against working file.\n",
"\t-r rev2\tDiff rev1/date1 against rev2.\n",
"(consult the documentation for your xdiff extension for xdiff-options.\n",
"(Specify the --help global option for a list of other help options)\n",
NULL
};
int xdiff(int argc, char **argv)
{
int c;
int err = 0;
int local = 0;
int which;
if (argc == -1)
usage (xdiff_usage);
optind = 0;
while ((c = getopt (argc, argv, "+lNRr:D:o:")) != -1)
{
switch (c)
{
case 'o':
xdiff_options = xstrdup(optarg);
break;
case 'l':
local = 1;
break;
case 'r':
if(!tags.AddTag(optarg))
error(1,0,"Couldn't parse tag '%s'",optarg);
if(tags.size()>2)
usage(xdiff_usage);
break;
case 'D':
if(!tags.AddTag(optarg,true))
error(1,0,"Couldn't parse date '%s'",optarg);
if(tags.size()>2)
usage(xdiff_usage);
break;
case 'N':
empty_files = 1;
case 'R':
local = 0;
break;
case '?':
default:
usage (xdiff_usage);
break;
}
}
argc -= optind;
argv += optind;
if (current_parsed_root->isremote)
{
if(!supported_request("xdiff"))
error (1, 0, "server does not support %s",command_name);
if(local)
send_arg("-l");
if(empty_files)
send_arg("-N");
if(tags.size())
{
for(size_t n=0; n<tags.size(); n++)
{
if(tags[n].hasDate())
option_with_arg("-D",tags[n].dateText());
else
option_with_arg("-r",tags[n].tag());
}
}
send_arg("--");
/* Send the current files unless diffing two revs from the archive */
if (tags.size()<2)
send_files (argc, argv, local, 0, 0);
else
send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
send_file_names (argc, argv, SEND_EXPAND_WILD);
send_to_server ("xdiff\n", 0);
err = get_responses_and_close ();
xfree(xdiff_options);
return err;
}
for(size_t n=0; n<tags.size(); n++)
{
if(tags[n].hasTag())
tag_check_valid(tags[n].tag(), argc, argv, local, 0, "");
}
which = W_LOCAL;
if (tags.size())
which |= W_REPOS;
/* start the recursion processor */
err = start_recursion (xdiff_fileproc, xdiff_filesdoneproc, (PREDIRENTPROC) NULL, xdiff_direntproc,
xdiff_dirleaveproc, NULL, argc, argv, local,
which, 0, 1, (char *) NULL, NULL, 1, verify_read);
xfree(xdiff_options);
return err;
}
/*
* display the status of a file
*/
/* ARGSUSED */
static int xdiff_fileproc(void *callerdat, struct file_info *finfo)
{
int status, err = 2; /* 2 == trouble, like rcsdiff */
Vers_TS *vers;
enum xdiff_file empty_file = XDIFF_DIFFERENT;
char *tmp=NULL,*tmp2=NULL;
char *fname;
char *label1;
char *label2;
char *default_branch;
/* Initialize these solely to avoid warnings from gcc -Wall about
variables that might be used uninitialized. */
tmp = NULL;
fname = NULL;
user_file_rev = 0;
vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0, 0);
if(vers->tag && RCS_isbranch(finfo->rcs, vers->tag))
default_branch = vers->tag;
else
default_branch = NULL;
if (tags.size()==2)
{
/* Skip all the following checks regarding the user file; we're
not using it. */
}
else if (vers->vn_user == NULL)
{
/* The file does not exist in the working directory. */
if (tags.size() && vers->srcfile != NULL)
{
/* The file does exist in the repository. */
if (empty_files)
empty_file = XDIFF_REMOVED;
else
{
int exists=0;
/* special handling for TAG_HEAD */
if (tags[0].hasTag() && !strcmp(tags[0].tag(), TAG_HEAD))
{
const char *head = (vers->vn_rcs == NULL ? NULL : RCS_branch_head (vers->srcfile, vers->vn_rcs));
exists = head != NULL;
xfree (head);
}
else
{
Vers_TS *xvers;
xvers = Version_TS (finfo, NULL, tags[0].hasTag()?tags[0].tag():default_branch, tags[0].hasDate()?tags[0].dateText():NULL, 1, 0, 0);
exists = xvers->vn_rcs != NULL;
freevers_ts (&xvers);
}
if (exists)
error (0, 0, "%s no longer exists, no comparison available", fn_root(finfo->fullname));
freevers_ts (&vers);
diff_errors+=err;
return err;
}
}
else
{
error (0, 0, "I know nothing about %s", fn_root(finfo->fullname));
freevers_ts (&vers);
diff_errors+=err;
return err;
}
}
else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
{
if (empty_files)
empty_file = XDIFF_ADDED;
else
{
error (0, 0, "%s is a new entry, no comparison available", fn_root(finfo->fullname));
freevers_ts (&vers);
diff_errors+=err;
return err;
}
}
else if (vers->vn_user[0] == '-')
{
if (empty_files)
empty_file = XDIFF_REMOVED;
else
{
error (0, 0, "%s was removed, no comparison available", fn_root(finfo->fullname));
freevers_ts (&vers);
diff_errors+=err;
return err;
}
}
else
{
if (vers->vn_rcs == NULL && vers->srcfile == NULL)
{
error (0, 0, "cannot find revision control file for %s",
fn_root(finfo->fullname));
freevers_ts (&vers);
diff_errors+=err;
return err;
}
else
{
if (vers->ts_user == NULL)
{
error (0, 0, "cannot find %s", fn_root(finfo->fullname));
freevers_ts (&vers);
diff_errors+=err;
return err;
}
else if (!strcmp (vers->ts_user, vers->ts_rcs))
{
/* The user file matches some revision in the repository
Diff against the repository (for remote CVS, we might not
have a copy of the user file around). */
user_file_rev = vers->vn_user;
}
}
}
empty_file = xdiff_file_nodiff (finfo, vers, empty_file, default_branch);
if (empty_file == XDIFF_SAME || empty_file == XDIFF_ERROR)
{
freevers_ts (&vers);
if (empty_file == XDIFF_SAME)
return 0;
else
{
diff_errors+=err;
return err;
}
}
if (empty_file == XDIFF_DIFFERENT)
{
int dead1, dead2;
if (use_rev1 == NULL)
dead1 = 0;
else
dead1 = RCS_isdead (vers->srcfile, use_rev1);
if (use_rev2 == NULL)
dead2 = 0;
else
dead2 = RCS_isdead (vers->srcfile, use_rev2);
if (dead1 && dead2)
{
freevers_ts (&vers);
return 0;
}
else if (dead1)
{
if (empty_files)
empty_file = XDIFF_ADDED;
else
{
error (0, 0, "%s is a new entry, no comparison available", fn_root(finfo->fullname));
freevers_ts (&vers);
diff_errors+=err;
return err;
}
}
else if (dead2)
{
if (empty_files)
empty_file = XDIFF_REMOVED;
else
{
error (0, 0, "%s was removed, no comparison available", fn_root(finfo->fullname));
freevers_ts (&vers);
diff_errors+=err;
return err;
}
}
}
/* Output an "Index:" line for patch to use */
cvs_output ("Index: ", 0);
cvs_output (fn_root(finfo->fullname), 0);
cvs_output ("\n", 1);
/* Set up file labels appropriate for compatibility with the Larry Wall
* implementation of patch if the user didn't specify. This is irrelevant
* according to the POSIX.2 specification.
*/
label1 = NULL;
label2 = NULL;
if (!have_rev1_label)
{
if (empty_file == XDIFF_ADDED)
label1 = make_file_label (DEVNULL, NULL, NULL, 0);
else
label1 = make_file_label (fn_root(finfo->fullname), use_rev1, vers ? vers->srcfile : NULL, 0);
}
if (!have_rev2_label)
{
if (empty_file == XDIFF_REMOVED)
label2 = make_file_label (DEVNULL, NULL, NULL, 0);
else
label2 = make_file_label (fn_root(finfo->fullname), use_rev2, vers ? vers->srcfile : NULL, 0);
}
/* This is fullname, not file, possibly despite the POSIX.2
* specification, because that's the way all the Larry Wall
* implementations of patch (are there other implementations?) want
* things and the POSIX.2 spec appears to leave room for this.
*/
cvs_output ("===================================================================\nRCS file: ", 0);
if(finfo->rcs)
cvs_output (fn_root(finfo->rcs->path), 0);
else
cvs_output ("New file!", 0);
cvs_output ("\n", 1);
if (empty_file == XDIFF_ADDED || empty_file == XDIFF_REMOVED)
{
if (empty_file == XDIFF_ADDED)
{
if (use_rev2 == NULL)
status = xdiff_exec (last_component(finfo->file),DEVNULL, finfo->file, label1+2, label2+2);
else
{
int retcode;
printf("retrieving revision %s\n",use_rev2);
tmp = cvs_temp_name ();
retcode = RCS_checkout (vers->srcfile, (char *) NULL,
use_rev2, (char *) NULL,
vers->options,
tmp, (RCSCHECKOUTPROC) NULL,
(void *) NULL, NULL);
if (retcode != 0)
{
diff_errors+=err;
return err;
}
status = xdiff_exec (last_component(finfo->file),DEVNULL, tmp, label1+2, label2+2);
}
}
else
{
int retcode;
printf("retrieving revision %s\n",use_rev1);
tmp = cvs_temp_name ();
retcode = RCS_checkout (vers->srcfile, (char *) NULL,
use_rev1, (char *) NULL,
vers->options,
tmp, (RCSCHECKOUTPROC) NULL,
(void *) NULL, NULL);
if (retcode != 0)
{
diff_errors+=err;
return err;
}
status = xdiff_exec (last_component(finfo->file),tmp, DEVNULL, label1+2, label2+2);
}
}
else
{
int retcode;
printf("retrieving revision %s\n",use_rev1);
tmp = cvs_temp_name ();
retcode = RCS_checkout (vers->srcfile, (char *) NULL,
use_rev1, (char *) NULL,
vers->options,
tmp, (RCSCHECKOUTPROC) NULL,
(void *) NULL, NULL);
if (retcode != 0)
{
diff_errors+=err;
return err;
}
if(tags.size()==2)
{
printf("retrieving revision %s\n",use_rev2);
tmp2 = cvs_temp_name ();
retcode = RCS_checkout (vers->srcfile, (char *) NULL,
use_rev2, (char *) NULL,
vers->options,
tmp2, (RCSCHECKOUTPROC) NULL,
(void *) NULL, NULL);
if (retcode != 0)
{
diff_errors+=err;
return err;
}
}
status = xdiff_exec (last_component(finfo->file),tmp, tmp2?tmp2:finfo->file, label1+2, label2+2);
}
xfree (label1);
xfree (label2);
switch (status)
{
case -1: /* fork failed */
error (1, errno, "unable to execute xdiff program while diffing %s", fn_root(vers->srcfile->path));
case 0: /* everything ok */
err = 0;
break;
default: /* other error */
err = status;
break;
}
if(tmp)
{
if (CVS_UNLINK (tmp) < 0)
error (0, errno, "cannot remove %s", tmp);
xfree (tmp);
}
if(tmp2)
{
if (CVS_UNLINK (tmp2) < 0)
error (0, errno, "cannot remove %s", tmp2);
xfree (tmp2);
}
freevers_ts (&vers);
diff_errors+=err;
return err;
}
static Dtype xdiff_direntproc(void *callerdat, char *dir,
char *repos, char *update_dir,
List *entries, const char *virtual_repository, Dtype hint)
{
if(hint!=R_PROCESS)
return hint;
if (!quiet)
error (0, 0, "Diffing %s", update_dir);
return R_PROCESS;
}
/*
* Concoct the proper exit status - done with files
*/
/* ARGSUSED */
static int xdiff_filesdoneproc (void *callerdat, int err, char *repos, char *update_dir, List *entries)
{
return diff_errors;
}
/*
* Concoct the proper exit status - leaving directories
*/
/* ARGSUSED */
static int xdiff_dirleaveproc (void *callerdat, char *dir, int err, char *update_dir, List *entries)
{
return diff_errors;
}
/*
* verify that a file is different
*/
static enum xdiff_file xdiff_file_nodiff(struct file_info *finfo, Vers_TS *vers, enum xdiff_file empty_file, const char *default_branch)
{
Vers_TS *xvers;
/* free up any old use_rev* variables and reset 'em */
xfree (use_rev1);
xfree (use_rev2);
if (tags.size())
{
/* special handling for TAG_HEAD */
if (tags[0].hasTag() && !strcmp (tags[0].tag(), TAG_HEAD))
use_rev1 = ((vers->vn_rcs == NULL || vers->srcfile == NULL) ? NULL : RCS_branch_head (vers->srcfile, vers->vn_rcs));
else
{
xvers = Version_TS(finfo, NULL, tags[0].hasTag()?tags[0].tag():default_branch, tags[0].hasDate()?tags[0].dateText():NULL, 1, 0, 0);
if (xvers->vn_rcs != NULL)
use_rev1 = xstrdup (xvers->vn_rcs);
freevers_ts (&xvers);
}
}
if (tags.size()==2)
{
/* special handling for TAG_HEAD */
if (tags[1].hasTag() && !strcmp (tags[1].tag(), TAG_HEAD))
use_rev1 = ((vers->vn_rcs == NULL || vers->srcfile == NULL) ? NULL : RCS_branch_head (vers->srcfile, vers->vn_rcs));
else
{
xvers = Version_TS(finfo, NULL, tags[1].hasTag()?tags[1].tag():default_branch, tags[1].hasDate()?tags[1].dateText():NULL, 1, 0, 0);
if (xvers->vn_rcs != NULL)
use_rev2 = xstrdup (xvers->vn_rcs);
freevers_ts (&xvers);
}
if (use_rev1 == NULL)
{
/* The first revision does not exist. If EMPTY_FILES is
true, treat this as an added file. Otherwise, warn
about the missing tag. */
if (use_rev2 == NULL)
/* At least in the case where XDIFF_REV1 and XDIFF_REV2
are both numeric, we should be returning some kind
of error (see basicb-8a0 in testsuite). The symbolic
case may be more complicated. */
return XDIFF_SAME;
else if (empty_files)
return XDIFF_ADDED;
else if (tags[0].hasTag())
error (0, 0, "tag %s is not in file %s", tags[0].tag(), fn_root(finfo->fullname));
else
error (0, 0, "no revision for date %s in file %s", tags[0].tag(), fn_root(finfo->fullname));
return XDIFF_ERROR;
}
if (use_rev2 == NULL)
{
/* The second revision does not exist. If EMPTY_FILES is
true, treat this as a removed file. Otherwise warn
about the missing tag. */
if (empty_files)
return XDIFF_REMOVED;
else if (tags[1].hasTag())
error (0, 0, "tag %s is not in file %s", tags[1].tag(), fn_root(finfo->fullname));
else
error (0, 0, "no revision for date %s in file %s", tags[1].tag(), fn_root(finfo->fullname));
return XDIFF_ERROR;
}
/* now, see if we really need to do the diff */
if (!strcmp (use_rev1, use_rev2))
return XDIFF_SAME;
else
return XDIFF_DIFFERENT;
}
if (tags.size() && use_rev1 == NULL)
{
/* The first revision does not exist, and no second revision
was given. */
if (empty_files)
{
if (empty_file == XDIFF_REMOVED)
return XDIFF_SAME;
else
{
if (user_file_rev && use_rev2 == NULL)
use_rev2 = xstrdup (user_file_rev);
return XDIFF_ADDED;
}
}
else
{
if (tags[0].hasTag())
error (0, 0, "tag %s is not in file %s", tags[0].tag(), fn_root(finfo->fullname));
else
error (0, 0, "no revision for date %s in file %s", tags[0].tag(), fn_root(finfo->fullname));
return XDIFF_ERROR;
}
}
if (user_file_rev)
{
/* drop user_file_rev into first unused use_rev */
if (!use_rev1)
use_rev1 = xstrdup (user_file_rev);
else if (!use_rev2)
use_rev2 = xstrdup (user_file_rev);
/* and if not, it wasn't needed anyhow */
user_file_rev = 0;
}
/* now, see if we really need to do the diff */
if (use_rev1 && use_rev2)
{
if (strcmp (use_rev1, use_rev2) == 0)
return XDIFF_SAME;
else
return XDIFF_DIFFERENT;
}
if (use_rev1 == NULL || (vers->vn_user != NULL && !strcmp (use_rev1, vers->vn_user)))
{
if (empty_file == XDIFF_DIFFERENT && vers->ts_user && !strcmp (vers->ts_rcs, vers->ts_user))
return XDIFF_SAME;
if (use_rev1 == NULL && (vers->vn_user[0] != '0' || vers->vn_user[1] != '\0'))
{
if (vers->vn_user[0] == '-')
use_rev1 = xstrdup (vers->vn_user + 1);
else
use_rev1 = xstrdup (vers->vn_user);
}
}
return empty_file;
}
static int xdiff_exec (const char *name, const char *file1, const char *file2, const char *label1, const char *label2)
{
const char *wrap = wrap_xdiffwrapper(name);
if(wrap)
{
CTokenLine line(wrap);
if(xdiff_options)
line.addArgs(xdiff_options);
if(line.size())
{
CLibraryAccess lib;
if(!lib.Load(line[0],CGlobalSettings::GetLibraryDirectory(CGlobalSettings::GLDXdiff)))
error(1,0,"Cannot load xdiff handler for %s",name);
get_plugin_interface_t fn = (get_plugin_interface_t)lib.GetProc("get_plugin_interface");
if(!fn)
error(1,0,"Cannot load xdiff handler for %s",name);
plugin_interface *pi = fn();
if(!pi)
error(1,0,"Cannot load xdiff handler for %s",name);
if(pi->interface_version!=PLUGIN_INTERFACE_VERSION)
error(1,0,"xdiff plugin handler for %s is wrong version",name);
if(pi->init)
{
if(pi->init(pi))
{
CServerIo::trace(3,"Not loading xdiff %s - initialisation failed",name);
return NULL;
}
}
xdiff_interface *xd;
if(!pi->get_interface || (xd = (xdiff_interface*)pi->get_interface(pi,pitXdiff,NULL))==NULL)
{
CServerIo::trace(3,"Xdiff library does not support protocol interface.");
return NULL;
}
const char *const*argv = line.toArgv(1);
int argc = line.size()-1;
int ret = xd->xdiff_function(name,file1,file2,label1,label2,argc,argv,cvs_output);
return ret;
}
else
{
error(0,0,"%s has no xdiff wrapper defined",name);
}
}
else
{
error(0,0,"%s has no xdiff wrapper defined",name);
}
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1