From: Mark Jamsek Subject: tog: implement diff view 'p' keymap to write patch file To: gameoftrees@openbsd.org Date: Sat, 10 Aug 2024 00:55:12 +1000 As mentioned in the log view 'm' keymap thread, this is the next step to facilitate code review workflows with a new 'p' keymap to write out the currently viewed diff to a patch file. We write to a file in /tmp and report the path to the status line. In contrast to existing action reports that are only displayed momentarily, the report sticks to the status line until the user scrolls, closes, or changes the diff view. I think this is important to allow users to copy the path if they don't want to rely on tab completion in the shell in the event they have written out several patch files. One thing I quite like is the patch respects runtime knobs. So if context lines have been increased to 5, the diff in the patch file has 5 context lines; if the diff algorithm is changed or whitespace is ignored, this is reflected in the patch file. It's a nice shortcut to generating diffs with the CLI. As suggested by stsp, the next step after writing tests for this is to enable writing out a git-format-patch style file with the 'P' keymap. ----------------------------------------------- commit 7c2ba31d44ea47bfec058e68e4d25d8540ebba9f (main) from: Mark Jamsek date: Fri Aug 9 14:45:51 2024 UTC tog: add diff view 'p' keymap to write out a patch file Write the current diff view to a temporary patch file in /tmp and report the patch file pathname to the status line. M tog/tog.1 | 4+ 0- M tog/tog.c | 81+ 0- 2 files changed, 85 insertions(+), 0 deletions(-) diff 3271ec93b5ca1c58fef468135fe380819cef6e9e 7c2ba31d44ea47bfec058e68e4d25d8540ebba9f commit - 3271ec93b5ca1c58fef468135fe380819cef6e9e commit + 7c2ba31d44ea47bfec058e68e4d25d8540ebba9f blob - 0f8f83ded2fa834467a0c0bef5de29b3578ddcaa blob + 6e0e4df78865c58469ef88303ebfa3222886178f --- tog/tog.1 +++ tog/tog.1 @@ -455,6 +455,10 @@ view, move to the Nth next (older) commit. If the diff was opened via the .Cm blame view, move to the Nth next line and load the corresponding commit (default: 1). +.It Cm p +Write the currently viewed diff to a patch file in +.Pa /tmp . +The patch pathname is drawn to the status line. .It Cm / Prompt for a search pattern and start searching for matching lines. The search pattern is an extended regular expression. blob - dbbaf92a90b365370c465e5011b8410a7a8d6c18 blob + 13586fd308da97f395a46278d336699dc1aa3eac --- tog/tog.c +++ tog/tog.c @@ -336,6 +336,7 @@ get_color_value(const char *envvar) struct tog_diff_view_state { struct got_object_id *id1, *id2; const char *label1, *label2; + char *action; FILE *f, *f1, *f2; int fd1, fd2; int lineno; @@ -585,6 +586,7 @@ struct tog_help_view_state { KEY_("A", "Toggle between Myers and Patience diff algorithm"), \ KEY_("a", "Toggle treatment of file as ASCII irrespective of binary" \ " data"), \ + KEY_("p", "Write diff to a patch file in /tmp"), \ KEY_("(", "Go to the previous file in the diff"), \ KEY_(")", "Go to the next file in the diff"), \ KEY_("{", "Go to the previous hunk in the diff"), \ @@ -1620,10 +1622,13 @@ action_report(struct tog_view *view) /* * Clear action status report. Only clear in blame view * once annotating is complete, otherwise it's too fast. + * In diff view, let its state control view->action lifetime. */ if (view->type == TOG_VIEW_BLAME) { if (view->state.blame.blame_complete) view->action = NULL; + } else if (view->type == TOG_VIEW_DIFF) { + view->action = view->state.diff.action; } else view->action = NULL; } @@ -5616,6 +5621,8 @@ close_diff_view(struct tog_view *view) s->id1 = NULL; free(s->id2); s->id2 = NULL; + free(s->action); + s->action = NULL; if (s->f && fclose(s->f) == EOF) err = got_error_from_errno("fclose"); s->f = NULL; @@ -5811,6 +5818,67 @@ show_diff_view(struct tog_view *view) } static const struct got_error * +diff_write_patch(struct tog_view *view) +{ + const struct got_error *err; + struct tog_diff_view_state *s = &view->state.diff; + FILE *f; + char buf[BUFSIZ]; + char *path; + size_t r; + off_t pos; + + if (s->action != NULL) { + free(s->action); + s->action = NULL; + } + + pos = ftello(s->f); + if (pos == -1) + return got_error_from_errno("ftello"); + if (fseeko(s->f, 0L, SEEK_SET) == -1) + return got_error_from_errno("fseeko"); + + err = got_opentemp_named(&path, &f, GOT_TMPDIR_STR "/tog", ".diff"); + if (err != NULL) + return err; + + while ((r = fread(buf, 1, sizeof(buf), s->f)) > 0) { + if (fwrite(buf, 1, r, f) != r) { + err = got_ferror(f, GOT_ERR_IO); + goto done; + } + } + + if (ferror(s->f)) { + err = got_error_from_errno("fread"); + goto done; + } + if (fseeko(s->f, pos, SEEK_SET) == -1) { + err = got_error_from_errno("fseeko"); + goto done; + } + + if (fflush(f) == EOF) { + err = got_error_from_errno2("fflush", path); + goto done; + } + + if (asprintf(&s->action, "patch file written to %s", path) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + + view->action = s->action; + +done: + if (f != NULL && fclose(f) == EOF && err == NULL) + err = got_error_from_errno2("fclose", path); + free(path); + return err; +} + +static const struct got_error * set_selected_commit(struct tog_diff_view_state *s, struct commit_queue_entry *entry) { @@ -5845,6 +5913,10 @@ reset_diff_view(struct tog_view *view) s->first_displayed_line = 1; s->last_displayed_line = view->nlines; s->matched_line = 0; + if (s->action != NULL) { + free(s->action); + s->action = NULL; + } diff_view_indicate_progress(view); return create_diff(s); } @@ -5904,6 +5976,12 @@ input_diff_view(struct tog_view **new_view, struct tog s->lineno = s->first_displayed_line - 1 + s->selected_line; + if (s->action != NULL && ch != ERR) { + free(s->action); + s->action = NULL; + view->action = NULL; + } + switch (ch) { case '0': case '$': @@ -6104,6 +6182,9 @@ input_diff_view(struct tog_view **new_view, struct tog diff_view_indicate_progress(view); err = create_diff(s); break; + case 'p': + err = diff_write_patch(view); + break; default: view->count = 0; break; -- Mark Jamsek GPG: F2FF 13DE 6A06 C471 CA80 E6E2 2930 DC66 86EE CF68