#include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "fatal.h" #include "subproc.h" #include "memstream.h" #include "datastruct.h" #include "git-intf.h" /* * git diff output: * * Modified file: * diff --git a/filename b/filename * index 0123456..0123456 100644 * --- a/filename * +++ b/filename * [hunks] * * New files: * diff --git a/filename b/filename * new file mode 100644 * index 0000000..0123456 100644 * --- /dev/null * +++ b/filename * [hunk] * * Deleted files: * diff --git a/filename b/filename * deleted file mode 100644 * index 0123456..0000000 * --- a/filename * +++ /dev/null * [hunk] * * Funky filename: * diff --git "a/file\nname" "b/file\nname" * --- "a/file\nname" * Escapes: * \a (7) \b (8) \t (9) \n (10) \v (11) \f (12) \r (13) * \ooo (0ooo) \" (") \\ (\) * Spaces do not funkify a filename in this sense; they are treated * like letters - but they _do_, weirdly, cause a tab to be appended * to the name on --- and +++ lines. (Why? I have NO FUCKING CLUE. * Go ask whoever wrote git's diff.c; I see no functionality reason * whatsoever for that code.) * * git checkout-index --temp output: * * tempname gitname * * If the gitname has funny characters, it is escaped as described * above (but without the trailing tab weirdness); this can be * detected by the gitname beginning with a ". */ struct gitfi { int ifd; PROC *p; } ; static void *dequal(volatile const void *v) { return((((volatile const char *)v)-(volatile const char *)0)+(char *)0); } static char *unescape(const char *es, int maxlen) { char *s; int i; int j; s = malloc(maxlen+1); j = 0; i = 0; while (i < maxlen) { if (es[i] == '\\') { switch <"esc"> (es[i+1]) { case 'a': s[j++] = 7; break; case 'b': s[j++] = 8; break; case 't': s[j++] = 9; break; case 'n': s[j++] = 10; break; case 'v': s[j++] = 11; break; case 'f': s[j++] = 12; break; case 'r': s[j++] = 13; break; case '"': case '\\': s[j++] = es[i+1]; break; case '0' ... '3': switch (es[i+2]) { case '0' ... '7': switch (es[i+3]) { case '0' ... '7': s[j++] = ((es[i+1] & 3) << 6) | ((es[i+2] & 7) << 3) | ((es[i+3] & 7) ); i += 2; break <"esc">; } } /* fall through */ default: fatal("unrecognized \\ sequence: %.*s >> %s",i,es,es+i); break; } i += 2; } else if (es[i]) { s[j++] = es[i++]; } else { break; } } s[j] = '\0'; return(s); } static void check_diffs(void) { FIL *f; HUNK *h; HUNKLINE *l; HUNK *ph; int nc; int nf; int nt; for (f=file_h;f;f=f->flink) { for (h=f->diff_h;h;h=h->fflink) if (h->f != f) abort(); } for (f=file_h;f;f=f->flink) { ph = 0; for (h=f->diff_h;h;h=h->fflink) { if ( ph && ( (h->fl < ph->fl+ph->fn) || (h->tl < ph->tl+ph->tn) ) ) { fatal("hunks don't progress monotonically through file: %s, %s",f->escname,h->hdrline); } nc = 0; nf = 0; nt = 0; for (l=h->line_h;l;l=l->flink) { if (l->h != h) abort(); switch (l->type) { default: abort(); break; case HLT_COMMON: nc ++; break; case HLT_FROM: nf ++; break; case HLT_TO: nt ++; break; } } if ( (h->fn != nc+nf) || (h->tn != nc+nt) ) { fatal("hunk should be +%d,%d -%d,%d: %s",h->fl,nc+nf,h->tl,nc+nt,h->hdrline); } } } } static void crack_diff(MEMSTREAM *m) { char *fn; char *efn; int fnspace; int i; int afterfn; FIL *curfil; HUNK *curhunk; HUNKLINE *hl; int lwh; char *ep; unsigned int ef; void line(const char *lb, int ll) { if (! strncmp(lb,"diff --git ",11)) { if (ll & 1) fatal("bad diff header (odd length): %s",lb); i = (ll - 11) / 2; if (i < 3) fatal("bad diff header (too short): %s",lb); if (lb[11+i] != ' ') fatal("bad diff header (no middle space): %s",lb); if (lb[11] == '"') { if ( strncmp(lb+11,"\"a/",3) || (lb[11+i-1] != '"') || strncmp(lb+11+i+1,"\"b/",3) || (lb[11+i+1+i-1] != '"') ) { fatal("bad diff header (\"a/...\" or \"b/...\" wrong): %s",lb); } if (bcmp(lb+11+3,lb+11+i+1+3,i-4)) { fatal("bad diff header (filenames differ): %s",lb); } efn = malloc(i-4+1); bcopy(lb+11+3,efn,i-4); fn = unescape(efn,i-4); ef = FF_ESCNAME; } else { if (strncmp(lb+11,"a/",2) || strncmp(lb+11+i+1,"b/",2)) { fatal("bad diff header (a/ or b/ wrong): %s",lb); } if (bcmp(lb+11+2,lb+11+i+1+2,i-2)) { fatal("bad diff header (filenames differ): %s",lb); } fn = malloc(i-2+1); bcopy(lb+11+2,fn,i-2); fn[i-2] = '\0'; efn = strdup(fn); ef = 0; } fnspace = !!index(fn,' '); afterfn = 1; curfil = malloc(sizeof(FIL)); curfil->flink = 0; curfil->blink = file_t; file_t = curfil; if (curfil->blink) curfil->blink->flink = curfil; else file_h = curfil; curfil->name = fn; curfil->escname = efn; curfil->diff_h = 0; curfil->diff_t = 0; curfil->flags = ef; curhunk = 0; } else if (! strncmp(lb,"new file mode ",14)) { fatal("new file present - handle manually"); } else if (! strncmp(lb,"deleted file mode ",18)) { fatal("deleted file present - handle manually"); } else if (! strncmp(lb,"index ",6)) { /* ignore */ } else if ( (afterfn == 1) && (!strncmp(lb,"--- ",4) || !strncmp(lb,"+++ ",4)) ) { char ab = (lb[0] == '-') ? 'a' : 'b'; if (! fn) fatal("bad diff header (before diff --git line): %s",lb); if (fnspace) { if (lb[ll-1] != '\t') { fatal("bad diff header (missing trailing tab): %s",lb); } ll --; } if (curfil->flags & FF_ESCNAME) { if (ll != 4+3+strlen(efn)+1) { fatal("bad diff header (length wrong): %s",lb); } if ((lb[4] != '"') || (lb[ll-1] != '"')) { fatal("bad diff header (missing \"): %s",lb); } if ((lb[5] != ab) || (lb[6] != '/')) { fatal("bad diff header (missing %c/): %s",ab,lb); } if (bcmp(lb+4+3,efn,strlen(efn))) { fatal("bad diff header (filename wrong): %s",lb); } } else { if (ll != 4+2+strlen(fn)) { fatal("bad diff header (length wrong): %s",lb); } if ((lb[4] != ab) || (lb[5] != '/')) { fatal("bad diff header (missing %c/): %s",ab,lb); } if (bcmp(lb+4+2,fn,strlen(fn))) { fatal("bad diff header (filename wrong): %s",lb); } } if (lb[0] == '+') afterfn = 2; } else { HLT lt; if (afterfn != 2) fatal("bad diff line (header disorder): %s",lb); switch (lb[0]) { default: /* XXX do we want to do anything with \ No newline at end of file lines? */ fatal("bad diff line (unrecognized): %s",lb); break; case '@': if (strncmp(lb,"@@ -",4)) fatal("bad diff line: @ but not @@ -: %s",lb); curhunk = malloc(sizeof(HUNK)); curhunk->aflink = 0; curhunk->ablink = hunk_t; hunk_t = curhunk; if (curhunk->ablink) curhunk->ablink->aflink = curhunk; else hunk_h = curhunk; curhunk->fflink = 0; curhunk->fblink = curfil->diff_t; curfil->diff_t = curhunk; if (curhunk->fblink) curhunk->fblink->fflink = curhunk; else curfil->diff_h = curhunk; curhunk->f = curfil; curhunk->flags = 0; curhunk->hdrline = strdup(lb); curhunk->fl = strtol(lb+4,&ep,0); switch (*ep) { case ',': curhunk->fn = strtol(ep+1,&ep,0); if (*ep != ' ') fatal("bad diff line: wrong char after - line count: %s",lb); break; case ' ': curhunk->fn = 1; break; default: fatal("bad diff line: wrong char after - line number: %s",lb); break; } if (ep[1] != '+') fatal("bad diff line: + sign isn't: %s",lb); curhunk->tl = strtol(ep+2,&ep,0); switch (*ep) { case ',': curhunk->tn = strtol(ep+1,&ep,0); if (*ep != ' ') fatal("bad diff line: wrong char after + line count: %s",lb); break; case ' ': curhunk->tn = 1; break; default: fatal("bad diff line: wrong char after + line number: %s",lb); break; } if (strncmp(ep," @@",3)) fatal("bad diff line: missing trailing @@: %s",lb); curhunk->line_h = 0; curhunk->line_t = 0; lwh = 0; break; case ' ': lt = HLT_COMMON; if (0) { case '-': lt = HLT_FROM; } if (0) { case '+': lt = HLT_TO; } if (! curhunk) fatal("bad diff line: before hunk beginning: %s",lb); hl = malloc(sizeof(HUNKLINE)); hl->flink = 0; hl->blink = curhunk->line_t; curhunk->line_t = hl; if (hl->blink) hl->blink->flink = hl; else curhunk->line_h = hl; hl->h = curhunk; hl->l = lwh++; hl->type = lt; hl->text = strdup(lb+1); break; } } } fn = 0; efn = 0; memstream_read_lines(m,&line); check_diffs(); } void git_initial_diff(char **av) { PROC *p; MEMSTREAM *om; MEMSTREAM *em; p = proc_start( PROC_NULLIN, PROC_OUT, &om, PROC_ERR, &em, PROC_N, 4, "git", "diff", "--cached", "--name-only", PROC_VEC, av+1, PROC_END ); proc_await(p); if (memstream_size(em) > 0) { fprintf(stderr,"git diff failed:\n"); memstream_dump_stdio(em,stderr); exit(1); } memstream_free(em); if (memstream_size(om) != 0) { printf("You have staged but uncommitted changes!\n"); exit(0); } memstream_free(om); p = proc_start( PROC_NULLIN, PROC_OUT, &om, PROC_ERR, &em, PROC_N, 2, "git", "diff", PROC_VEC, av+1, PROC_END ); proc_await(p); if (memstream_size(em) > 0) { fprintf(stderr,"git diff failed:\n"); memstream_dump_stdio(em,stderr); exit(1); } memstream_free(em); if (memstream_size(om) == 0) { printf("No diffs!\n"); exit(0); } file_h = 0; file_t = 0; hunk_h = 0; hunk_t = 0; crack_diff(om); memstream_free(om); } void git_work_dir(void) { PROC *p; MEMSTREAM *om; MEMSTREAM *em; int n; int i; char *gitdir; p = proc_start( PROC_NULLIN, PROC_OUT, &om, PROC_ERR, &em, PROC_N, 3, "git", "rev-parse", "--git-dir", PROC_END ); proc_await(p); if (memstream_size(em) > 0) { fprintf(stderr,"%s: git rev-parse --git-dir failed:\n",__progname); memstream_dump_stdio(em,stderr); exit(1); } memstream_free(em); n = memstream_size(om); if (n < 1) fatal("git rev-parse --git-dir produced no output!"); gitdir = malloc(n); memstream_rewind_r(om); for (i=0;i 4) && !strcmp(gitdir+n-5,"/.git")) { gitdir[n-5] = '\0'; workdir = strdup(gitdir); } else { fatal("strange git rev-parse --git-dir output!"); } free(gitdir); } void git_user_info(void) { void foo(const char *tag, char **varp) { PROC *p; MEMSTREAM *om; MEMSTREAM *em; int n; int i; char *v; p = proc_start( PROC_NULLIN, PROC_OUT, &om, PROC_ERR, &em, PROC_N, 5, "git", "config", "-z", "--get", tag, PROC_END ); proc_await(p); if (memstream_size(em) > 0) { fprintf(stderr,"%s: git config -z --get %s failed:\n",__progname,tag); memstream_dump_stdio(em,stderr); exit(1); } memstream_free(em); n = memstream_size(om); if (n < 1) fatal("git config -z --get %s produced no output!",tag); v = malloc(n); memstream_rewind_r(om); for (i=0;i ll) fatal("bad git checkout-index --temp output line (too short): %s",lb); if (lb[ll-1] != '"') fatal("bad git checkout-index --temp output line (no trailing \"): %s",lb); gfn = unescape(tab+2,(lb+(ll-1))-(tab+2)); } else { gfn = strdup(tab+1); } do <"found"> { for (f=file_h;f;f=f->flink) if (! strcmp(f->name,gfn)) break <"found">; fatal("bad git checkout-index --temp output line (unrecognized filename): %s",lb); } while (0); tfn = malloc((tab-lb)+1); bcopy(lb,tfn,tab-lb); tfn[tab-lb] = '\0'; fd = open(tfn,O_RDONLY,0); if (fd < 0) fatal("bad git checkout-index --temp output line: (can't open %s: %s): %s\n",tfn,strerror(errno),lb); fstat(fd,&stb); f->mode = stb.st_mode & 07777; f->contents_len = stb.st_size; if (f->contents_len != stb.st_size) fatal("file too huge: %s",gfn); f->contents = malloc(f->contents_len); n = read(fd,f->contents,f->contents_len); if (n != f->contents_len) { if (n < 0) fatal("%s (%s) read error: %s",tfn,gfn,strerror(errno)); fatal("%s (%s) read wanted %d, read %d",tfn,gfn,f->contents_len,n); } if ((n > 0) && (f->contents[n-1] != '\n')) fatal("%s (%s) doesn't end with a newline",tfn,gfn); unlink(tfn); close(fd); free(gfn); free(tfn); } na = 0; for (f=file_h;f;f=f->flink) na ++; av = malloc((na+1)*sizeof(const char *)); i = 0; for (f=file_h;f;f=f->flink) av[i++] = f->name; if (i != na) abort(); av[i] = 0; p = proc_start( PROC_NULLIN, PROC_OUT, &om, PROC_ERR, &em, PROC_N, 3, "git", "checkout-index", "--temp", PROC_VEC, av, PROC_END ); proc_await(p); if (memstream_size(em) > 0) { fprintf(stderr,"%s: git checkout-index --temp failed:\n",__progname); memstream_dump_stdio(em,stderr); exit(1); } memstream_free(em); memstream_read_lines(om,&grabfile); memstream_free(om); } static void do_git_reset(const char *what) { PROC *p; int status; MEMSTREAM *om; MEMSTREAM *em; p = proc_start( PROC_NULLIN, PROC_OUT, &om, PROC_ERR, &em, PROC_N, 5, "git", "reset", "--mixed", "-q", what, PROC_END ); status = proc_await(p); if (memstream_size(em) > 0) { fprintf(stderr,"git reset %s failed:\n",what); memstream_dump_stdio(em,stderr); exit(1); } memstream_free(em); if (memstream_size(om) != 0) { fprintf(stderr,"git reset %s produced unexpected output:\n",what); memstream_dump_stdio(om,stderr); exit(1); } memstream_free(om); if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) { fatal("git reset %s: exit %d",what,WEXITSTATUS(status)); } } else if (WIFSIGNALED(status)) { fatal("git reset %s: died on signal %d (%s)%s", what, WTERMSIG(status), strsignal(WTERMSIG(status)), WCOREDUMP(status) ? " (core dumped)" : "" ); } else { fatal("git reset %s: impossible wait status %08x",what,status); } } int git_commit(void) { PROC *p; int status; do_git_reset("HEAD"); p = proc_start(PROC_N,4,"git","commit","-o","--amend",PROC_END); status = proc_await(p); if (WIFEXITED(status) && (WEXITSTATUS(status) == 0)) { return(1); } else { do_git_reset("HEAD^"); return(0); } } GITFI *git_fast_import_start(void) { GITFI *gfi; struct msghdr mh; struct iovec iov[5]; gfi = malloc(sizeof(GITFI)); gfi->p = proc_start( PROC_INFD, &gfi->ifd, PROC_N, 4, "git", "fast-import", "--date-format=now", "--quiet", PROC_END ); iov[0].iov_base = dequal("commit HEAD\ncommitter "); iov[0].iov_len = 22; iov[1].iov_base = gitname; iov[1].iov_len = strlen(gitname); iov[2].iov_base = dequal(" <"); iov[2].iov_len = 2; iov[3].iov_base = gitemail; iov[3].iov_len = strlen(gitemail); iov[4].iov_base = dequal("> now\ndata 1\n\n\nfrom HEAD^0\n"); iov[4].iov_len = 27; mh.msg_name = 0; mh.msg_namelen = 0; mh.msg_iov = &iov[0]; mh.msg_iovlen = 5; mh.msg_control = 0; mh.msg_controllen = 0; mh.msg_flags = 0; if (sendmsg(gfi->ifd,&mh,MSG_NOSIGNAL) < 0) { fatal("error writing initial data to git fast-import: %s",strerror(errno)); } return(gfi); } void git_fast_import_file(GITFI *gfi, const char *fn, int m, MEMSTREAM *data) { char *s; int l; void sendit(const void *blk, int len) { if (send(gfi->ifd,blk,len,MSG_NOSIGNAL) < 0) { fatal("error writing %s to git fast-import: %s",fn,strerror(errno)); } } l = asprintf(&s,"M %s inline %s\ndata %d\n",(m&0111)?"755":"644",fn,memstream_size(data)); sendit(s,l); memstream_map(data,&sendit); sendit("\n",1); free(s); } void git_fast_import_finish(GITFI *gfi) { int status; close(gfi->ifd); status = proc_await(gfi->p); free(gfi); if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) { fatal("git fast-import: exit %d",WEXITSTATUS(status)); } } else if (WIFSIGNALED(status)) { fatal("git fast-import: died on signal %d (%s)%s", WTERMSIG(status), strsignal(WTERMSIG(status)), WCOREDUMP(status) ? " (core dumped)" : "" ); } else { fatal("git fast-import: impossible wait status %08x",status); } }