tags:

views:

49

answers:

3

Hi all

It's kind weird, but I can't fulfill a pretty common operation with git. Basically what I want is to checkout a feature branch, not using it's head but using SHA id. This SHA points between merges from master branch.

The problem is that all I get is just master branch without a commits from feature branch. Currently I'm trying to fix a regression introduced earlier in master branch.

Just to be more descriptive, I crafted a small bash script to recreate a problem repository:

#!/bin/bash

rm -rf ./.git
git init

echo "test1" > test1.txt
git add test1.txt
git commit -m "test1" -a

git checkout -b patches master

echo "test2" > test2.txt
git add test2.txt
git commit -m "test2" -a

git checkout master

echo "test3" > test3.txt
git add test3.txt
git commit -m "test3" -a

echo "test4" > test4.txt
git add test4.txt
git commit -m "test4" -a

echo "test5" > test5.txt
git add test5.txt
git commit -m "test5" -a

git checkout patches
git merge master    

#Now how to get a branch having all commits from patches + test3.txt + test4.txt - test5.txt ???

Basically all I want is just to checkout branch "patches" with files 1-4, but not including test5.txt.

Doing: git checkout [sha_where_test4.txt_entered]

... just gives a branch with test1,test3,test4, but excluding test2.txt

More complex example:

#!/bin/bash

rm -rf ./.git
git init

echo "test1" > test1.txt
git add test1.txt
git commit -m "test1" -a

git checkout -b patches master

echo "test2" > test2.txt
git add test2.txt
git commit -m "test2" -a

git checkout master

echo "test3" > test3.txt
git add test3.txt
git commit -m "test3" -a

echo "test4" > test4.txt
git add test4.txt
git commit -m "test4" -a

echo "test5" > test5.txt
git add test5.txt
git commit -m "test5" -a

git checkout patches
git merge master

echo "test6" > test6.txt
git add test6.txt
git commit -m "test6" -a

#Now how to get a branch having all commits from patches + test3.txt + test4.txt - test5.txt ???
git log --topo-order | cat

# Now I need something to help me going back to history 
# without manually calculating that patches~2 sha's
git checkout -b patches.tmp master~1
git merge patches~2

Thanks.

A: 

A git merge operation doesn't go back and change history, rather it creates a new commit pointing to the two histories that it came from. So, in pictures, what you have is something like this:

  2-----------\
 /             \
1---3---4---5---M (HEAD)

When you rewind history to go back to revision 4, you get files 1, 3, and 4 but not 2.

Greg Hewgill
Yes. You are right.The problem is that I need that test2.txt
MageSlayer
@calmh's answer describes one way to do that, by creating a *new* merge commit.
Greg Hewgill
I appreciate his answer, but it's too much work imho. I'm already digging in cherrypick direction. Yet not quite successfully though.
MageSlayer
I'd humbly say that if merging is too much work, why do you bother branching at all ? One hardly comes without the other...
subtenante
+1  A: 

There is no such point. You have two parallel paths of development here, one containing the files test1 and test2, the other containing files test1, test3, test4 and test5. You can create such a point with a merge, though.

Check out the SHA1 for the commit that adds test4, and merge with the commit that adds test2. For example, after running your script my repository looks like this:

*   b054987 (HEAD, patches) Merge branch 'master' into patches
|\  
* | 5ae790f test2
| * f2a3dac (master) test5
| * 70e8cd2 test4
| * c4102ed test3
|/  
* d448eaa test1

On this, run:

% git checkout 70e8c
% git merge 5ae79

The result is a HEAD which contains files 1-4:

*   bcc8f7a (HEAD) Merge commit '5ae79' into HEAD
|\  
| | *   b054987 (patches) Merge branch 'master' into patches
| | |\  
| |/ /  
| * | 5ae790f test2
| | * f2a3dac (master) test5
| |/  
|/|   
* | 70e8cd2 test4
* | c4102ed test3
|/  
* d448eaa test1

% ls
test1.txt   test2.txt   test3.txt   test4.txt

You can now create a branch from this point of you like.

calmh
Sure I can do that manually, but it requires a lot of work in reality. I'm afraid git bisect won't help me here too.
MageSlayer
But this does what you want, just call the branch `patches` after the merge. If not, I'm unsure what it is you want to achieve? There is no point in the development you describe that contains files 1 to 4, so you need to create such a point. I don't think any other VCS would be able to checkout a commit that doesn't exist...
calmh
Basically I'm trying to present that checkout as linear set of patches to be able to bisect that branch. Your solution is perfectly valid, except that I need manually find out commits refs to merge in two branches.
MageSlayer
I see. Then I'd say the problem is that the history isn't linear, therefore there isn't a linear set of patches to bisect. That is a disadvantage of working with branches in this way.
calmh
Yes. I feel some hackery needed :)
MageSlayer
Possibly implementing something like http://nvie.com/git-model for the future can help with this. Then you can at least track down which feature branch is guilty, and from that perhaps do bisections within that branch.
calmh
+1  A: 

Regarding your first example, you need to replay test3 and 4 on top of test2: this is a classic case of rebase --onto:

Start from:

alt text

Mark the current patches emplacement, and move the patches branch where you would like to end (test4):

C:\Prog\Git\tests\rep\main>git checkout patches
Switched to branch 'patches'

C:\Prog\Git\tests\rep\main>git checkout -b tmp
Switched to a new branch 'tmp'

C:\Prog\Git\tests\rep\main>git checkout patches
Switched to branch 'patches'

C:\Prog\Git\tests\rep\main>git reset --hard master~1
HEAD is now at 8448d0f test4

That gives you:

alt text

And just rebase onto what you want the correct sequence of commits:

C:\Prog\Git\tests\rep\main>git checkout tmp
Switched to branch 'tmp'

C:\Prog\Git\tests\rep\main>git rebase --onto tmp tmp~1 patches
First, rewinding head to replay your work on top of it...
Applying: test3
Applying: test4

Which gives:

alt text

That only works for linear set of commits to be moved around.

VonC
Pretty neat.It looks like a black magic. I'm just trying to understand why commits moved on top of patches instead of tmp, as --onto tmp suggested.According to manual, git rebase [--onto <newbase>]<upstream> [<branch>], newbase is tmp!Do I miss something?
MageSlayer
@mageslayer: commits didn't move on top of `patches`, but on top of `tmp`. `newbase` is indeed `tmp`. `patches` ends up at the right commit simply because, before the `rebase --onto`, I reset it at the right destination place I wanted it to be at.
VonC
@mageslayer: since a "branch" in Git is really just a label to the tip of a graph path, you can feel free to move that label around... except if you did already published that branch (`push`), which would mean a world of hurt for anyone pulling from your (new) branch: everything would be to be merged again.
VonC
Hm. You mean that commits are on top of tmp after rebase, but tmp tip is still at test2?Does git move patches tip to tmp last commit?Is it so?
MageSlayer
`tmp` tip is still at `test2` indeed: the new base for `test3` and `test4` is '`tmp`' (`test2`). since `patches` referenced `test4` before the rebase, it still references `test4` after.
VonC