Git branch point

I am having trouble clearing my git history tree.

Basically my 1-branch git repository looks like this:

A--B--C--D--E--F--G--H--I--Z--X--Y...   master
          \
           F--G--H--I--J--K--L...       branch1

      

This commit E is some important change and might be overkill. The correct split point here should not be D. This commit E can either be removed or added to both branches.

Can this be done?

----- Update

Wow, thanks for your help! I tried what Torek suggested and it worked like a charm!

Now I have another repository to clean up, but this is more complicated.

Looks like:

1--2--3--4--*A--5--6--7--8--*B--9--10--11--*C--*D--12--13--14            master
          \ 
           5--6--7--8--9--10--11--12--13--14                             branch1

      

note1: the letters are the contents of the command
note2: * the letter commits are only for the master branch

Is there a way to clear the history tree like this?

Also, what is the recommended git method for projects structured this way?
then after branching, most commits will contain the same updates, except for the special commits associated with the branch

+3


source to share


3 answers


Firstly, +1 for drawing the commit graph. :-) Secondly, not very important, but you should think of it as two branches, master

and branch1

. There's nothing special about it master

, it's just a branch like any other. 1

Now, central to everything in git is the fact that you can never change a commit. This is due to the fact that git actually calls each commit, 2 using the big ugly 40 character SHA-1: SHA-1 is created by calculating the cryptographic checksum of the commit content, so if you (try) to change anything, it changes "true name" and you will have a new and different commit.

Instead, you can copy the old commit to the new one by making the necessary changes along the way just before the commit is complete (and hence getting its "true name"). We'll make copies.

You drew this graph:

A--B--C--D--E--F--G--H--I--Z--X--Y...   master
          \
           F--G--H--I--J--K--L...       branch1

      

but this is not entirely correct, because each commit points to its parent commit, 3 and here F

points to E

when we are looking at master

, but F

points to D

when we are looking at branch1

. (I am assuming that every single letter is behind the "true name" SHA-1 of the given commit.) I'm going to assume that the ones currently on branch1

are copies, although that's not really what if you're happy with the trees, associated with those commits (that is, what you get when you are git checkout

one of them).

What you want looks like this:

A--B--C--D--E--F--G--H--I--Z--X--Y...   master
                         \
                          J'-K'-L'...   branch1

      

Here the "prime" sign in J'

, K'

and L'

indicates that these are copies of commits J

, K

and L

.

While I was writing this answer, some have suggested using it git cherry-pick

to create these copies. It works really well, but we can actually use git rebase

it to do the trick because where cherry-pick

is the simple ax that you can use to cut down one cherry tree rebase

is a fully automatic chainsaw that you can use them all in one fell swoop.

Let's start with what rebase

will work for branch1

; the easy way is git checkout branch1

(or we could just add branch1

as the final argument to git rebase

, but I'll use the "easy way").

Next, we need to know where we want the commits to turn off, i.e. had some way to call commit I

. From the above we can say master~3

: recount four commits from master

(whose names commit Y

) and you go through X

, then Z

, then reach I

. Or you can do it with true SHA-1, which always works but often requires cut and paste to get right. For the command below, I'll just write I

. We will rebase the base --onto

, which will commit.

Finally, we need to get rebase

to select commits J

, K

and L

to copy. There are many ways to do this. Perhaps easiest to use git rebase -i

, which will prompt you to reinstall everything on branch1

, and then you can delete the lines pick

for the first four commits. Or we can say rebase

exclude certain commits, but suppose you will be using an interactive method.

So the final command is here git rebase -i --onto I master

(after the command is executed git checkout branch1

). -i

makes it interactive so you can opt out of commits; --onto

chooses a target for a new series of commits, which rebase

will be cherry-picked; and the part master

tells rebase

which commits to exclude: in particular, any commits are reachable on behalf of master

. (This excludes commit A

, B

, C

and D

, but not copied version F'

, G'

, H'

and I'

which appear on branch

: You simply remove the line "pick" for them).



After it git rebase

finishes its series of commands git cherry-pick

, the last thing it does is move the branch shortcut for your current branch ( branch1

) to the newest commit command. So this gives:

A--B--C--D--E--F--G--H--I--Z--X--Y...   master
                         \
                          J'-K'-L'...   branch1

      

assuming everything goes well. (If this does not go well, you can git rebase --abort

stop the process and return everything as it was before.)


We could be more kind and (assuming I'

is branch1~3

) do:

git rebase --onto master~3 branch1~3 branch1

      

even without the initial one git checkout

, but that assumes that counting 3 is correct here (for master

and branch1

). This is basically the same as before, with three modifications:

  • add branch1

    to the end to make rebase

    check this as the first step
  • we use branch1~3

    instead master

    as an argument that says "Exclude this stuff, only reinstall stuff not available from here"
  • we discard -i

    , since this time we don't need to edit the "pick" commands.

This requires more careful commits counting to make sure all expressions ~

are correct (or, again, you can work with them using raw SHA-1 identifiers).


1 Well, master

there are a couple of peculiarities: first, it is the branch that you pushed to when you do git init

, and it creates a new repository. Another is that git merge

slightly flattens the merge message. But both are pretty trivial.

2 This does indeed apply to all four types of objects found in the git repository (commit, tree, annotated tag and "blob" - the latter stores the contents of the file), each is stored in the repository and then given a name that consists of its own cryptographic checksum.

3 More precisely, each commit has 0 or more lines in it parent ...

, each parent

providing the SHA-1 ID of the corresponding parent commit. The first parent is usually the "mainline" of the branch, and all additional parents indicate that this is a merge commit. An end without parents like A

above is a "root commit".

+3


source


You can create a new branch in I

and cherry pick all the changes made in branch1

, so basically

 git branch -b temp I
 git cherry-pick J..branch1

      



You will then have a new branch, temp

split in, I

unchanged E

and containing all commits branch1

starting from J

, if that worked right you can rename those two to make temp

open your new one branch1

. Before cherry-picking you can revert the commit E if you don't want it inbranch1

+2


source


One way to do this is to create a new branch as a copy of the master and do git reset --hard I-commit

and then cherry-pick

commit you want (hoping there aren't many)

+1


source







All Articles