开发者

git: deleted code mysteriously reappeared

So I was just making a patch to one of our files, and I saw some code that shouldn't be there (coupled with an error in the webpage caused by this rogue code). The code was added in a patch a few months ago, and then deleted by a revert of that commit about a month ago. So it really shouldn't be there.

I tried a few methods to track down where it came from:

  1. git blame: strangely, showed me the commit tag and message of the original commit that added the code, as if the revert never happened.
  2. git log -- filename: Showed all the commits I expected to be there, including the revert, but nothing after it that suggested it should reappear.
  3. Manual checking patches (bisect): The code reappeared in the commit directly after the revert, but when I checked out that patch, its log was totally different, and didn't have the revert.

This last observation leads me to conclude that the patch in question was a botched merge, but the commit message has no mention of a merge, and our team usually keeps the merge message that git generates.

So does that seem like the case? Was the code reintroduced by making a wrong choice during a conflict resolution? If so, is there a faster way to find this sort of thing? For example, the git blame said the code came from my original patch. But is there a way to get it 开发者_StackOverflowto directly tell me that it got reintroduced in patch X, after the revert? How does git keep track of where each piece of code came from, if it doesn't know that it was affected during the merge? Is it capable of telling me that someone messed with that piece of code during a merge conflict? I had figured that the merge itself should have showed up in the blame.


This looks like a definitive case of

Git merge, then revert, then revert the revert

Meaning: you shouldn't really revert a merge commit like, ever. Instead 'reset --hard' to the point before that. Other lowlevel techniques do apply, but I only recommend even looking at those if you absolutely cannot afford non-fastforward pushed (git resetting to an earlier commit will lead to 'forced' pushes upon pushing later new work).

What revert does is revert contents. However, the original merge commit will still show the merge source as a parent commit. This fools git into thinking the remote branch has been fully merged, even though the content has been reverted (!). With all possible nasty consequences.

git show-branch mybranch previouslymergedbranch`

git log --no-walk $(git merge-base -a mybranch previouslymergedbranch)

should be enlightening in showing you the relation(s)


@Tesserex Ok..., I said "looks like" :)

Still the odds are that you have been bitten by the marvelous-but-necessarily-imperfect merge tracking (base detection) of git[1]. Perhaps you can review the output of show-branch and merge-base at the time of the merge. If this was the last merge, you can

git rev-list --merges --parents -1 mybranch

This should return three sha1's: <mergecommit> <mergetarget> <mergesource>. Here, mergecommit is simply when the merge occurred, mergetarget is 'mybranch' and mergesource is 'previouslymergedbranch', so you can run the above commands.

It is possible that the resilient commit (that you reverted) was still pending a merge (according to git) and as such has been merged all over, undoing your 'revert' undo... This can happen for a variety of reasons (e.g. the original commit had been manually transferred or a conflicted cherry-pick, making it impossible for git to detect that the commit had already been applied to the merge-target)

I'm not even sure whether git would actively detect a non-conflicted ('pure') cherrypick in the middle of a merge (sequence of commits); Although it certainly can:

git log --left-right --oneline --cherry-pick --graph --boundary mergetarget...mergesource

This will show you what git sees as the delta in 'commits' between the two refs at the time of merge. The man page for git-log explains that --cherry-pick tells git to eliminate commits from the output even if they have a different commit-id (hash) but the diffs are identical for all intents and purposes.


[1] OT: this is the conundrum: we can make software smarter, but as long as there are leaky abstractions, there will be trouble:

  • merge tracking is not airtight in git
  • git merges in patchsets to replay commits, yet stores all data in snapshots only; this surfaces when reverting a merge commit, e.g. or when using grafts to 'fake' historical relations

The general rule of thumb seems to be: the most refined tooling will result in fewer problems. However, when the abtraction breaks, the shit will just be of the refined variety :)


I had this issue where a file was added and later removed on a branch that was not merged with master, but master had been merged back to feature branch, and the file kept reappearing when switching to and from this branch. I fixed it with a rebase squashing two commits which added and removed the "phantom" file . I used a script which I'd written awhile ago to find "lost files" on commits, or branches which may have deleted them.

Here's the script I use to find the file:

git-find-file () {
    for branch in $(git log --all --oneline --no-color  -- "$1" | cut -c 1-9)
    do
        git branch --contains $branch
        echo "found $1 in $branch"
    done
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜