TL;DR
(You'll probaly misunderstand this, so better skip to the next section ;)
I'd like to find the (previous) commits a patch (commit or staged/unstaged workspace change) would change.
TL;DR: continue reading, but skip "Background" ...
Background
I'll describe my workflow here, so you can understand what I need.
On my development branch I frequently commit small changes in a lot of WIP commits. When I'm happy with my overall change, then I interactively rebase all these WIP commits to make up a clean history. I squash commits into other commits and write a detailed commit message.
There are three cases where I'd like to note the previous commit another commit should be squashed into.
- When I notice that there is a bug that I've introduced in one of the previous WIP commits
- When I'd like to explicitly merge the most recent change into a certain WIP commit
- Mentioning the commit ID that introduced a bug within the bugfix's commit message
Then I make a commit noting the commit that change (bugfix) has to be squashed into.
A history might look like this:
[WIP] Fixed some bug [SQUASH with '[WIP] Made some sloppy change'] (A)
...
[WIP] Made some sloppy change (B)
...
(Sometimes I produce more than 15 commits within 4 hours)
Since I do such minor patches very often I thought about an automated solution.
What I want to know
When a patch ...
- removes and adds lines (change), I'd like to get the commit that has introduced or recently changed that line
- adds lines, I'd like to get the commits of the lines before and after that insertion that where recently changed or introduces
- removes lines only, I'd like to get the commits that added or recently modified these lines
How I do this the manual way
TL;DR: skip this section and continue reading the "Example..." section ...
When preparing for commit (A)
, I search for the commit (B)
this way:
- Examine local change and remember the affecte line numbers by issuing
git diff
if the change has not been commited yetgit log -1 -p
if the change has already been comitted
- Find the commit that affected the memorized line(s) before the patch was made by issuing
git blame HEAD -- path/to/file
if the change has not been commited yetgit blame HEAD~1 -- path/to/file
if the change has already been comitted
Afterwards, in both cases then I get the commit message for those commit IDs using git log <COMMIT ID>
Example for add and change (change not commited yet):
This is the patch ( git diff
)
diff --git a/Gruntfile.js b/Gruntfile.js
index 9af9384..380726c 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -82,6 +82,7 @@ module.exports = function(grunt) {
less: {
build: {
options: {
+ dumpLineNumbers: "comments",
paths: ["target/"]
},
@@ -141,7 +142,7 @@ module.exports = function(grunt) {
src: {
files: 'src/**',
- tasks: ['copy:source-tree', 'copy:devel-source-tree']
+ tasks: ['copy:source-tree', 'copy:devel-source-tree', 'less']
}
}
});
This is git blame -L82,+8 HEAD -- Gruntfile.js
and git blame -L142,+8 HEAD -- Gruntfile.js
f39f25c0 (Nobody 2014-05-17 16:08:20 +0200 82) less: {
f39f25c0 (Nobody 2014-05-17 16:08:20 +0200 83) build: {
f39f25c0 (Nobody 2014-05-17 16:08:20 +0200 84) options: {
f39f25c0 (Nobody 2014-05-17 16:08:20 +0200 85) paths: ["target/"]
f39f25c0 (Nobody 2014-05-17 16:08:20 +0200 86) },
f39f25c0 (Nobody 2014-05-17 16:08:20 +0200 87)
f39f25c0 (Nobody 2014-05-17 16:08:20 +0200 88) files: {
f39f25c0 (Nobody 2014-05-17 16:08:20 +0200 89) "target/styles.css": "src/less/styles.less"
e2e46b59 (Nobody 2014-05-03 10:30:34 +0200 142) src: {
e2e46b59 (Nobody 2014-05-03 10:30:34 +0200 143) files: 'src/**',
e2e46b59 (Nobody 2014-05-03 10:30:34 +0200 144) tasks: ['copy:source-tree', 'copy:devel-source-tree']
f9a635c0 (Nobody 2014-04-21 16:03:38 +0200 145) }
f9a635c0 (Nobody 2014-04-21 16:03:38 +0200 146) }
f9a635c0 (Nobody 2014-04-21 16:03:38 +0200 147) });
5128dcdc (Nobody 2014-04-20 12:56:05 +0200 148)
f9a635c0 (Nobody 2014-04-21 16:03:38 +0200 149) grunt.task.loadTasks("tasks/");
These are the commit messages ( git log --oneline -1 f39f25c0
and git log --oneline -1 e2e46b59
)
f39f25c [WIP] * Changed to less (left .css file)
e2e46b5 [WIP] * Changed watch configuration
Now I know, that I have to commit both hunks individually mentioning individual commits.
Solutions
Yes, I read the git-blame
man page, but I did not found a suitable option that does this out of box (I'm sure that I might have missed something).
Yes, I've also tried to search the internet for this. But, huh, it took me one page to describe it - how should I formulate the golden Google search expression to find this?
Yes, as you'll see in the next section, I have my own ideas.
My approach
I would create a script that will parse the patches generated by git log -p
or git diff
.
It would output all line numbers that a patch affects using the rules in the "What I want to know" section.
Then a bash script looks up the commit IDs by examining the git blame HEAD[~1]
output, extracting the commit ID.
The last step is to get the commit messages.
Another step could be, to decorate the whole patch output (of multiple chunks and files) with the commit messages just found.
(Actually at the moment I'm writing a Python script that does the patch parsing)
Alternative approach
Maybe this can be accomplished by extracting the commit column from git blame -- <FILE>
and git blame HEAD -- <FILE>
and feeding both outputs into diff
, then grep -C <N>
ing for 00000000
, etc. to find the commit IDs that are affected.
But I think this is fragile and not as reliable as the above method. And I need to parse the patch of the blame's diff either.
(I've tried it already. The hardest part is the diff output parsing.)
Try to reduce it to one question.
Is there a standard Git tool that combines the power of git blame with the data provided by git log by showing the commits that where changed by applying a commit?
I hope my question is clear enough. :)
Be secific.
I'm sure, I am.
To help with the latter part, you should really look into
git rebase -i --autosquash
. Autosquash rebase will look for specially formatted commits to automatically reorder your commits when you rebase and mark them for squash/fixup. Fromgit help rebase
:As for automatically looking up the last changes, doing it automatically will always be fragile.
Given the following history:
If you had an intermediate commit (B) that modifies the same line as (A) and (C) but you don't want your commit (A) to be merged with (B) but with the older (C) instead, you could very easily accidentally squash the wrong commits.
Also often times the commit you want to merge to would have no conflicting edits with your current changes so no amount of magic can make a script prophesize what you wanted to do. Scripting this part is, therefore, of marginal value, instead I find that using a GUI-based history browsers aid this step tremendously (I used gitg on Linux and gitx on Mac but any history browsers would do).