commit 84451b3ef755f3226d0d79af367632e5f3a830e7 from: Stefan Sperling date: Tue Jul 10 11:17:00 2018 UTC implement incremental blame display for tog commit - 63581804340e880bf611c6a4a59eda26c503799f commit + 84451b3ef755f3226d0d79af367632e5f3a830e7 blob - b53ca469a18871cc2f6af334dab25028599c6488 blob + c787aadf05e2afab61bd34976f7349912252e6da --- include/got_blame.h +++ include/got_blame.h @@ -14,5 +14,22 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +/* + * Write an annotated version of a file at a given in-repository path, + * as found in the commit specified by ID, to the specified output file. + */ const struct got_error *got_blame(const char *, struct got_object_id *, struct got_repository *, FILE *); + +/* + * Like got_blame() but instead of generating an output file invoke + * a callback whenever an annotation has been computed for a line. + * + * The callback receives the provided void * argument, the total number + * of lines of the annotated file, a line number, and the ID of the commit + * which last changed this line. + */ +const struct got_error *got_blame_incremental(const char *, + struct got_object_id *, struct got_repository *, + const struct got_error *(*cb)(void *, int, int, struct got_object_id *), + void *); blob - 52bd46fe2b3ac6189e515513a49f08371cabec86 blob + 16aa7b925adeefc4dbd6703da310ba7a98900ff4 --- include/got_object.h +++ include/got_object.h @@ -190,11 +190,13 @@ const struct got_error *got_object_blob_read_block(siz /* * Read the entire content of a blob and write it to the specified file. - * Flush and rewind the file as well, and indicate the amount of bytes - * written in the size_t output argument. + * Flush and rewind the file as well. Indicate the amount of bytes + * written in the first size_t output argument, and the number of lines + * in the file in the second size_t output argument (NULL can be passed + * for either output argument). */ -const struct got_error *got_object_blob_dump_to_file(size_t *, FILE *, - struct got_blob_object *); +const struct got_error *got_object_blob_dump_to_file(size_t *, size_t *, + FILE *, struct got_blob_object *); const struct got_error * got_object_open_as_commit(struct got_commit_object **, blob - 7f25ad2d36b9f21d0db48ab55c858472cf281a7e blob + 1a2d03cda24f7afe7ef3cb27b7b9683cbf87fe65 --- lib/blame.c +++ lib/blame.c @@ -47,58 +47,32 @@ struct got_blame { }; static const struct got_error * -dump_blob_and_count_lines(size_t *nlines, FILE *outfile, - struct got_blob_object *blob) +annotate_line(struct got_blame *blame, int lineno, struct got_object_id *id, + const struct got_error *(*cb)(void *, int, int, struct got_object_id *), + void *arg) { const struct got_error *err = NULL; - size_t len, hdrlen; - const uint8_t *buf; - int i; - - hdrlen = got_object_blob_get_hdrlen(blob); - *nlines = 0; - do { - err = got_object_blob_read_block(&len, blob); - if (err) - return err; - if (len == 0) - break; - buf = got_object_blob_get_read_buf(blob); - for (i = 0; i < len; i++) { - if (buf[i] == '\n') - (*nlines)++; - } - /* Skip blob object header first time around. */ - fwrite(buf + hdrlen, len - hdrlen, 1, outfile); - hdrlen = 0; - } while (len != 0); - - - fflush(outfile); - rewind(outfile); - - return NULL; -} - -static void -annotate_line(struct got_blame *blame, int lineno, struct got_object_id *id) -{ struct got_blame_line *line; if (lineno < 1 || lineno > blame->nlines) - return; + return got_error(GOT_ERR_RANGE); line = &blame->lines[lineno - 1]; if (line->annotated) - return; + return NULL; memcpy(&line->id, id, sizeof(line->id)); line->annotated = 1; + if (cb) + err = cb(arg, blame->nlines, lineno, id); + return err; } static const struct got_error * blame_commit(struct got_blame *blame, struct got_object_id *id, - struct got_object_id *pid, const char *path, struct got_repository *repo) + struct got_object_id *pid, const char *path, struct got_repository *repo, + const struct got_error *(*cb)(void *, int, int, struct got_object_id *), + void *arg) { const struct got_error *err = NULL; struct got_object *obj = NULL, *pobj = NULL; @@ -152,8 +126,11 @@ blame_commit(struct got_blame *blame, struct got_objec int a = change->cv.a; int b = change->cv.b; int lineno; - for (lineno = a; lineno <= b; lineno++) - annotate_line(blame, lineno, id); + for (lineno = a; lineno <= b; lineno++) { + err = annotate_line(blame, lineno, id, cb, arg); + if (err) + goto done; + } } } done: @@ -179,7 +156,9 @@ blame_close(struct got_blame *blame) static const struct got_error * blame_open(struct got_blame **blamep, const char *path, - struct got_object_id *start_commit_id, struct got_repository *repo) + struct got_object_id *start_commit_id, struct got_repository *repo, + const struct got_error *(*cb)(void *, int, int, struct got_object_id *), + void *arg) { const struct got_error *err = NULL; struct got_object *obj = NULL; @@ -212,7 +191,8 @@ blame_open(struct got_blame **blamep, const char *path err = got_error_from_errno(); goto done; } - err = dump_blob_and_count_lines(&blame->nlines, blame->f, blob); + err = got_object_blob_dump_to_file(NULL, &blame->nlines, blame->f, + blob); if (err) goto done; @@ -238,7 +218,7 @@ blame_open(struct got_blame **blamep, const char *path if (pid == NULL) break; - err = blame_commit(blame, id, pid->id, path, repo); + err = blame_commit(blame, id, pid->id, path, repo, cb, arg); if (err) { if (err->code == GOT_ERR_ITER_COMPLETED) err = NULL; @@ -254,12 +234,15 @@ blame_open(struct got_blame **blamep, const char *path got_object_commit_close(commit); err = got_object_open_as_commit(&commit, repo, id); if (err) - break; + goto done; } /* Annotate remaining non-annotated lines with last commit. */ - for (lineno = 1; lineno <= blame->nlines; lineno++) - annotate_line(blame, lineno, id); + for (lineno = 1; lineno <= blame->nlines; lineno++) { + err = annotate_line(blame, lineno, id, cb, arg); + if (err) + goto done; + } done: free(id); @@ -313,7 +296,7 @@ got_blame(const char *path, struct got_object_id *star if (asprintf(&abspath, "%s%s", path[0] == '/' ? "" : "/", path) == -1) return got_error_from_errno(); - err = blame_open(&blame, abspath, start_commit_id, repo); + err = blame_open(&blame, abspath, start_commit_id, repo, NULL, NULL); if (err) { free(abspath); return err; @@ -346,3 +329,22 @@ got_blame(const char *path, struct got_object_id *star free(abspath); return err; } + +const struct got_error * +got_blame_incremental(const char *path, struct got_object_id *commit_id, + struct got_repository *repo, + const struct got_error *(*cb)(void *, int, int, struct got_object_id *), + void *arg) +{ + const struct got_error *err = NULL; + struct got_blame *blame; + char *abspath; + + if (asprintf(&abspath, "%s%s", path[0] == '/' ? "" : "/", path) == -1) + return got_error_from_errno(); + + err = blame_open(&blame, abspath, commit_id, repo, cb, arg); + free(abspath); + blame_close(blame); + return err; +} blob - abdee829510822e65a88b390d5b9f2bddb49d525 blob + 8fc337d544e52f440acacbbc75a073499ead13c7 --- lib/diff.c +++ lib/diff.c @@ -67,7 +67,7 @@ diff_blobs(struct got_blob_object *blob1, struct got_b size1 = 0; if (blob1) { idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1)); - err = got_object_blob_dump_to_file(&size1, f1, blob1); + err = got_object_blob_dump_to_file(&size1, NULL, f1, blob1); if (err) goto done; } else @@ -76,7 +76,7 @@ diff_blobs(struct got_blob_object *blob1, struct got_b size2 = 0; if (blob2) { idstr2 = got_object_blob_id_str(blob2, hex2, sizeof(hex2)); - err = got_object_blob_dump_to_file(&size2, f2, blob2); + err = got_object_blob_dump_to_file(&size2, NULL, f2, blob2); if (err) goto done; } else blob - 4cf62dd8461ada927c3e2522739feadbe7a4c211 blob + 8bc417f477f5c5bb085172a3adf35471edc7f006 --- lib/object.c +++ lib/object.c @@ -1438,13 +1438,19 @@ got_object_blob_read_block(size_t *outlenp, struct got } const struct got_error * -got_object_blob_dump_to_file(size_t *total_len, FILE *outfile, - struct got_blob_object *blob) +got_object_blob_dump_to_file(size_t *total_len, size_t *nlines, + FILE *outfile, struct got_blob_object *blob) { const struct got_error *err = NULL; size_t len, hdrlen; + const uint8_t *buf; + int i; + + if (total_len) + *total_len = 0; + if (nlines) + *nlines = 0; - *total_len = 0; hdrlen = got_object_blob_get_hdrlen(blob); do { err = got_object_blob_read_block(&len, blob); @@ -1452,10 +1458,17 @@ got_object_blob_dump_to_file(size_t *total_len, FILE * return err; if (len == 0) break; - *total_len += len; + if (total_len) + *total_len += len; + buf = got_object_blob_get_read_buf(blob); + if (nlines) { + for (i = 0; i < len; i++) { + if (buf[i] == '\n') + (*nlines)++; + } + } /* Skip blob object header first time around. */ - fwrite(got_object_blob_get_read_buf(blob) + hdrlen, - len - hdrlen, 1, outfile); + fwrite(buf + hdrlen, len - hdrlen, 1, outfile); hdrlen = 0; } while (len != 0); blob - 76ebd6a1e50dc0e61ce0a0a9ad35306dff853b52 blob + 06bda662dc489c32771945b9cdb976211c884a6e --- tog/Makefile +++ tog/Makefile @@ -7,7 +7,7 @@ SRCS= tog.c blame.c commit_graph.c delta.c diff.c dif sha1.c worktree.c utf8.c inflate.c CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib -LDADD = -lpanel -lncursesw -lutil -lz +LDADD = -lpanel -lncursesw -lutil -lz -lpthread DPADD = ${LIBZ} ${LIBUTIL} .if defined(PROFILE) CC = gcc blob - c66824027f131e9b4a8456718212eb87e469c90c blob + 343f4c6f06df7a59fcff7d6df42e716c0f0b1ea3 --- tog/tog.c +++ tog/tog.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "got_error.h" #include "got_object.h" @@ -1047,25 +1048,174 @@ usage_blame(void) getprogname()); exit(1); } + +struct tog_blame_line { + int annotated; + struct got_object_id *id; +}; static const struct got_error * +draw_blame(WINDOW *window, FILE *f, struct tog_blame_line *lines, int nlines, + int *first_displayed_line, int *last_displayed_line, int *eof, + int max_lines) +{ + const struct got_error *err; + int lineno = 0, nprinted = 0; + char *line; + size_t len; + wchar_t *wline; + int width; + struct tog_blame_line *blame_line; + + rewind(f); + werase(window); + + *eof = 0; + while (nprinted < max_lines) { + line = parse_next_line(f, &len); + if (line == NULL) { + *eof = 1; + break; + } + if (++lineno < *first_displayed_line) { + free(line); + continue; + } + + err = format_line(&wline, &width, line, COLS - 9); + if (err) { + free(line); + return err; + } + + blame_line = &lines[lineno - 1]; + if (blame_line->annotated) { + char *id_str; + err = got_object_id_str(&id_str, blame_line->id); + if (err) { + free(line); + return err; + } + wprintw(window, "%.8s ", id_str); + free(id_str); + } else + waddstr(window, " "); + + waddwstr(window, wline); + if (width < COLS - 9) + waddch(window, '\n'); + if (++nprinted == 1) + *first_displayed_line = lineno; + free(line); + } + *last_displayed_line = lineno; + + update_panels(); + doupdate(); + + return NULL; +} + +struct tog_blame_cb_args { + pthread_mutex_t *mutex; + struct tog_blame_line *lines; /* one per line */ + int nlines; + + FILE *f; + WINDOW *window; + int *first_displayed_line; + int *last_displayed_line; +}; + +static const struct got_error * +blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id) +{ + const struct got_error *err = NULL; + struct tog_blame_cb_args *a = arg; + struct tog_blame_line *line; + int eof; + + if (nlines != a->nlines || lineno < 1 || lineno > a->nlines) + return got_error(GOT_ERR_RANGE); + + if (pthread_mutex_lock(a->mutex) != 0) + return got_error_from_errno(); + + line = &a->lines[lineno - 1]; + line->id = got_object_id_dup(id); + if (line->id == NULL) { + err = got_error_from_errno(); + goto done; + } + line->annotated = 1; + + err = draw_blame(a->window, a->f, a->lines, a->nlines, + a->first_displayed_line, a->last_displayed_line, &eof, LINES); +done: + if (pthread_mutex_unlock(a->mutex) != 0) + return got_error_from_errno(); + return err; +} + +struct tog_blame_thread_args { + const char *path; + struct got_object_id *commit_id; + struct got_repository *repo; + void *blame_cb_args; +}; + +static void * +blame_thread(void *arg) +{ + struct tog_blame_thread_args *a = arg; + return (void *)got_blame_incremental(a->path, a->commit_id, a->repo, + blame_cb, a->blame_cb_args); +} + +static const struct got_error * show_blame_view(const char *path, struct got_object_id *commit_id, struct got_repository *repo) { const struct got_error *err = NULL; - FILE *f; int ch, done = 0, first_displayed_line = 1, last_displayed_line = LINES; int eof, i; + struct got_object *obj = NULL; + struct got_blob_object *blob = NULL; + FILE *f = NULL; + size_t filesize, nlines; + struct tog_blame_line *lines = NULL; + pthread_t thread = NULL; + pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + struct tog_blame_cb_args blame_cb_args; + struct tog_blame_thread_args blame_thread_args; - f = got_opentemp(); - if (f == NULL) - return got_error_from_errno(); + err = got_object_open_by_path(&obj, repo, commit_id, path); + if (err) + goto done; + if (got_object_get_type(obj) != GOT_OBJ_TYPE_BLOB) { + err = got_error(GOT_ERR_OBJ_TYPE); + got_object_close(obj); + goto done; + } - err = got_blame(path, commit_id, repo, f); + err = got_object_blob_open(&blob, repo, obj, 8192); + got_object_close(obj); if (err) goto done; + f = got_opentemp(); + if (f == NULL) { + err = got_error_from_errno(); + goto done; + } + err = got_object_blob_dump_to_file(&filesize, &nlines, f, blob); + if (err) + goto done; - fflush(f); + lines = calloc(nlines, sizeof(*lines)); + if (lines == NULL) { + err = got_error_from_errno(); + goto done; + } if (tog_blame_view.window == NULL) { tog_blame_view.window = newwin(0, 0, 0, 0); @@ -1080,14 +1230,49 @@ show_blame_view(const char *path, struct got_object_id } else show_panel(tog_blame_view.panel); + if (pthread_mutex_init(&mutex, NULL) != 0) { + err = got_error_from_errno(); + goto done; + } + blame_cb_args.lines = lines; + blame_cb_args.nlines = nlines; + blame_cb_args.mutex = &mutex; + blame_cb_args.f = f; + blame_cb_args.window = tog_blame_view.window; + blame_cb_args.first_displayed_line = &first_displayed_line; + blame_cb_args.last_displayed_line = &last_displayed_line; + + blame_thread_args.path = path; + blame_thread_args.commit_id = commit_id; + blame_thread_args.repo = repo; + blame_thread_args.blame_cb_args = &blame_cb_args; + + if (pthread_create(&thread, NULL, blame_thread, + &blame_thread_args) != 0) { + err = got_error_from_errno(); + goto done; + } + while (!done) { - err = draw_file(tog_blame_view.window, f, &first_displayed_line, - &last_displayed_line, &eof, LINES); + if (pthread_mutex_lock(&mutex) != 0) { + err = got_error_from_errno(); + goto done; + } + err = draw_blame(tog_blame_view.window, f, lines, nlines, + &first_displayed_line, &last_displayed_line, &eof, LINES); + if (pthread_mutex_unlock(&mutex) != 0) { + err = got_error_from_errno(); + goto done; + } if (err) break; nodelay(stdscr, FALSE); ch = wgetch(tog_blame_view.window); nodelay(stdscr, TRUE); + if (pthread_mutex_lock(&mutex) != 0) { + err = got_error_from_errno(); + goto done; + } switch (ch) { case 'q': done = 1; @@ -1123,10 +1308,22 @@ show_blame_view(const char *path, struct got_object_id break; default: break; + } + if (pthread_mutex_unlock(&mutex) != 0) { + err = got_error_from_errno(); + goto done; } } done: - fclose(f); + if (blob) + got_object_blob_close(blob); + if (f) + fclose(f); + free(lines); + if (thread) { + if (pthread_join(thread, (void **)&err) != 0) + err = got_error_from_errno(); + } return err; }