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