vandys@sequent.com (Andrew Valencia) (06/25/91)
This article has two purposes. First, I'd like to know if it's making
it out to the world. If not (since I AM doing the approval correctly),
I'll have to go talk with our News admin. If it's working, I'm probably
going to get a million messages, but it's worth it :-).
Second, a mandatory hack. This program lets you interactively
merge together two source files. You basically look at individual
chunks of diff(1) output, and type >, <, or = to pick which variation
of the diff you want. = means include both, in merge-conflict format
ala the merge(1) command.
Enjoy!
Andy Valencia
vandys@sequent.com
/*
* idiff.c--routine to interactively apply diff(1) output to a file
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <ctype.h>
/*
* Data structure for managing an incoming stream we're merging
*/
struct istream {
FILE *fp; /* File we get data from */
int lnum; /* Line number counter */
char buf[512]; /* Line buffer */
};
/*
* Data structure representing a single "diff" entry
*/
struct diffelem {
char op; /* 'a', 'd', or 'c' */
int lnum1[2]; /* Line range for file1 */
int lnum2[2]; /* ... for file2 */
};
/*
* These are the three types of actions available to the user when
* choosing what to do with a diff entry.
*/
#define LEFT 1 /* Take the '<' version of the diff */
#define RIGHT 2 /* ... the '>' version */
#define MERGE 3 /* Put both in merge-conflict format in the output */
#define EDIT 4 /* Put both in tmp, let user edit it */
static void skipto(), copyto(), copyrange();
void idiff();
extern void exit();
extern unsigned strlen();
extern char *strrchr();
main(argc, argv)
int argc;
char **argv;
{
FILE *fp1, *fp2, *out, *diff;
char buf[256], *fname;
struct istream f1, f2;
if (argc != 3) {
(void)fprintf(stderr, "Usage is: %s <file1> <file2>\n",
argv[0]);
exit(1);
}
/* Open input file */
if ((fp1 = fopen(argv[1], "r")) == NULL) {
perror(argv[1]);
exit(1);
}
/*
* If second argument is a directory, create our filename by
* tacking on the basename of the first file.
*/
if (isdir(argv[2])) {
char *p;
sprintf(buf, "%s/", argv[2]);
if (!(p = strrchr(argv[1], '/')))
p = argv[1];
else
p += 1;
strcat(buf, p);
fname = buf;
} else {
fname = argv[2];
}
if ((fp2 = fopen(fname, "r")) == NULL) {
perror(fname);
exit(1);
}
if (access("idiff.out", 0) >= 0) {
fprintf(stderr, "Error: idiff.out exists.\n");
exit(1);
}
if ((out = fopen("idiff.out", "w")) == NULL) {
perror("idiff.out");
exit(1);
}
f1.fp = fp1;
f2.fp = fp2;
/*
* Fire up our diff program
*/
(void)sprintf(buf, "diff %s %s", argv[1], argv[2]);
if ((diff = popen(buf, "r")) == NULL) {
(void)fprintf(stderr, "diff command failed: %s\n", buf);
exit(1);
}
/*
* Do our idiff algorithm
*/
idiff(&f1, &f2, diff, out);
printf("Result is in idiff.out\n");
return(0);
}
/*
* Get the one or two arguments, fill into int array
*/
static char *
getargs(p, args)
register char *p;
int *args;
{
(void)sscanf(p, "%d", args);
while (isdigit(*p))
++p;
/*
* If second number, take it, too
*/
if (*p == ',') {
++p;
(void)sscanf(p, "%d", args+1);
while (isdigit(*p))
++p;
} else
args[1] = args[0];
return(p);
}
/*
* getdiff()--read the next "event" from the diff output, encode it
* in our data structure
*/
getdiff(fp, d)
FILE *fp;
struct diffelem *d;
{
char buf[512];
register int c;
register char *p;
/*
* Read next line
*/
p = buf;
while ((c = getc(fp)) != EOF)
if (c == '\n')
break;
else
*p++ = c;
*p = '\0';
/*
* EOF--all done
*/
if (c == EOF)
return(0);
/*
* Parse into shape
*/
/*
* Get first numbers, advance beyond
*/
p = buf;
p = getargs(p, d->lnum1);
/*
* Record letter operation
*/
d->op = *p++;
/*
* Get second numbers
*/
(void) getargs(p, d->lnum2);
return(1);
}
/*
* A safe version of gets
*/
static void
mygets(buf, lim)
char *buf;
int lim;
{
register char *p = buf, *ebuf = buf+lim-1;
register int c;
/*
* Avoid hard EOF on stdin
*/
if (feof(stdin))
clearerr(stdin);
while ((c = getchar()) != EOF) {
if (c == '\n')
break;
if (p < ebuf)
*p++ = c;
}
*p = '\0';
}
/*
* Prompt for a choice in resolving the diff merge
*/
static
getaction()
{
char buf[32];
for (;;) {
(void)printf("Action ('<', '>', or '='): ");
mygets(buf, sizeof(buf));
switch (*buf) {
case '<':
return(LEFT);
case '>':
return(RIGHT);
case '=':
return(MERGE);
case 'e':
return(EDIT);
case 'q':
(void)printf("Are you sure? ");
mygets(buf, sizeof(buf));
if ((*buf == 'y') || (*buf == 'Y'))
exit(1);
continue;
default:
(void)printf("Bad selection, try again.\n");
continue;
}
}
}
/*
* copyrange()--copy lines to output for given range
*/
static void
copyrange(f, range, out)
struct istream *f;
int *range;
FILE *out;
{
skipto(f, range[0]);
do {
(void)fputs(f->buf, out);
if (fgets(f->buf, sizeof(f->buf), f->fp) == NULL) {
perror("copyrange");
exit(1);
}
f->lnum += 1;
} while (f->lnum <= range[1]);
}
/*
* copyto()--copy lines to output until reach specified line
*/
static void
copyto(f, lnum, out)
struct istream *f;
int lnum;
FILE *out;
{
while (f->lnum != lnum) {
if (f->lnum)
(void)fputs(f->buf, out);
if (fgets(f->buf, sizeof(f->buf), f->fp) == NULL) {
perror("copyto");
exit(1);
}
f->lnum += 1;
}
}
/*
* skipto()--skip to given line number on a file
*/
static void
skipto(f, lnum)
struct istream *f;
int lnum;
{
while (f->lnum != lnum) {
if (fgets(f->buf, sizeof(f->buf), f->fp) == NULL) {
perror("skipto");
exit(1);
}
f->lnum += 1;
}
}
/*
* Copy rest of file to output, don't try to keep line number
* accurate. Used only at end of processing to copy trailing lines.
*/
static void
copyrest(f, out)
register struct istream *f;
FILE *out;
{
register int c;
if (f->lnum)
(void)fputs(f->buf, out);
while ((c = getc(f->fp)) != EOF)
(void)putc(c, out);
}
/*
* showdiff()--dump output from diff until start of next new entry
*/
static void
showdiff(f)
FILE *f;
{
register int c;
/*
* Copy output until EOF worst case
*/
while ((c = getc(f)) != EOF) {
/*
* A line starting with a digit is the beginning of the
* next diff entry. Push the character back and return.
*/
if (isdigit(c)) {
(void)ungetc(c, f);
return;
}
/*
* Otherwise dump the line out
*/
(void)putchar(c);
while ((c = getc(f)) != EOF) {
(void)putchar(c);
if (c == '\n')
break;
}
}
}
/*
* do_change()--interactively apply an change-type diff entry
*/
void
do_change(f1, f2, out, d)
struct istream *f1, *f2;
FILE *out;
struct diffelem *d;
{
/*
* Let the user choose
*/
switch (getaction()) {
case LEFT: /* Take our existing text */
copyto(f1, d->lnum1[1]+1, out);
return;
case RIGHT: /* Take the new text */
copyto(f2, d->lnum2[1]+1, out);
skipto(f1, d->lnum1[1]+1);
break;
case MERGE: /* Give them the choice */
(void)fprintf(out, "<<<<<<<\n");
copyto(f1, d->lnum1[1]+1, out);
(void)fprintf(out, "=======\n");
copyto(f2, d->lnum2[1]+1, out);
(void)fprintf(out, ">>>>>>>\n");
break;
}
}
/*
* do_delete()--interactively apply an delete-type diff entry
*/
void
do_delete(f1, f2, out, d)
struct istream *f1, *f2;
FILE *out;
struct diffelem *d;
{
#ifdef lint
if (f2)
f1 = f2;
#endif
/*
* Let the user choose
*/
switch (getaction()) {
case LEFT: /* Delete nothing */
return;
case RIGHT: /* "take" the deletion */
skipto(f1, d->lnum1[1]+1);
break;
case MERGE: /* Give them the choice */
(void)fprintf(out, "<<<<<<<\n");
copyto(f1, d->lnum1[1]+1, out);
(void)fprintf(out, "=======\n");
(void)fprintf(out, ">>>>>>>\n");
break;
}
}
/*
* do_append()--interactively apply an append-type diff entry
*/
void
do_append(f1, f2, out, d)
struct istream *f1, *f2;
FILE *out;
struct diffelem *d;
{
#ifdef lint
if (f1)
f2 = f1;
#endif
/*
* Append operation--so copy beyond the specified point,
* not just until it.
*/
copyto(f1, d->lnum1[0]+1, out);
/*
* Let the user choose
*/
switch (getaction()) {
case LEFT: /* Add nothing */
return;
case RIGHT: /* Add in the new lines */
copyrange(f2, d->lnum2, out);
break;
case MERGE:
(void)fprintf(out, "<<<<<<<\n");
(void)fprintf(out, "=======\n");
copyrange(f2, d->lnum2, out);
(void)fprintf(out, ">>>>>>>\n");
break;
}
}
/*
* Read diff stream, interactively apply diffs, write to output
*/
void
idiff(f1, f2, diff, out)
struct istream *f1, *f2;
FILE *diff, *out;
{
struct diffelem d;
/*
* While have diff elements, apply them
*/
while (getdiff(diff, &d)) {
/*
* Move to the next diff block in each file
*/
copyto(f1, d.lnum1[0], out);
skipto(f2, d.lnum2[0]);
/*
* Show them the diff--this consumes it from the diff
* stream, but we already know what lines are involved,
* and we'll take them from the original source.
*/
showdiff(diff);
/*
* Choose our action based on the operation--append, delete
* or change.
*/
switch (d.op) {
case 'a':
do_append(f1, f2, out, &d);
break;
case 'd':
do_delete(f1, f2, out, &d);
break;
case 'c':
do_change(f1, f2, out, &d);
break;
default:
(void)printf("Illegal op %c\n", d.op);
exit(1);
}
}
copyrest(f1, out);
}
/*
* Tell whether the named file is a directory
*/
isdir(fname)
char *fname;
{
struct stat sb;
if (stat(fname, &sb) < 0) {
perror(fname);
exit(1);
}
return ((sb.st_mode & S_IFMT) == S_IFDIR);
}