Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to git cherry-pick from sibling directories?

I would like to use git cherry-pick to apply a commit from one file to another without rename detection (to many similar files lead to wrong detections).

(master) directory1/file

(master) directory2/file

But I don't know how to tell cherry-pick the corresponding directory.

I had another case where it worked fine with git-1.7.5.rc1 which now supports merge strategies aka -Xsubtree=.

(master) directory1/file

(branch) file

I called

git cherry-pick --no-commit -Xsubtree=directory1 branch~95

and it worked fine, taking the changes from (branch~95) file to (master) directory1/file without rename detection.

For the first case I tried calling git cherry-pick with -Xsubtree=../directory1 but that did not work. I guess I would have to tell git cherry-pick somehow to leave the directory2 and then go to directory1 to apply the patches.

Does anyone have a solution for this problem ?

like image 538
Stefan G. Avatar asked Apr 14 '11 08:04

Stefan G.


1 Answers

At a glance

git-read-tree

For subtrees, I'd look at

git read-tree -u --prefix dir1/ HEAD:dir2

This will not do a merge (git read-tree --merge does not support the --prefix simultaneously...)

manual solution

Otherwise, the best you can do, is probably

basecommitish='HEAD^'
git show "$basecommitish":dir1/a" > /tmp/a.orig
git show HEAD:dir2/b > /tmp/b.new
git merge-file dir1/a /tmp/a.orig /tmp/b.new    

This worked for my test repository resulting in correct merge with changes to both dir1/a and dir2/b.

Detecting base revisions

Unfortunately, finding the correct basse revision for the merge can be a challenge, as git merge-base won't work for other object references than commit ids.

Finding last revision where the files were identical

So here is a snippet that will get you started in finding a revision where both files were in synch (looking at the contents only):

git rev-list HEAD | while read sha1
do 
    blob1=$(git rev-list --objects $sha1:dir1/a) 
    blob2=$(git rev-list --objects $sha1:dir2/b)

    echo $sha1: $blob1 $blob2
    if [ "$blob1" == "$blob2" ]; 
    then 
        echo Match; 
        break; 
    fi
done

Output on my testrepo:

c5a6b97712d9ebd49146dad6523b2bbc33aea7c0: 4ce3b294e6408ace53b50127aafb2c9308caacf1 e913153db7650d7b8e947066652cf21388552812
7b75768fd3434c867d3741cf07044bf04ef1cc79: 03b82631ac519bf10c20bb12d3b1b03b872dd087 03b82631ac519bf10c20bb12d3b1b03b872dd087
Match

You can easily include any revisions that might exist on other branches by replacing git rev-list HEAD by git rev-list --all.

Finding a pair of revisions where the files were identical

A more advanced script that will look for matching contents in non-matching revisions by doing nested loops would be

function findblobs() 
{
    for path in "$@"; 
    do 
        git rev-list HEAD | while read sha1
        do 
            echo $sha1 $(git rev-list --objects "$sha1:$path")
        done | uniq -f 1
    done
}

Now you can find the same info by doing

findblobs dir1/a dir2/b | sort -k2 | uniq -Ddf 1

// output on testrepo again:
7b75768fd3434c867d3741cf07044bf04ef1cc79 03b82631ac519bf10c20bb12d3b1b03b872dd087
7b75768fd3434c867d3741cf07044bf04ef1cc79 03b82631ac519bf10c20bb12d3b1b03b872dd087

// force multiple hits across several revisions:
git show 7b75768fd3:dir1/a > dir2/b && git commit -am 'force synch with 7b75768fd3'

findblobs dir1/a dir2/b | sort -k2 | uniq -Ddf 1

// output is now:
46b8748f121f8842d936994fa09ad1a81b35d3cc 03b82631ac519bf10c20bb12d3b1b03b872dd087
7b75768fd3434c867d3741cf07044bf04ef1cc79 03b82631ac519bf10c20bb12d3b1b03b872dd087
7b75768fd3434c867d3741cf07044bf04ef1cc79 03b82631ac519bf10c20bb12d3b1b03b872dd087

Since sort(1) uses a stable sort, you can rely on the first commit hash to correspond to dir1/a and the second one to dir2/b in this sample call (note the ordering in the call to findblobs)

like image 110
sehe Avatar answered Oct 16 '22 18:10

sehe