#include #include #include #include #include #include #include "fatal.h" #include "subproc.h" #include "memstream.h" #include "datastruct.h" #include "git-intf.h" /* * 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.) */ static char *unescape(char *es) { char *s; int i; int j; s = malloc(strlen(es)+1); j = 0; i = 0; while (1) { 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; int nc; int nf; int nt; for (f=files;f;f=f->link) { for (h=f->diffs;h;h=h->flink) if (h->f != f) abort(); } for (h=hunks;h;h=h->alink) { nc = 0; nf = 0; nt = 0; for (l=h->lines;l;l=l->link) { 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) { unsigned char *lb; int la; int ll; int c; char *fn; char *efn; int fnspace; int i; int afterfn; FIL *curfil; HUNK *curhunk; HUNKLINE *hl; int lwh; char *ep; FIL **filet; HUNK **hunkt; HUNK **fhunkt; HUNKLINE **linet; void l_append(int ch) { if (ll >= la) lb = realloc(lb,la=ll+8); lb[ll++] = ch; } memstream_rewind_r(m); lb = 0; la = 0; fn = 0; efn = 0; filet = &files; hunkt = &hunks; while <"lines"> (1) { ll = 0; while (1) { c = memstream_readc(m); if (c < 0) { if (ll == 0) break <"lines">; break; } if (c == '\n') break; l_append(c); } l_append('\0'); 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); efn[i-4] = '\0'; fn = unescape(efn); } 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); } efn = 0; fn = malloc(i-2+1); bcopy(lb+11+2,fn,i-2); fn[i-2] = '\0'; } fnspace = !!index(fn,' '); printf("diff header line %s\n",lb); if (efn) printf("escaped filename %s\n",efn); printf("filename %s\n",fn); afterfn = 1; curfil = malloc(sizeof(FIL)); curfil->link = 0; *filet = curfil; filet = &curfil->link; curfil->name = fn; curfil->escname = efn; curfil->diffs = 0; fhunkt = &curfil->diffs; 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 (efn) { 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->alink = 0; *hunkt = curhunk; hunkt = &curhunk->alink; curhunk->flink = 0; *fhunkt = curhunk; fhunkt = &curhunk->flink; curhunk->f = curfil; 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->lines = 0; linet = &curhunk->lines; 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->link = 0; *linet = hl; linet = &hl->link; hl->h = curhunk; hl->l = lwh++; hl->type = lt; hl->text = strdup(lb+1); break; } } } check_diffs(); } void git_initial_diff(char **av) { int ofd; int efd; MEMSTREAM *om; MEMSTREAM *em; proc_start(0,&ofd,&efd,PROC_N,2,"git","diff",PROC_VEC,av+1,PROC_END); proc_gather_outputs(ofd,&om,efd,&em); if (memstream_size(em) > 0) { void chunk(const void *data, int len) { fwrite(data,1,len,stdout); } printf("git diff failed:\n"); memstream_dump(em,&chunk); exit(1); } memstream_free(em); if (memstream_size(om) == 0) { printf("No diffs!\n"); exit(0); } crack_diff(om); memstream_free(om); }