Similar to the previous patch, when git blame fails it usually means
that the blamed file is not tracked by Git. I don't know of other
common failure scenarios.
Since git blame runs in detached process, failure handling is a bit
more involved. Let's forward stderr to the debug buffer and escalate
the error. I'm not sure how to get the exit code from git blame in
"git blame | perl" - we can't use $PIPESTATUS.
As a workaround, interpret empty output as failure.
In case of failure, also hide blame; this makes the UI more consistent
I think.
git blame typically fails when the buffile is not tracked by Git.
Let's escalate the failure early instead of continuing witout blame
data, only to eventually hit the generic
"git blame-jump: missing blame info" error.
I frequently run ":git diff" followed by ":git blame-jump" to find
the commit that added the lines I deleted. I wasn't sure whether
":git blame" should allow this use case too. I think it should,
I don't think this is too confusing.
I personally mostly use "git blame-jump" so this "git blame" bug has
flown under the radar. When we run git blame in a git-diff buffer,
we want to move one column to the left since the lines in the target
blob don't have the +- prefix. We already subtract one but we add it
back accidentally when using "l" to go to this column. Fix it.
In future we should try to preserve more of the selection(s), not
just the main cursor.
When running git blame in a "git show" buffer, we annotate the youngest
version of the file that has the line referenced by the diff line
at cursor.
In case the cursor is on an added or context line, we simply show
the version from the surrounding commit.
When the cursor is on a deleted line, we show the parent commit,
which still has the deleted line. However there is a bug: we use
the line number in the new version of the file. Fix that.
Commit 53d9b9b67 (Escaping tweak in git.kak, 2024-02-06) broke
blame-jump when the commit subject contains a single quote.
(Also on unbalanced "{" which is a rare edge case but we already have
it in our Git history.)
git.kak assumes that filenames don't contain ' or unbalanced {,
but we can't really make that assumption about people's names or
commit subjects.
Unfortunately the escaping here is very messy. We need to pass
arbitrary text to callbacks; maybe we should have closures that can
capture private temporary registers.
Our "git add" and "git rm" default to the current file. The shell
interprets globs in the filename, which can lead to surprising results,
for example if it's accidentally used on a scratch buffer like *git*.
If I run
git blame
execute-keys 10o<esc>,j
git blame-jump
that'll jump to the wrong commit.
Use a flag-lines option to tell if a line still has blame information
cached.
Closes#5084
Today we can recursively search history with "git blame-jump". However
that command has some drawbacks, mainly that it's blocking. Making
it async without any progress indicator might be confusing. Better
to run plain "git blame"[1] and press Enter.
Also it might be nice to enable recursive searches using only "git
blame" and `<ret>` (since that is bound to "git blame-jump" while
blame annotations are displayed).
Make "git blame" in git-diff/git-log buffers run "git show
$commit:$file" for the commit and file at cursor, and decorate this
blob view with blame annotations. The latter allows to use `<ret>`
and repeat.
Unfortunately this relies on a hidden option "git_blob" to keep the
commit ID and filename. Maybe we can put this metadata somewhere
else like the buffer name or contents, ideally in a way that survives
serialization.
I'd still keep "git blame-jump" because it seems faster for the common
case of tracking down a single line.
[1]: In my testing, "git blame --incremental" is not any slower than
"git blame -L123,123" at finding that line.
Running git blame signals intent to view the blamed commit. Let's make
`<ret>` to go to that commit+line as long as blame info is shown.
In diff buffers we already use `<ret>` for "diff-jump".
Like blame annotations, the blame-jump mapping lives in window scope.
This means it will not collide with "diff-jump" which is mapped in
buffer scope.
Add the mapping synchronously (unlike the rest of the git-blame code)
to perhaps allow the user to override the mapping:
git blame; map window normal <ret> ...
Our ":git blame" annotates each line with the most recent commit.
However often a line has been modified by several commits.
Introduce ":git blame-jump" which shows the commit that added the
line at cursor. Crucially, it works also in Git diff buffers, so it
can be used recursively to find the full history of a line.
To do the recursive blame from a diff, I need to navigate to the
old (deleted) version of a line. Since old and new line are usually
neighbors. Speed up the common scenario of finding the old version
by making ":git blame-jump" jump to the new version. This means the
initial diff view might not include the commit message etc. Compensate
this by showing the commit's date+author+subject in the status line.
Here are some test cases.
- run blame-jump after "git blame"
- create an uncommitted or unsaved line, run "git blame" and
"blame-jump" on the uncommitted line
- run blame-jump without running "git blame"
- run blame-jump in "git show"
- run blame-jump in "git diff HEAD"
- run blame-jump in "git diff --cached"
- run blame-jump in "git diff" (YMMV if there are cached changes,
could fix that)
Naming: there are some similar commands in the wild [1];
they are usually called "show-blamed" or similar, but they
don't jump to the corresponding line. Also our list of git
commands is getting a bit messy (especially the undocumented
show-diff/hide-diff/next-hunk/prev-hunk; subject first naming seems
better).
[1]: f6e78ec4c0/kakrc (L423)
Future work: to go back to the previously-blamed commit we need to
have had the foresight to use "rename-buffer". Perhaps we want to
add some kind of buffer stack (like Magit does for example).
Diff buffers created by ":git diff" differ from other filetype=diff
buffers in that they use "git rev-parse --show-toplevel" as root
directory for diff-jump. This makes sense because paths printed by
"git diff" are relative to that directory.
Today we handle the above difference by making ":git" override the
diff-jump mapping. This doesn't work for buffers that were read from
a file. Fix this by introducing a separate filetype, "git-diff",
which allows to move the mapping in the usual place.
This breaks existing filetype=diff hooks[1] which need to be adapted
to match git-diff (also git-log).
Another motivation for the separate filetype is that a following
patch wants to enable Git blame commands in git-diff buffers but
not in plain diff buffers -- those should keep being blamed like any
other file if tracked by Git.
Perhaps git-* buffers are for Git metadata, not files that are tracked
by Git.
The added hooks awkwardly include their hook parameter to work around
hook ordering issues when switching between filetypes. See also [2].
We could also use filetype=git-log instead of git-diff.
Our highlighting for "git log --graph" would have rare false positives.
Closes#5049
[1]: https://github.com/search?utf8=%E2%9C%93&q=filetype%3Ddiff+language%3Akakounescript+-repo%3Amawww%2Fkakoune+-is%3Afork&type=code
[2]: https://lists.sr.ht/~mawww/kakoune/%3C20240201091907.973508-1-aclopte@gmail.com%3E
When a buffer has unsaved deleted/added lines, then any blame
annotations below those lines may be off. Fix this by feeding the
latest buffer contents to Git. Unfortunately there is no easy way
to distinguish between "Unsaved" and "Saved but not committed yet"
so let's keep using the umbrella term.
Sometimes a patch that fails to apply will apply cleanly after
adding -3. Also sometimes we do want to apply with conflict markers.
So this is another somewhat common option.
Sorry I did not test my earlier patch in production. It passes
blame flags via the environment. On a 5000 line file this results in
"execve failed: Argument list too long" errors.
Use a different way of checking whether blame info is shown.
When using either of
set-option g completers option=my_option
prompt -shell-script-candidates ...
While the search text is empty, the completions will be sorted
alphabetically.
This is bad because it means the most important entries are not listed
first, making them harder to select or even spot.
Let's apply input order before resorting to sorting alphabetically.
In theory there is a more elegant solution: sort candidates (except
if they're user input) before passing them to RankedMatch, and then
always use stable sort. However that doesn't work because we use a
heap which doesn't support stable sort.
Closes#1709, #4813
This adds a somewhat discoverable frontend for common uses of the
patch command.
Here are some frequently used commands
# apply selected changes
git apply
# revert selected changes
git apply -R
# stage selected changes
git apply --cached
# unstage selected changes
git apply --cached -R
# apply selected changes and stage them
git apply --index
For everyday use that's a lot of typing so I recommend adding mappings.
This commit adds `diff_add_char`, `diff_mod_char`, `diff_del_char` and `diff_top_char` as `str` options, with typical
defaults.
This commit also replaces the hard coded +, _, ≃, etc. hardcoded characters in `git update-diff` to use the options from
above.
We often use the pattern «map global normal ": foo"». The space
after the colon is unnecessary since execution of the mapping won't
add to history anyway, since 217dd6a1d (Disable history when executing
maps, 2015-11-10).
With the parent commit, the space is no longer necessary for user
mappings, so there is no reason to continue the cargo-cult.
Remove the space from mappings to set a good example.
There is a bug that causes `:git show-diff` to fail when using an external diff, for example difftastic.
This change ensures that we don't use an external diff tool when diffing the current buffer.
The wrapper for "git blame" creates flags for each line of the buffer.
It parses the output from git and would send a flag (or a series of
flags) each time the commit to blame for a line differs from the
previous one. For files that were touched by a large number of commits,
this results in a high number of kakoune processes being launched, and
may take some time. This is visible in the session through the flags for
the different commits appearing on the lines one by one, possibly during
several seconds.
To speed up the process, batch flags before passing them to the kak
session. One solution could be to send all flags at once, but this might
delay the appearance of commit info for too long if "git blame" really
takes a long time. The alternative solution retained for this commit
consists in grouping as many flags as we can during one second
(roughly), to pass them to kakoune, and then to move on to the next
flags. This way, a new batch of commit information flags appears every
second or so in the client, until all information is added. This should
be much faster than lauching a kakoune process for each commit
reported by "git blame": tests have shown that blaming a large file in
the Linux repository goes 4.5 times faster when batching flags.
Co-authored-by: Johannes Altmanninger <aclopte@gmail.com>
git-diff-goto-source is specific to diffs produced by Git. This patch
generalizes the logic and moves it to a new diff-jump in diff.kak.
The main differences are:
- diff-jump handles plain file diffs (i.e. without the -r option). These
have no "diff" line. This means that it needs to parse +++/--- instead.
- diff-jump can go to the old file, not just the new one.
- diff-jump allows to override the base directory and the number of
directory components to strip.
git-diff-goto-source was implemented with several nested try/catch
blocks. Implementing the extra features would have added more
nesting, redundancy or hidden options. To avoid that, I ported the
parsing logic to Perl (which git.kak already depends on). Maybe
it's possible to do the same in awk.
Potential concerns:
- We could move diff-jump to a new rc/tools/diff.kak but then it's not
obvious where the "diff" module belongs to.
- Should diff "diff-jump -1" be spelled "diff-jump -p1"?
In future, the diff parser could be reused to implement a vimdiff-style
feature: given a diff and the "old" line number, we can compute the
corresponding "new" line number. Perhaps diff-jump should get a -client
argument.
We want to move git-diff-goto-source from rc/tools/git.kak
to rc/filetype/diff.kak (or should we could create
rc/tools/diff.kak?). Either way, create the diff module so we can
formalize this dependency.
Currently this module only provides highlighters, so require it
wherever we reference them.
Keep the diff-select-{file,hunk} commands outside the module because
people might already use them in git buffers.
adds the ability to press <ret> within a hunk and navigate to the original
source code. This can be useful because one often needs to go back and forth
between the diff and the full source code.
- You can press <ret> anywhere _within_ a hunk i.e. lines that start with
` `, `+`, `-`. You will be taken to the exact line in the source that corresponds
to where you pressed <ret> in the hunk. It actually does not make sense
to press <ret> on a `-` line because that does not exist anymore but
in that case you are taken to a nearby line in the hope this is still useful.
- You can also press <ret> on a range line (lines that
look like @@ ... @@). If you press <ret> on anywhere on a range line e.g.
```
@@ -120,3 +123,4 @@ fn some_function {
```
The code will try to navigate to the section heading "fn some_function {"
Note that the section heading is _not_ necessarily located at the
range line (in the above example the range line is 123).
- You can press <ret> on a +++ line also and you will be taken the first
line of the file
Caveats:
- Navigation to the original source file will be accurate only if any edits to
the original source file have been saved to disk, because otherwise
they will not be detected by the `:git diff` or `:git show` commands
- This feature should work well for most typical uses e.g. `:git diff`, `:git diff HEAD^`
`:git diff <some-sha1>`. In fact this feature should work in all scenarios when
the *current files* on disk are being compared _with_ some arbitrary git revision/staging.
It will be less useful in other scenarios when two arbitrary revisions are being
compared to each other or when you are trying to compare staging to some revision.
For example when you invoke `:git diff --staged` you are trying to compare staging
with HEAD but are navigating to what is currently on disk (which may be different
from staging).
Co-authored-by: Johannes Altmanninger <aclopte@gmail.com>
This adds two things I forgot in
9a7d8df4 (Avoid accidentally using environment variables in sh scopes)
Mea culpa, the problem was that I was skipping matches with "filetype"
because that's usually just a hook parameter as in "WinSetOption filetype=.."
rg --pcre2 '\b(?!filetype=)\w+=' rc/
So I missed these two cases where a shell variable is actually called "filetype".
The one in git.kak was not a problem because show_git_cmd_output is only
ever called with sane inputs. However, file.kak does use the filetype
environment variable for many mime types, for example:
filetype=somefiletype\''; echo -debug injection; nop '\' kak /dev/null
Will run the echo since /dev/null has mime type "inode/chardevice"