#include "cvs.h"
enum diff_file
{
DIFF_ERROR,
DIFF_ADDED,
DIFF_REMOVED,
DIFF_DIFFERENT,
DIFF_SAME
};
static Dtype diff_dirproc PROTO ((void *callerdat, char *dir,
char *pos_repos, char *update_dir,
List *entries));
static int diff_filesdoneproc PROTO ((void *callerdat, int err,
char *repos, char *update_dir,
List *entries));
static int diff_dirleaveproc PROTO ((void *callerdat, char *dir,
int err, char *update_dir,
List *entries));
static enum diff_file diff_file_nodiff PROTO ((struct file_info *finfo,
Vers_TS *vers,
enum diff_file));
static int diff_fileproc PROTO ((void *callerdat, struct file_info *finfo));
static void diff_mark_errors PROTO((int err));
static char *diff_rev1, *diff_rev2;
static char *diff_date1, *diff_date2;
static char *use_rev1, *use_rev2;
static int have_rev1_label, have_rev2_label;
static char *user_file_rev;
static char *options;
static char *opts;
static size_t opts_allocated = 1;
static int diff_errors;
static int empty_files = 0;
static const char *const diff_usage[] =
{
"Usage: %s %s [-lNR] [rcsdiff-options]\n",
" [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
"\t-l\tLocal directory only, not recursive\n",
"\t-R\tProcess directories recursively.\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 rev1\tDiff revision for rev1 against working file.\n",
"\t-r rev2\tDiff rev1/date1 against rev2.\n",
"\t--ifdef=arg\tOutput diffs in ifdef format.\n",
"(consult the documentation for your diff program for rcsdiff-options.\n",
"The most popular is -c for context diffs but there are many more).\n",
"(Specify the --help global option for a list of other help options)\n",
NULL
};
static struct option const longopts[] =
{
{"ignore-blank-lines", 0, 0, 'B'},
{"context", 2, 0, 143},
{"ifdef", 1, 0, 131},
{"show-function-line", 1, 0, 'F'},
{"speed-large-files", 0, 0, 'H'},
{"ignore-matching-lines", 1, 0, 'I'},
{"label", 1, 0, 'L'},
{"new-file", 0, 0, 'N'},
{"initial-tab", 0, 0, 148},
{"width", 1, 0, 'W'},
{"text", 0, 0, 'a'},
{"ignore-space-change", 0, 0, 'b'},
{"minimal", 0, 0, 'd'},
{"ed", 0, 0, 'e'},
{"forward-ed", 0, 0, 'f'},
{"ignore-case", 0, 0, 'i'},
{"paginate", 0, 0, 144},
{"rcs", 0, 0, 'n'},
{"show-c-function", 0, 0, 'p'},
{"brief", 0, 0, 145},
{"report-identical-files", 0, 0, 's'},
{"expand-tabs", 0, 0, 't'},
{"ignore-all-space", 0, 0, 'w'},
{"side-by-side", 0, 0, 147},
{"unified", 2, 0, 146},
{"left-column", 0, 0, 129},
{"suppress-common-lines", 0, 0, 130},
{"old-line-format", 1, 0, 132},
{"new-line-format", 1, 0, 133},
{"unchanged-line-format", 1, 0, 134},
{"line-format", 1, 0, 135},
{"old-group-format", 1, 0, 136},
{"new-group-format", 1, 0, 137},
{"unchanged-group-format", 1, 0, 138},
{"changed-group-format", 1, 0, 139},
{"horizon-lines", 1, 0, 140},
{"binary", 0, 0, 142},
{0, 0, 0, 0}
};
static void strcat_and_allocate PROTO ((char **, size_t *, const char *));
static void
strcat_and_allocate (str, lenp, src)
char **str;
size_t *lenp;
const char *src;
{
size_t new_size;
new_size = strlen (*str) + strlen (src) + 1;
if (*str == NULL || new_size >= *lenp)
{
while (new_size >= *lenp)
*lenp *= 2;
*str = xrealloc (*str, *lenp);
}
strcat (*str, src);
}
int
diff (argc, argv)
int argc;
char **argv;
{
char tmp[50];
int c, err = 0;
int local = 0;
int which;
int option_index;
if (argc == -1)
usage (diff_usage);
have_rev1_label = have_rev2_label = 0;
if (opts == NULL)
{
opts_allocated = 1;
opts = xmalloc (opts_allocated);
}
opts[0] = '\0';
optind = 0;
while ((c = getopt_long (argc, argv,
"+abcdefhilnpstuw0123456789BHNRC:D:F:I:L:U:V:W:k:r:",
longopts, &option_index)) != -1)
{
switch (c)
{
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
case 'h': case 'i': case 'n': case 'p': case 's': case 't':
case 'u': case 'w': case '0': case '1': case '2':
case '3': case '4': case '5': case '6': case '7': case '8':
case '9': case 'B': case 'H':
(void) sprintf (tmp, " -%c", (char) c);
strcat_and_allocate (&opts, &opts_allocated, tmp);
break;
case 'L':
if (have_rev1_label++)
if (have_rev2_label++)
{
error (0, 0, "extra -L arguments ignored");
break;
}
strcat_and_allocate (&opts, &opts_allocated, " -L");
strcat_and_allocate (&opts, &opts_allocated, optarg);
break;
case 'C': case 'F': case 'I': case 'U': case 'V': case 'W':
(void) sprintf (tmp, " -%c", (char) c);
strcat_and_allocate (&opts, &opts_allocated, tmp);
strcat_and_allocate (&opts, &opts_allocated, optarg);
break;
case 131:
strcat_and_allocate (&opts, &opts_allocated, " -D");
strcat_and_allocate (&opts, &opts_allocated, optarg);
break;
case 129: case 130: case 132: case 133: case 134:
case 135: case 136: case 137: case 138: case 139: case 140:
case 141: case 142: case 143: case 144: case 145: case 146:
case 147: case 148:
strcat_and_allocate (&opts, &opts_allocated, " --");
strcat_and_allocate (&opts, &opts_allocated,
longopts[option_index].name);
if (longopts[option_index].has_arg == 1
|| (longopts[option_index].has_arg == 2
&& optarg != NULL))
{
strcat_and_allocate (&opts, &opts_allocated, "=");
strcat_and_allocate (&opts, &opts_allocated, optarg);
}
break;
case 'R':
local = 0;
break;
case 'l':
local = 1;
break;
case 'k':
if (options)
free (options);
options = RCS_check_kflag (optarg);
break;
case 'r':
if (diff_rev2 != NULL || diff_date2 != NULL)
error (1, 0,
"no more than two revisions/dates can be specified");
if (diff_rev1 != NULL || diff_date1 != NULL)
diff_rev2 = optarg;
else
diff_rev1 = optarg;
break;
case 'D':
if (diff_rev2 != NULL || diff_date2 != NULL)
error (1, 0,
"no more than two revisions/dates can be specified");
if (diff_rev1 != NULL || diff_date1 != NULL)
diff_date2 = Make_Date (optarg);
else
diff_date1 = Make_Date (optarg);
break;
case 'N':
empty_files = 1;
break;
case '?':
default:
usage (diff_usage);
break;
}
}
argc -= optind;
argv += optind;
if (!options)
options = xstrdup ("");
#ifdef CLIENT_SUPPORT
if (client_active) {
start_server ();
ign_setup ();
if (local)
send_arg("-l");
if (empty_files)
send_arg("-N");
send_option_string (opts);
if (options[0] != '\0')
send_arg (options);
if (diff_rev1)
option_with_arg ("-r", diff_rev1);
if (diff_date1)
client_senddate (diff_date1);
if (diff_rev2)
option_with_arg ("-r", diff_rev2);
if (diff_date2)
client_senddate (diff_date2);
send_file_names (argc, argv, SEND_EXPAND_WILD);
if (diff_rev2 == NULL && diff_date2 == NULL)
send_files (argc, argv, local, 0, 0);
else
send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
send_to_server ("diff\012", 0);
err = get_responses_and_close ();
free (options);
return (err);
}
#endif
if (diff_rev1 != NULL)
tag_check_valid (diff_rev1, argc, argv, local, 0, "");
if (diff_rev2 != NULL)
tag_check_valid (diff_rev2, argc, argv, local, 0, "");
which = W_LOCAL;
if (diff_rev1 != NULL || diff_date1 != NULL)
which |= W_REPOS | W_ATTIC;
wrap_setup ();
err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc,
diff_dirleaveproc, NULL, argc, argv, local,
which, 0, 1, (char *) NULL, 1);
free (options);
return (err);
}
static int
diff_fileproc (callerdat, finfo)
void *callerdat;
struct file_info *finfo;
{
int status, err = 2;
Vers_TS *vers;
enum diff_file empty_file = DIFF_DIFFERENT;
char *tmp;
char *tocvsPath;
char *fname;
tmp = NULL;
fname = NULL;
user_file_rev = 0;
vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0);
if (diff_rev2 != NULL || diff_date2 != NULL)
{
}
else if (vers->vn_user == NULL)
{
if ((diff_rev1 != NULL || diff_date1 != NULL)
&& vers->srcfile != NULL)
{
if (empty_files)
empty_file = DIFF_REMOVED;
else
{
int exists;
exists = 0;
if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
{
char *head =
(vers->vn_rcs == NULL
? NULL
: RCS_branch_head (vers->srcfile, vers->vn_rcs));
exists = head != NULL;
if (head != NULL)
free (head);
}
else
{
Vers_TS *xvers;
xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
1, 0);
exists = xvers->vn_rcs != NULL;
freevers_ts (&xvers);
}
if (exists)
error (0, 0,
"%s no longer exists, no comparison available",
finfo->fullname);
freevers_ts (&vers);
diff_mark_errors (err);
return (err);
}
}
else
{
error (0, 0, "I know nothing about %s", finfo->fullname);
freevers_ts (&vers);
diff_mark_errors (err);
return (err);
}
}
else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
{
if (empty_files)
empty_file = DIFF_ADDED;
else
{
error (0, 0, "%s is a new entry, no comparison available",
finfo->fullname);
freevers_ts (&vers);
diff_mark_errors (err);
return (err);
}
}
else if (vers->vn_user[0] == '-')
{
if (empty_files)
empty_file = DIFF_REMOVED;
else
{
error (0, 0, "%s was removed, no comparison available",
finfo->fullname);
freevers_ts (&vers);
diff_mark_errors (err);
return (err);
}
}
else
{
if (vers->vn_rcs == NULL && vers->srcfile == NULL)
{
error (0, 0, "cannot find revision control file for %s",
finfo->fullname);
freevers_ts (&vers);
diff_mark_errors (err);
return (err);
}
else
{
if (vers->ts_user == NULL)
{
error (0, 0, "cannot find %s", finfo->fullname);
freevers_ts (&vers);
diff_mark_errors (err);
return (err);
}
else if (!strcmp (vers->ts_user, vers->ts_rcs))
{
user_file_rev = vers->vn_user;
}
}
}
empty_file = diff_file_nodiff (finfo, vers, empty_file);
if (empty_file == DIFF_SAME || empty_file == DIFF_ERROR)
{
freevers_ts (&vers);
if (empty_file == DIFF_SAME)
{
return (0);
}
else
{
diff_mark_errors (err);
return (err);
}
}
if (empty_file == DIFF_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 = DIFF_ADDED;
else
{
error (0, 0, "%s is a new entry, no comparison available",
finfo->fullname);
freevers_ts (&vers);
diff_mark_errors (err);
return (err);
}
}
else if (dead2)
{
if (empty_files)
empty_file = DIFF_REMOVED;
else
{
error (0, 0, "%s was removed, no comparison available",
finfo->fullname);
freevers_ts (&vers);
diff_mark_errors (err);
return (err);
}
}
}
cvs_output ("Index: ", 0);
cvs_output (finfo->fullname, 0);
cvs_output ("\n", 1);
tocvsPath = wrap_tocvs_process_file(finfo->file);
if (tocvsPath)
{
fname = xmalloc (strlen (finfo->file)
+ sizeof CVSADM
+ sizeof CVSPREFIX
+ 10);
sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file);
if (unlink_file_dir (fname) < 0)
if (! existence_error (errno))
error (1, errno, "cannot remove %s", fname);
rename_file (finfo->file, fname);
copy_file (tocvsPath, finfo->file);
}
if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
{
cvs_output ("\
===================================================================\n\
RCS file: ", 0);
cvs_output (finfo->file, 0);
cvs_output ("\n", 1);
cvs_output ("diff -N ", 0);
cvs_output (finfo->file, 0);
cvs_output ("\n", 1);
if (empty_file == DIFF_ADDED)
{
if (use_rev2 == NULL)
status = diff_exec (DEVNULL, finfo->file, opts, RUN_TTY);
else
{
int retcode;
tmp = cvs_temp_name ();
retcode = RCS_checkout (vers->srcfile, (char *) NULL,
use_rev2, (char *) NULL,
(*options
? options
: vers->options),
tmp, (RCSCHECKOUTPROC) NULL,
(void *) NULL);
if (retcode != 0)
{
diff_mark_errors (err);
return err;
}
status = diff_exec (DEVNULL, tmp, opts, RUN_TTY);
}
}
else
{
int retcode;
tmp = cvs_temp_name ();
retcode = RCS_checkout (vers->srcfile, (char *) NULL,
use_rev1, (char *) NULL,
*options ? options : vers->options,
tmp, (RCSCHECKOUTPROC) NULL,
(void *) NULL);
if (retcode != 0)
{
diff_mark_errors (err);
return err;
}
status = diff_exec (tmp, DEVNULL, opts, RUN_TTY);
}
}
else
{
char *label1 = NULL;
char *label2 = NULL;
if (!have_rev1_label)
label1 =
make_file_label (finfo->fullname, use_rev1, vers->srcfile);
if (!have_rev2_label)
label2 =
make_file_label (finfo->fullname, use_rev2, vers->srcfile);
status = RCS_exec_rcsdiff (vers->srcfile, opts,
*options ? options : vers->options,
use_rev1, use_rev2,
label1, label2,
finfo->file);
if (label1) free (label1);
if (label2) free (label2);
}
switch (status)
{
case -1:
error (1, errno, "fork failed while diffing %s",
vers->srcfile->path);
case 0:
err = 0;
break;
default:
err = status;
break;
}
if (tocvsPath)
{
if (unlink_file_dir (finfo->file) < 0)
if (! existence_error (errno))
error (1, errno, "cannot remove %s", finfo->file);
rename_file (fname, finfo->file);
if (unlink_file (tocvsPath) < 0)
error (1, errno, "cannot remove %s", tocvsPath);
free (fname);
}
if (empty_file == DIFF_REMOVED
|| (empty_file == DIFF_ADDED && use_rev2 != NULL))
{
if (CVS_UNLINK (tmp) < 0)
error (0, errno, "cannot remove %s", tmp);
free (tmp);
}
freevers_ts (&vers);
diff_mark_errors (err);
return (err);
}
static void
diff_mark_errors (err)
int err;
{
if (err > diff_errors)
diff_errors = err;
}
static Dtype
diff_dirproc (callerdat, dir, pos_repos, update_dir, entries)
void *callerdat;
char *dir;
char *pos_repos;
char *update_dir;
List *entries;
{
if (!isdir (dir))
return (R_SKIP_ALL);
if (!quiet)
error (0, 0, "Diffing %s", update_dir);
return (R_PROCESS);
}
static int
diff_filesdoneproc (callerdat, err, repos, update_dir, entries)
void *callerdat;
int err;
char *repos;
char *update_dir;
List *entries;
{
return (diff_errors);
}
static int
diff_dirleaveproc (callerdat, dir, err, update_dir, entries)
void *callerdat;
char *dir;
int err;
char *update_dir;
List *entries;
{
return (diff_errors);
}
static enum diff_file
diff_file_nodiff (finfo, vers, empty_file)
struct file_info *finfo;
Vers_TS *vers;
enum diff_file empty_file;
{
Vers_TS *xvers;
int retcode;
if (use_rev1)
free (use_rev1);
if (use_rev2)
free (use_rev2);
use_rev1 = use_rev2 = (char *) NULL;
if (diff_rev1 || diff_date1)
{
if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
use_rev1 = ((vers->vn_rcs == NULL || vers->srcfile == NULL)
? NULL
: RCS_branch_head (vers->srcfile, vers->vn_rcs));
else
{
xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0);
if (xvers->vn_rcs != NULL)
use_rev1 = xstrdup (xvers->vn_rcs);
freevers_ts (&xvers);
}
}
if (diff_rev2 || diff_date2)
{
if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
use_rev2 = ((vers->vn_rcs == NULL || vers->srcfile == NULL)
? NULL
: RCS_branch_head (vers->srcfile, vers->vn_rcs));
else
{
xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0);
if (xvers->vn_rcs != NULL)
use_rev2 = xstrdup (xvers->vn_rcs);
freevers_ts (&xvers);
}
if (use_rev1 == NULL)
{
if (use_rev2 == NULL)
return DIFF_SAME;
else if (empty_files)
return DIFF_ADDED;
else if (diff_rev1)
error (0, 0, "tag %s is not in file %s", diff_rev1,
finfo->fullname);
else
error (0, 0, "no revision for date %s in file %s",
diff_date1, finfo->fullname);
return DIFF_ERROR;
}
if (use_rev2 == NULL)
{
if (empty_files)
return DIFF_REMOVED;
else if (diff_rev2)
error (0, 0, "tag %s is not in file %s", diff_rev2,
finfo->fullname);
else
error (0, 0, "no revision for date %s in file %s",
diff_date2, finfo->fullname);
return DIFF_ERROR;
}
if (strcmp (use_rev1, use_rev2) == 0)
return DIFF_SAME;
else
return DIFF_DIFFERENT;
}
if ((diff_rev1 || diff_date1) && use_rev1 == NULL)
{
if (empty_files)
{
if (empty_file == DIFF_REMOVED)
return DIFF_SAME;
else
{
if (user_file_rev && use_rev2 == NULL)
use_rev2 = xstrdup (user_file_rev);
return DIFF_ADDED;
}
}
else
{
if (diff_rev1)
error (0, 0, "tag %s is not in file %s", diff_rev1,
finfo->fullname);
else
error (0, 0, "no revision for date %s in file %s",
diff_date1, finfo->fullname);
return DIFF_ERROR;
}
}
if (user_file_rev)
{
if (!use_rev1)
use_rev1 = xstrdup (user_file_rev);
else if (!use_rev2)
use_rev2 = xstrdup (user_file_rev);
user_file_rev = 0;
}
if (use_rev1 && use_rev2)
{
if (strcmp (use_rev1, use_rev2) == 0)
return DIFF_SAME;
else
return DIFF_DIFFERENT;
}
if (use_rev1 == NULL
|| (vers->vn_user != NULL && strcmp (use_rev1, vers->vn_user) == 0))
{
if (empty_file == DIFF_DIFFERENT
&& vers->ts_user != NULL
&& strcmp (vers->ts_rcs, vers->ts_user) == 0
&& (!(*options) || strcmp (options, vers->options) == 0))
{
return DIFF_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);
}
}
if (empty_file != DIFF_DIFFERENT)
return empty_file;
retcode = RCS_cmp_file (vers->srcfile, use_rev1,
*options ? options : vers->options,
finfo->file);
return retcode == 0 ? DIFF_SAME : DIFF_DIFFERENT;
}