/* * Program to turn git diff3 conflict-resolution text into diff files. * * Specifically, this reads its input, searching for lines beginning * with >>>>>>>, |||||||, =======, or <<<<<<<. These are taken as * marking a git conflict in diff3 style. They are taken as marking * the `into' (HEAD), `base' (the merge base), and `from' (the commit * being merged from) pieces of a commit conflict. These are used to * generate three diffs: base-to-into, base-to-from, and into-to-from. * Six files are created, three holding these diffs and three holding * the pieces which were fed to diff. * * Each such conflict generates one set of three files. Each such set * has a prefix, detailed below; using PREFIX to stand for that, the * files are named PREFIX.base-into, PREFIX.base-from, and * PREFIX.into-from. There is a prefix string, specified on the * command line or defaulted to "z"; calling that PFX, the first * triple of files uses PFX as its PREFIX, the ones after that use * PFX%d for increasing arguments, starting with 2. Thus, by default, * you get z.base-into, z.base-from, and z.into-from for the first * conflict, z2.base-into, z2.base-from, and z2.info-from for the * second, z3.base-into, z3.base-from, and z3.info-from for the third, * etc. * * By default, produces nothing on stdout. With -echo, echos its input * to stdout (for use with filter-region). -noecho negates -echo. * * -filter FILTER causes all diff output to be run through FILTER * before being written to the relevant file. * * Produces nothing on stderr except for error reports. * * Copyright status: this program is in the public domain. */ #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "stdio-util.h" static const char *pfx = "z"; static int echo = 0; static const char *filter = 0; static const char *dev_null = "/dev/null"; static int nullfd; static int set_num; static int lno; static ES iline; static ES parts[3]; static int part; static int lno0; static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { if (! pfx) { pfx = *av; } else { fprintf(stderr,"%s: extra argument `%s'\n",__progname,*av); errs = 1; } continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs = 1; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-echo")) { echo = 1; continue; } if (!strcmp(*av,"-noecho")) { echo = 0; continue; } if (!strcmp(*av,"-pfx")) { WANTARG(); pfx = av[skip]; continue; } if (!strcmp(*av,"-filter")) { WANTARG(); filter = av[skip]; continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs = 1; } if (errs) exit(1); } static int get_in(void) { int c; c = getchar(); if (c == EOF) return(EOF); if (echo) putchar(c); return(c); } static void setup(void) { int fd; set_num = 1; es_init(&parts[0]); es_init(&parts[1]); es_init(&parts[2]); es_init(&iline); part = -1; lno = 1; nullfd = open(dev_null,O_RDWR,0); if (nullfd < 0) { fprintf(stderr,"%s: can't open %s: %s\n",__progname,dev_null,strerror(errno)); exit(1); } while (nullfd < 3) { fd = dup(nullfd); if (fd < 0) { fprintf(stderr,"%s: can't dup %s: %s\n",__progname,dev_null,strerror(errno)); exit(1); } nullfd = fd; } } static int try_read_output(const struct pollfd *pfd, int fd, char *buf, int bufsize, int *lenp, int *eofp, int eofbit) { int nr; if (! (pfd->revents & (POLLIN|POLLRDNORM|POLLERR|POLLHUP|POLLNVAL))) return(0); nr = read(fd,buf,bufsize); if (nr < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return(0); break; default: fprintf(stderr,"%s: read error on diff output: %s\n",__progname,strerror(errno)); exit(1); break; } } *lenp = nr; if (nr == 0) *eofp |= eofbit; return(nr!=0); } static int addpfd(struct pollfd *pfdv, int *npfdp, int fd, int ev) { int i; i = npfdp[0] ++; pfdv[i].fd = fd; pfdv[i].events = ev; return(i); } static void write_to(int fd, const void *data, int len, const char *on) { const char *dp; int p; int n; dp = data; p = 0; while (p < len) { n = write(fd,dp+p,len-p); if (n < 0) { switch (errno) { case EINTR: continue; break; default: fprintf(stderr,"%s: write on %s: %s\n",__progname,on,strerror(errno)); exit(1); } } p += n; } } /* * There are a lot of file descriptors involved here - 22 of them, if a * filter is in use, or 18, if not. * * ip is input pipes, from us to diff. ip[0] is the pipe for arg 1, * ip[2], arg 2. * * ipp is inter-process pipes. ipp[0] is the pipe between diff and * sed. ipp[1] is the pipe between sed and the filter, if filter, or * unused, if not. * * op is the output pipe, from sed or the filter (as appropriate) back * to us. * * There are various per-process things, such as stderr pipes and * output buffers. These are always indexed [0] for diff, [1] for * sed, [2] either filter (if filter) or unused (if not). * * xp is exec error socketpairs, per-process. * * ep is stderr pipes, per-process. * * Other per-process things: eout[], holding stderr buffers; epx[], * holding pfd[] indices for error output. */ static void run_diff(ES *f, ES *t, const char *suf1, const char *suf2) { static const char * const pgm[3] = { "diff", "sed", "shell" }; char *fn; pid_t kid; int ip[2][2]; int ipp[2][2]; int op[2]; int ep[3][2]; int xp[3][2]; int ofd; char *diffarg[2]; char *sedarg[2]; const char *obuf[2]; int olen[2]; int optr[2]; int eofs; int e; int n; ES eout[3]; char rbuf[512]; int rlen; int ipx[2]; int opx; int epx[3]; struct pollfd pfd[6]; int npfd; int i; char *hline; int hllen; const char *shell; hllen = asprintf(&hline,"(%s -> %s diff for conflict starting at line %d)\n",suf1,suf2,lno0); if (set_num == 1) { asprintf(&fn,"%s.d.%s-%s",pfx,suf1,suf2); } else { asprintf(&fn,"%s%d.d.%s-%s",pfx,set_num,suf1,suf2); } ofd = open(fn,O_WRONLY|O_CREAT,0666); if (ofd < 0) { fprintf(stderr,"%s: can't create %s: %s\n",__progname,fn,strerror(errno)); exit(1); } if ( (pipe(&ip[0][0]) < 0) || (pipe(&ip[1][0]) < 0) || (pipe(&ipp[0][0]) < 0) || (pipe(&op[0]) < 0) || (pipe(&ep[0][0]) < 0) || (pipe(&ep[1][0]) < 0) || (filter ? (pipe(&ipp[1][0]) < 0) || (pipe(&ep[2][0]) < 0) : 0) ) { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno)); exit(1); } if ( (socketpair(AF_LOCAL,SOCK_STREAM,0,&xp[0][0]) < 0) || (socketpair(AF_LOCAL,SOCK_STREAM,0,&xp[1][0]) < 0) || (filter ? (socketpair(AF_LOCAL,SOCK_STREAM,0,&xp[2][0]) < 0) : 0) ) { fprintf(stderr,"%s: exec socketpair: %s\n",__progname,strerror(errno)); exit(1); } fcntl(xp[0][1],F_SETFD,1); fcntl(xp[1][1],F_SETFD,1); if (filter) fcntl(xp[2][1],F_SETFD,1); asprintf(&diffarg[0],"/dev/fd/%d",ip[0][0]); asprintf(&diffarg[1],"/dev/fd/%d",ip[1][0]); asprintf(&sedarg[0],"1s@%s@%s@",diffarg[0],suf1); asprintf(&sedarg[1],"2s@%s@%s@",diffarg[1],suf2); fflush(0); kid = fork(); if (kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { // ip[0][0] stays right where it is close(ip[0][1]); // ip[1][0] stays right where it is close(ip[1][1]); close(ipp[0][0]); dup2(ipp[0][1],1); close(ipp[0][1]); close(op[0]); close(op[1]); close(ep[0][0]); dup2(ep[0][1],2); close(ep[1][0]); close(ep[1][1]); close(xp[0][0]); close(xp[1][0]); close(xp[1][1]); if (filter) { close(ipp[1][0]); close(ipp[1][1]); close(ep[2][0]); close(ep[2][1]); close(xp[2][0]); close(xp[2][1]); } execlp("diff","diff","-u",diffarg[0],diffarg[1],(const char *)0); e = errno; write(xp[0][1],&e,sizeof(e)); _exit(0); } kid = fork(); if (kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { close(ip[0][0]); close(ip[0][1]); close(ip[1][0]); close(ip[1][1]); close(ipp[0][1]); dup2(ipp[0][0],0); close(ipp[0][0]); if (filter) { close(op[0]); close(op[1]); close(ipp[1][0]); dup2(ipp[1][1],1); close(ipp[1][1]); } else { close(op[0]); dup2(op[1],1); close(op[1]); } close(ep[0][0]); close(ep[0][1]); close(ep[1][0]); dup2(ep[1][1],2); close(ep[1][1]); close(xp[0][0]); close(xp[0][1]); close(xp[1][0]); if (filter) { close(ep[2][0]); close(ep[2][1]); close(xp[2][0]); close(xp[2][1]); } execlp("sed","sed","-e",sedarg[0],"-e",sedarg[1],(const char *)0); e = errno; write(xp[1][1],&e,sizeof(e)); _exit(0); } if (filter) { shell = getenv("SHELL"); if (! shell) shell = "bin/sh"; kid = fork(); if (kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { close(ip[0][0]); close(ip[0][1]); close(ip[1][0]); close(ip[1][1]); close(ipp[0][0]); close(ipp[0][1]); dup2(ipp[1][0],0); close(ipp[1][0]); close(ipp[1][1]); close(op[0]); dup2(op[1],1); close(op[1]); close(ep[0][0]); close(ep[0][1]); close(ep[1][0]); close(ep[1][1]); close(ep[2][0]); dup2(ep[2][1],2); close(ep[2][1]); close(xp[0][0]); close(xp[0][1]); close(xp[1][0]); close(xp[1][1]); close(xp[2][0]); execlp(shell,shell,"-c",filter,(const char *)0); e = errno; write(xp[2][1],&e,sizeof(e)); _exit(0); } } close(ip[0][0]); close(ip[1][0]); close(ipp[0][0]); close(ipp[0][1]); close(ipp[1][0]); close(ipp[1][1]); close(op[1]); close(ep[0][1]); close(ep[1][1]); close(xp[0][1]); close(xp[1][1]); if (filter) { close(ep[2][1]); close(xp[2][1]); } for (i=(filter?3:2)-1;i>=0;i--) { n = recv(xp[i][0],&e,sizeof(e),MSG_WAITALL); if (n < 0) { fprintf(stderr,"%s: exec recv: %s\n",__progname,strerror(errno)); exit(1); } if (n == sizeof(e)) { fprintf(stderr,"%s: exec %s: %s\n",__progname,pgm[i],strerror(e)); exit(1); } if (n != 0) { fprintf(stderr,"%s: exec protocol error: wanted %d or 0, got %d\n",__progname,(int)sizeof(e),n); exit(1); } close(xp[i][0]); } obuf[0] = es_buf(f); olen[0] = es_len(f); optr[0] = 0; obuf[1] = es_buf(t); olen[1] = es_len(t); optr[1] = 0; eofs = filter ? 0 : 8; es_init(&eout[0]); es_init(&eout[1]); fcntl(ip[0][1],F_SETFL,fcntl(ip[0][1],F_GETFL,0)|O_NONBLOCK); fcntl(ip[1][1],F_SETFL,fcntl(ip[1][1],F_GETFL,0)|O_NONBLOCK); fcntl(op[0],F_SETFL,fcntl(op[0],F_GETFL,0)|O_NONBLOCK); fcntl(ep[0][0],F_SETFL,fcntl(ep[0][0],F_GETFL,0)|O_NONBLOCK); fcntl(ep[1][0],F_SETFL,fcntl(ep[1][0],F_GETFL,0)|O_NONBLOCK); if (filter) { es_init(&eout[2]); fcntl(ep[2][0],F_SETFL,fcntl(ep[2][0],F_GETFL,0)|O_NONBLOCK); } write_to(ofd,hline,hllen,fn); while (eofs != 15) { npfd = 0; for (i=2-1;i>=0;i--) { if (optr[i] < olen[i]) { ipx[i] = addpfd(&pfd[0],&npfd,ip[i][1],POLLOUT|POLLWRNORM); } else { ipx[i] = -1; if (ip[i][1] >= 0) { close(ip[i][1]); ip[i][1] = -1; } } } if (eofs & 1) opx = -1; else opx = addpfd(&pfd[0],&npfd,op[0],POLLIN|POLLRDNORM); if (eofs & 2) epx[0] = -1; else epx[0] = addpfd(&pfd[0],&npfd,ep[0][0],POLLIN|POLLRDNORM); if (eofs & 4) epx[1] = -1; else epx[1] = addpfd(&pfd[0],&npfd,ep[1][0],POLLIN|POLLRDNORM); if (eofs & 8) epx[2] = -1; else epx[2] = addpfd(&pfd[0],&npfd,ep[2][0],POLLIN|POLLRDNORM); if (npfd < 1) abort(); e = poll(&pfd[0],npfd,INFTIM); if (e < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: poll: %s\n",__progname,strerror(errno)); exit(1); } for (i=2-1;i>=0;i--) { if ((ipx[i] >= 0) && (pfd[ipx[i]].revents & (POLLOUT|POLLWRNORM|POLLERR|POLLHUP|POLLNVAL))) { n = write(ip[i][1],obuf[i]+optr[i],olen[i]-optr[i]); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: continue; break; default: fprintf(stderr,"%s: write on pipe to diff: %s\n",__progname,strerror(errno)); exit(1); } } optr[i] += n; } } if ((opx >= 0) && try_read_output(&pfd[opx],op[0],&rbuf[0],sizeof(rbuf),&rlen,&eofs,1)) write_to(ofd,&rbuf[0],rlen,fn); if ((epx[0] >= 0) && try_read_output(&pfd[epx[0]],ep[0][0],&rbuf[0],sizeof(rbuf),&rlen,&eofs,2)) es_append_n(&eout[0],&rbuf[0],rlen); if ((epx[1] >= 0) && try_read_output(&pfd[epx[1]],ep[1][0],&rbuf[0],sizeof(rbuf),&rlen,&eofs,4)) es_append_n(&eout[1],&rbuf[0],rlen); if ((epx[2] >= 0) && try_read_output(&pfd[epx[2]],ep[2][0],&rbuf[0],sizeof(rbuf),&rlen,&eofs,8)) es_append_n(&eout[2],&rbuf[0],rlen); } for (i=2-1;i>=0;i--) if (ip[i][1] >= 0) close(ip[i][1]); close(op[0]); close(ep[0][0]); close(ep[1][0]); if (filter) close(ep[2][0]); for (i=0;i<(filter?3:2);i++) { if (es_len(&eout[i])) { FILE *f; fprintf(stderr,"%s: note: %s for %s-%s printed on stderr:\n",__progname,pgm[i],suf1,suf2); f = fwrap_indent(stderr); fwrite(es_buf(&eout[i]),1,es_len(&eout[i]),f); if (((char *)es_buf(&eout[i]))[es_len(&eout[i])-1] != '\n') putc('\n',f); fclose(f); } es_done(&eout[i]); } if (close(ofd) < 0) fprintf(stderr,"%s: closing %s: %s\n",__progname,fn,strerror(errno)); free(fn); free(diffarg[0]); free(diffarg[1]); free(sedarg[0]); free(sedarg[1]); } static void save_piece(ES *pc, const char *filesuf) { char *fn; int fd; char *hdr; int hlen; if (set_num == 1) { asprintf(&fn,"%s.f.%s",pfx,filesuf); } else { asprintf(&fn,"%s%d.f.%s",pfx,set_num,filesuf); } fd = open(fn,O_WRONLY|O_CREAT,0666); if (fd < 0) { fprintf(stderr,"%s: can't create %s: %s\n",__progname,fn,strerror(errno)); exit(1); } hlen = asprintf(&hdr,"(%s for conflict starting at line %d)\n",filesuf,lno0); write_to(fd,hdr,hlen,fn); write_to(fd,es_buf(pc),es_len(pc),fn); if (close(fd) < 0) fprintf(stderr,"%s: closing %s: %s\n",__progname,fn,strerror(errno)); free(hdr); free(fn); } /* * Note that run_diff depends (in its construction of sed args) on its * third and fourth args not containing @ signs. */ static void process_parts(void) { save_piece(&parts[0],"into"); save_piece(&parts[1],"base"); save_piece(&parts[2],"from"); run_diff(&parts[1],&parts[0],"base","into"); run_diff(&parts[1],&parts[2],"base","from"); run_diff(&parts[0],&parts[2],"into","from"); set_num ++; } static void process_line(void) { const char *b; int l; int wantp; b = es_buf(&iline); l = es_len(&iline); if ( (l < 7) || (b[1] != b[0]) || (b[2] != b[0]) || (b[3] != b[0]) || (b[4] != b[0]) || (b[5] != b[0]) || (b[6] != b[0]) || ((l > 7) && (b[7] != ' ')) ) return; switch (b[0]) { default: return; break; case '<': if (part != -1) { fprintf(stderr,"%s: line %d: <<<<<<< line in conflict part %d\n",__progname,lno,part); return; } part = 0; es_clear(&parts[0]); lno0 = lno; break; case '|': wantp = 0; if (0) { case '=': wantp = 1; } if (0) { case '>': wantp = 2; } if (part < 0) { fprintf(stderr,"%s: line %d: %c%c%c%c%c%c%c line outside conflict\n",__progname,lno,b[0],b[0],b[0],b[0],b[0],b[0],b[0]); return; } if (part != wantp) { fprintf(stderr,"%s: line %d: %c%c%c%c%c%c%c line in conflict part %d\n",__progname,lno,part,b[0],b[0],b[0],b[0],b[0],b[0],b[0]); return; } es_pop_n(&parts[part],es_len(&iline)+1); part ++; if (part > 2) { process_parts(); part = -1; } else { es_clear(&parts[part]); } break; } } static void process(void) { int c; while (1) { c = get_in(); if (c == EOF) break; if (c == '\n') { if (part >= 0) es_append_1(&parts[part],'\n'); process_line(); es_clear(&iline); lno ++; } else { if (part >= 0) es_append_1(&parts[part],c); es_append_1(&iline,c); } } } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); setup(); process(); return(0); }