3

Let me start by saying I've searched high and low and have not found a good answer.

If my change were only converting tabs to spaces, or running a script that did everything automatically, then this one would work: git: change styling (whitespace) without changing ownership/blame?

I want to run an arbitrary interactive cleanup, such as running a style linter then taking human steps to resolve the issues, how do I then make a commit without affecting the git blame? I see there is --reset-author, but it turns out that resets everything to the author of that one commit, not what this situation needs.

SwimBikeRun
  • 4,192
  • 11
  • 49
  • 85
  • Related: [How do I run a code formatter over my source without modifying git history?](https://stackoverflow.com/questions/53502654/how-do-i-run-a-code-formatter-over-my-source-without-modifying-git-history) – ggorlen Jun 01 '23 at 19:06
  • Does this answer your question? [How do I run a code formatter over my source without modifying git history?](https://stackoverflow.com/questions/53502654/how-do-i-run-a-code-formatter-over-my-source-without-modifying-git-history) – ggorlen Jun 01 '23 at 19:42

2 Answers2

1

You can still use the general idea outlined in the linked question, but you won't be able to use git filter-branch to run it. Well, not directly. Study the filter-branch script to see how it sets each new commit it makes so that this preserves the author and committer name, email address, and time-stamp, and to see how it runs a tree-filter.

One way or another you will have to write some not-entirely-trivial code yourself, to run your style linter and then obtain the desired help. I see two obvious paths for doing this:

  • Reproduce the logic of filter-branch, but in something that saves state across multiple runs (saving state in files, for instance). That way you can start it, tell it to run until it needs interaction, and have it terminate at that point. Now you can fix things up and invoke it with the "continue" option, have it run until it needs help again, and then stop again. Repeat until done.

  • Write or modify a filter-branch script so that during your --tree-filter (this would be the one to use), if it encounters a situation in which it needs human help, it pauses. (Perhaps have your tree-filter read an instruction from a named pipe at this point. The instruction can be limited to just 'continue': the idea is that it doesn't actually continue until told to go ahead.)

    While it's paused, you manually enter the temporary directory containing the tree and fix it up. When that's ready, you send the "continue" instruction into the named pipe, and your tree filter then returns control to filter-branch, which goes on its way, filtering.

This second method means that you don't have to save and load the filter-branch state—the existing git filter-branch code just works as is; it just seems that somehow, the tree-filter it runs is sometimes so slow as to take minutes instead of just a few seconds.

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
  • Oh man. Too bad git doesn't have a `commit --retain-original-author` option. Well I think the real answer to our problem is run the linter we have all agreed upon before committing code, else the git blame will mess up upon subsequent cleanups. – SwimBikeRun Feb 24 '19 at 02:22
0

how do I then make a commit without affecting the git blame

You can get pretty darn close, I'd think compensating for the hand modifications won't be worth the pain, but ignoring automatic-reformatting changes is easy: git blame applies any text conversions you specify to the file contents, so tell it to run a prepass with your linter in full-auto mode before it inspects the results for blameworthy changes.

Here's a test case where the "style linter" just adds "REFORMATTED" as the first word of the second line. and git is instructed to regard that as not blameworthy by running (an idempotent version of) it as the text conversion.

cd into a trash directory and copy this into file temp inside,

find ! -name temp -delete; git init
doit() { eval "$@"; shift $(($#-1)); git add .; git commit -m "$*"; }
(
doit '>file'
doit echo '>>file' line1
doit echo '>>file' line2
doit '>B'
doit sed -i "'s/line2/REFORMATTED line2/'" file '#' 'REFORMATTED line2'
doit echo '>>file' line3
) >/dev/null

set -x
git log --oneline --graph --decorate

git blame file

git config diff.REF.textconv 'awk '\''NR==2 && $1!="REFORMATTED" {$1="REFORMATTED "$1 }1'\'
mkdir .git/info
echo file diff=REF >.git/info/attributes

git blame file

then do sh temp. Your metadata will change 'cause timestamps and ids, but otherwise you should see

$ sh temp
Initialized empty Git repository in /home/jthill/src/snips/test/.git/
+ git log --oneline --graph --decorate
* 282c142 (HEAD -> master) line3
* ee58923 REFORMATTED line2
* d8558d2 >B
* 6801a1d line2
* 030e551 line1
* f912c83 >file
+ git blame file
030e5517 (jthill 2019-02-23 18:41:36 -0800 1) line1
ee589239 (jthill 2019-02-23 18:41:36 -0800 2) REFORMATTED line2
282c142d (jthill 2019-02-23 18:41:36 -0800 3) line3
+ git config diff.REF.textconv 'awk '\''NR==2 && $1!="REFORMATTED" {$1="REFORMATTED "$1 }1'\'''
+ mkdir .git/info
+ echo file diff=REF
+ git blame file
030e5517 (jthill 2019-02-23 18:41:36 -0800 1) line1
6801a1d6 (jthill 2019-02-23 18:41:36 -0800 2) REFORMATTED line2
282c142d (jthill 2019-02-23 18:41:36 -0800 3) line3

... notice in particular the change in the blame for line2.

jthill
  • 55,082
  • 5
  • 77
  • 137