[alt.hackers] Hack and probe

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);
}