#include #include #include #include #include #include #include #include "fatal.h" #include "git-intf.h" #include "datastruct.h" #include "ui.h" typedef struct fhl FHL; struct fhl { FIL *f; HUNK *h; HUNKLINE *l; } ; static unsigned int tempserial = 0; static int cursed = 0; static FHL top; static FHL cur; static int curline; void ui_abort(void) { if (cursed) { move(LINES-1,0); refresh(); endwin(); printf("\n"); cursed = 0; } } static void ui_save(void) { ui_abort(); /* conceptually different, same implementation */ } static void ui_setup(void) { if (! cursed) { initscr(); noecho(); cbreak(); cursed = 1; } } static void ui_restore(void) { ui_setup(); /* conceptually different, same implemenation */ } static void file_line(int y, FIL *f) { mvprintw(y,0,"%c %.*s", (f->flags & FF_ALLAPPLIED) ? 'A' : ' ', COLS-3, f->escname ); clrtoeol(); } static void hunk_line(int y, HUNK *h) { mvprintw(y,0,"%c | @@ -%d,%d +%d,%d",(h->flags&HF_SELECTED)?'>':' ',h->fl,h->fn,h->tl,h->tn); clrtoeol(); } static void line_line(int y, HUNKLINE *l) { int c; unsigned const char *t; unsigned char q[8]; int qx; int tch; int i; mvprintw(y,0,"%c | | ",(l->h->flags&HF_SELECTED)?'>':' '); switch (l->type) { default: abort(); break; case HLT_COMMON: addch(' '); break; case HLT_FROM: addch('-'); break; case HLT_TO: addch('+'); break; } addch(' '); c = 0; t = (const void *) l->text; qx = 0; while ((c < COLS-8-2) && *t) { tch = -1; if (qx > 0) { tch = q[--qx]; } else if (*t == '\t') { i = 8 - (c & 7); memset(&q[0],' ',i); qx = i; } else if (*t < 32) { q[0] = *t ^ 64; q[1] = '^'; qx = 2; } else if (*t == 127) { q[0] = '?'; q[1] = '^'; qx = 2; } else if ((*t >= 127) && (*t < 160)) { q[0] = '0' + (*t & 7); q[1] = '0' + ((*t >> 3) & 7); q[2] = '0' + ((*t >> 6) & 7); q[3] = '\\'; qx = 4; } else { tch = *t++; } if (tch < 0) { t ++; } else { addch(tch); c ++; } } clrtoeol(); } static void fhl_down(FHL *c) { void down_f(void) { do c->f = c->f->flink; while (c->f && (c->f->flags & FF_ALLAPPLIED)); c->h = 0; } void down_h(void) { do c->h = c->h->fflink; while (c->h && (c->h->flags & HF_APPLIED)); if (! c->h) down_f(); c->l = 0; } void down_l(void) { c->l = c->l->flink; if (! c->l) down_h(); } if (! c->f) abort(); if (c->f->flags & FF_EXPANDED) { if (c->h) { if (c->h->flags & HF_EXPANDED) { if (c->l) { down_l(); } else { c->l = c->h->line_h; } } else { down_h(); } } else { c->h = c->f->diff_h; } } else { down_f(); } } static void fhl_up(FHL *c) { void up_f(void) { do c->f = c->f->blink; while (c->f && (c->f->flags & FF_ALLAPPLIED)); if (c->f && (c->f->flags & FF_EXPANDED)) { c->h = c->f->diff_t; while (c->h->flags & HF_APPLIED) c->h = c->h->fblink; if (c->h->flags & HF_EXPANDED) c->l = c->h->line_t; } } void up_h(void) { do c->h = c->h->fblink; while (c->h && (c->h->flags & HF_APPLIED)); if (c->h && (c->h->flags & HF_EXPANDED)) c->l = c->h->line_t; } void up_l(void) { c->l = c->l->blink; } if (! c->f) abort(); if (c->f->flags & FF_EXPANDED) { if (c->h) { if (c->h->flags & HF_EXPANDED) { if (c->l) { up_l(); } else { up_h(); } } else { up_h(); } } else { up_f(); } } else { up_f(); } } static void fhl_down_level(FHL *c) { if (! c->f) abort(); if ((c->f->flags & FF_EXPANDED) && c->h) { c->h = c->h->fflink; c->l = 0; if (! c->h) c->f = c->f->flink; } else { c->f = c->f->flink; c->h = 0; } } static void fhl_up_level(FHL *c) { if (! c->f) abort(); if ((c->f->flags & FF_EXPANDED) && c->h) { if ((c->h->flags & HF_EXPANDED) && c->l) { c->l = 0; } else { c->h = c->h->fblink; } } else { c->f = c->f->blink; c->h = 0; } } static void redraw_list(void) { int y; FHL c; y = 0; c = top; while (c.f) { if (c.f->flags & FF_EXPANDED) { if (c.h) { if (c.h->flags & HF_EXPANDED) { if (c.l) { if ((c.f == cur.f) && (c.h == cur.h) && (c.l == cur.l)) curline = y; line_line(y,c.l); } else { if ((c.f == cur.f) && (c.h == cur.h) && !cur.l) curline = y; hunk_line(y,c.h); } } else { if ((c.f == cur.f) && (c.h == cur.h)) curline = y; hunk_line(y,c.h); } } else { if ((c.f == cur.f) && !cur.h) curline = y; file_line(y,c.f); } } else { if (c.f == cur.f) curline = y; file_line(y,c.f); } y ++; if (y >= LINES) break; fhl_down(&c); } clrtobot(); } static void recenter(void) { FHL t; FHL t2; int i; t = cur; for (i=(LINES-1)/2;i>0;i--) { t2 = t; fhl_up(&t2); if (! t2.f) break; t = t2; } top = t; } static char *new_temp_name(const char *dname, const char *s) { char *name; asprintf(&name,"%s/.~%s~%x",dname,s,tempserial++); return(name); } static void open_temp_in_dir(const char *dname, const char *s, unsigned int mode, int *fdp, char **namep) { char *name; int fd; while (1) { name = new_temp_name(dname,s); fd = open(name,O_RDWR|O_CREAT|O_EXCL,mode); if (fd >= 0) { *fdp = fd; *namep = name; return; } if (errno == EEXIST) { free(name); continue; } fatal("can't open temp file %s: %s",name,strerror(errno)); } } static MEMSTREAM *gen_applied_file(FIL *f) { HUNK *h; HUNKLINE *l; int fln; int fx; unsigned char *nl; int ll; MEMSTREAM *ms; ms = memstream_open_w(); h = f->diff_h; if (f->contents_len == 0) { if (h->fflink) fatal("multiple patches to an empty file %s",f->escname); if (h->fn != 0) fatal("patch deletes from an empty file %s",f->escname); if (h->flags & (HF_APPLIED|HF_SELECTED)) { for (l=h->line_h;l;l=l->flink) { if (l->type != HLT_TO) fatal("non-+ line in patch to empty file %s",f->escname); memstream_append(ms,l->text,strlen(l->text)); memstream_append(ms,"\n",1); } } } else { fln = 1; fx = 0; while (h && ((h->flags & (HF_APPLIED|HF_SELECTED)) == 0)) h = h->fflink; l = h ? h->line_h : 0; while (1) { if (h && !l) { do h = h->fflink; while (h && ((h->flags & (HF_APPLIED|HF_SELECTED)) == 0)); l = h ? h->line_h : 0; } else if (!h || (fln < h->fl)) { if (fx >= f->contents_len) break; nl = memchr(f->contents+fx,'\n',f->contents_len-fx); if (! nl) abort(); nl ++; memstream_append(ms,f->contents+fx,nl-(f->contents+fx)); fln ++; fx = nl - f->contents; } else { switch (l->type) { default: abort(); break; case HLT_COMMON: case HLT_FROM: if (fx >= f->contents_len) { fatal("diff overflows file (%s, hunk %s)",f->escname,h->hdrline); } nl = memchr(f->contents+fx,'\n',f->contents_len-fx); if (! nl) abort(); ll = nl - (f->contents + fx); if ( (ll != strlen(l->text)) || bcmp(l->text,f->contents+fx,ll) ) { fatal("diff line doesn't match file line (%s, line %d, hunk %s)\n", f->escname, fln, h->hdrline); } nl ++; if (l->type == HLT_COMMON) memstream_append(ms,f->contents+fx,nl-(f->contents+fx)); fln ++; fx = nl - f->contents; break; case HLT_TO: memstream_append(ms,l->text,strlen(l->text)); memstream_append(ms,"\n",1); break; } l = l->flink; } } } return(ms); } static FILE *fopen_info_file(char **namep) { int fd; char *name; open_temp_in_dir(".","info",0644,&fd,&name); *namep = name; return(fdopen(fd,"w")); } static int fhl_equal(FHL a, FHL b) { if (a.f != b.f) return(0); if (!a.f || !(a.f->flags & FF_EXPANDED)) return(1); if (a.h != b.h) return(0); if (!a.h || !(a.h->flags & HF_EXPANDED)) return(1); if (a.l != b.l) return(0); return(1); } static void cmd_down(void) { FHL ct; ct = cur; fhl_down(&ct); if (ct.f) cur = ct; } static void cmd_up(void) { FHL ct; ct = cur; fhl_up(&ct); if (ct.f) cur = ct; } static void cmd_down_screen(void) { FHL t; int i; int match; t = top; match = fhl_equal(t,cur); for (i=(LINES*4)/5;i>0;i--) { fhl_down(&t); if (! t.f) break; top = t; if (match) cur = t; else if (fhl_equal(t,cur)) match = 1; } } static void cmd_up_screen(void) { FHL t; FHL last; int i; t = top; for (i=(LINES*4)/5;i>0;i--) { fhl_up(&t); if (! t.f) break; top = t; } t = top; for (i=LINES;t.f&&(i>0);i--) { if (fhl_equal(t,cur)) return; last = t; fhl_down(&t); } cur = last; } static void cmd_down_level(void) { FHL ct; ct = cur; fhl_down_level(&ct); if (ct.f) cur = ct; } static void cmd_up_level(void) { FHL ct; ct = cur; fhl_up_level(&ct); if (ct.f) cur = ct; } static void cmd_close(void) { if (cur.f->flags & FF_EXPANDED) { if (cur.h) { if (cur.h->flags & HF_EXPANDED) { if (cur.l) { cur.l = 0; } else { cur.h->flags &= ~HF_EXPANDED; } } else { cur.h = 0; } } else { cur.f->flags &= ~FF_EXPANDED; } } } static void cmd_open(void) { if (cur.f->flags & FF_EXPANDED) { if (cur.h) { if (cur.h->flags & HF_EXPANDED) { if (! cur.l) cur.l = cur.h->line_h; } else { cur.h->flags |= HF_EXPANDED; cur.l = 0; } } else { cur.h = cur.f->diff_h; cur.l = 0; } } else { cur.f->flags |= FF_EXPANDED; cur.h = 0; } } static void cmd_seltoggle(void) { if ((cur.f->flags & FF_EXPANDED) && cur.h) { cur.h->flags ^= HF_SELECTED; } else { beep(); } } static void cmd_commit(void) { FIL *f; HUNK *h; unsigned int fl; FHL t; FILE *lf; char *infofn; GITFI *gfi; MEMSTREAM *ms; ui_save(); lf = fopen_info_file(&infofn); setbuf(lf,0); fprintf(lf,"starting git fast-import\n"); gfi = git_fast_import_start(); for (f=file_h;f;f=f->flink) { fprintf(lf,"generating patched %s...",f->escname); ms = gen_applied_file(f); fprintf(lf,"feeding to git fast-import..."); git_fast_import_file(gfi,f->escname,f->mode,ms); fprintf(lf,"freeing...\n"); memstream_free(ms); fprintf(lf,"done\n"); } fprintf(lf,"finishing git fast-import\n"); git_fast_import_finish(gfi); fprintf(lf,"running git commit..."); if (git_commit()) { fprintf(lf,"success\n"); for (h=hunk_h;h;h=h->aflink) { if (h->flags & HF_SELECTED) h->flags = (h->flags & ~HF_SELECTED) | HF_APPLIED; } for (f=file_h;f;f=f->flink) { fl = HF_APPLIED; for (h=f->diff_h;h;h=h->fflink) fl &= h->flags; if (fl & HF_APPLIED) f->flags = (f->flags & ~FF_EXPANDED) | FF_ALLAPPLIED; } if ( (cur.f->flags & FF_ALLAPPLIED) || ( (cur.f->flags & FF_EXPANDED) && cur.h && (cur.h->flags & HF_APPLIED) ) ) { if ( (cur.f->flags & FF_EXPANDED) && cur.h && (cur.h->flags & HF_EXPANDED) ) cur.l = cur.h->line_t; t = cur; fhl_down(&t); if (! t.f) { t = cur; fhl_up(&t); if (! t.f) { unlink(infofn); printf("All hunks applied!\n"); exit(0); } } cur = t; } if ( (top.f->flags & FF_ALLAPPLIED) || ( (top.f->flags & FF_EXPANDED) && top.h && (top.h->flags & HF_APPLIED) ) ) { recenter(); } } else { fprintf(lf,"failed\n"); } fclose(lf); unlink(infofn); ui_restore(); } void ui_main_loop(void) { int c; ui_setup(); top.f = file_h; cur = top; while (1) { curline = -1; redraw_list(); if (curline < 0) { recenter(); continue; } else { move(curline,0); } refresh(); c = getch(); switch (c) { case 'j': cmd_down(); break; case 'k': cmd_up(); break; case 'J': cmd_down_level(); break; case 'K': cmd_up_level(); break; case 'h': cmd_close(); break; case 'l': cmd_open(); break; case 'a': cmd_seltoggle(); break; case 'C': cmd_commit(); break; case ' ': case 'f': cmd_down_screen(); break; case 'b': cmd_up_screen(); break; } } }