Reputation: 75
I've been struggling with GIT for a while now since it does some weird things.
I have a base commit with the initial code.
Ok next I make a branch do my edits and commit and push:
git checkout -b feature/feature1
git commit -m "added feature1"
git push origin feature/feature1
Now I do a pull request on that feature.
After that I change branch and update for example:
git checkout -b chore/update-framework
git commit -m "updated framework"
git push origin chore/update-framework
At this point when I try a pull request on the 2nd push, it contains the first commit with all the feature stuff in it. So all the changed files from that commit are in the new pull request, while the old pull request only contains the feature files.
What am I doing wrong?
Upvotes: 4
Views: 8428
Reputation: 29
Let's say you have this repository with branch 'prev_branch'
---o---o---A prev_branch
Let's say you commit something A in prev_branch and created new branch 'new_branch'1, it won't differ until you start commiting to new branch
---o---o---A prev_branch + new_branch
Then you commit B in newbranch
---o---o---A prev_branch
\
B new_branch
The new commit B has A as its parent commit. Now prev_branch and new_branch differs.
---o---o---A---X---Y prev_branch
\
B---Z new_branch
summary:
When you create a new branch from a particular branch, you'll start from the point where that branch currently is. So all commit history will be there in the new branch as well. A good rule of thumb is to always create a new branch from the branch that you intend to eventually merge the new branch into (main branch). So if D is intended to be merged into main at some future point in time, create it from the current tip of main.
Solution:
git checkout main/master : Always checkout to main/master first
git branch -b new_branch : Create new branch
git add . : Add new changes here
git commit -m "message" : commit here
Upvotes: 0
Reputation: 489708
I think your issue here comes down to understanding what it means for a commit to be on (or it may be better to say contained in) a branch. This is different in Git than in many traditional version control systems.
In Git, each commit is on (or contained in) many branches, at least potentially. It can be on no branches, 1 branch, 2 branches, and so on. This is because, in Git, a branch name is just a moveable pointer to one specific commit. Think of it more as a yellow sticky note. Creating a new branch name means grabbing a blank sticky note and writing the name on it, feature/feature1
.
But where is the note attached? Well, here it helps a lot to draw (part of) the commit graph.
In Git, each commit "points to" its predecessor(s) (or parent commits). For a linear chain of commits, this is basically a series of arrows pointing backwards in time:
... <- o <- o <- o <- ... <- o
Each o
here represents a commit and each commit points back to its (one) parent.
A merge commit points back to two parents, which is in fact what makes it a merge commit. These are much harder to draw with arrows when restricted to plain text on StackOverflow:
o--o
/ \
... --o--* M--o
\ /
o--o
Imagine arrowheads on all the lines, so that they all point left-ish (up-and-left, or down-and-left, if needed). M
here is the merge commit, with its two parents. I marked one more commit here, commit *
. It is not a merge commit, but it does have two children (two commits that point back to it). This sometimes makes it more interesting.
In fact, commit *
is especially interesting before we make the merge commit. Let's draw that (by erasing M
and some o
s):
o
/
... --o--*
\
o--o
This is where branches really come into proper focus. Let's add names and longer <--
arrows:
o <-- bra1
/
... --o--*
\
o--o <-- bra2
Branch bra1
points to (or, equivalently, is a yellow sticky note pasted onto) the upper tip-most o
commit, and bra2
points to the lower tip-most o
.
That commit we called out as *
is on both branches (as is every commit to its left).
There are two more pieces to this particular puzzle. One is the name HEAD
, and the other has to do with what happens when we add new commits and write up new branch names.
HEAD
The name HEAD
, in Git, tells us about our current commit and current branch. The way Git does this is almost laughably simple: HEAD
normally just contains the name of the branch. Think of it as another sticky note (maybe bright pink, or green, or purple, just to make it obviously a bit different). It can point directly to a commit—that's the "detached HEAD" thing you have no doubt seen—but normally it just has a branch name written on it:
o <-- HEAD->bra1
/
... --o--*
\
o--o <-- bra2
This means we are on branch bra1
.
Let's make a new commit while we're on bra1
.
When making a new commit, Git:
HEAD
(it says branch bra1
).bra1
(that's some big ugly SHA-1 a1c397f...
or whatever).git add
ed, with your log message, pointing to that parent ID. The new commit gets a new unique SHA-1 (such as 0bc3112...
).bra1
.Step 4 causes bra1
to point to the new commit, and now we have:
o--o <-- HEAD->bra1
/
... --o--*
\
o--o <-- bra2
Just for completeness, let's look at making a merge commit.
The process of doing the merge itself can be messy, but actually making the merge commit is easy: it's just a commit with two parents. Note that we still are on bra1
. We run git merge bra2
. Git fires up the merge machinery to do the work in the work-tree. If there are conflicts, it leaves a mess behind and we have to fix that up manually, but if not, it automatically starts a new commit.
The new commit happens just as before, with one small change. In step 3, instead of writing one parent ID (the one from the existing bra1
), it writes two parent IDs: the first one is the usual one, and the second one is the ID obtained from reading bra2
.
Step 4 works as usual, writing the new commit's ID into bra1
(because HEAD
still says "use branch bra1
"):
o--o
/ \
... --o--* M <-- HEAD->bra1
\ /
o--o <-- bra2
Because M
is a merge (has two parents), this means that all the commits that used to be exclusively on bra2
are now on bra1
as well!
Commit *
used to be on both branches, and still is. And, there are still those two post-*
commits we can get to by starting from bra2
and working leftwards.
We're only allowed to move left(ish), so starting from bra2
, we are not allowed to move to M
, which means we cannot get to the top row of commits. We can only get there by starting there, or starting from M
. We are, however, not just allowed, but actually required, to follow all the parents of a merge like M
. So starting from bra1
, we get M
, then we get both sides of the branch structure all the way back to commit *
, and keep going leftward from there.
If you count the nodes, you will see that there are now three commits contained in bra1
that are not contained in bra2
... but all commits contained in bra2
are contained in bra1
.
Here HEAD
comes back into play.
You can use git branch
to just create a branch:
$ git branch bra3
By default, this reads HEAD
to figure out where we are now. HEAD
says bra1
and bra1
has M
's ID in it. So this makes bra3
point to M
:
o--o
/ \
... --o--* M <-- HEAD->bra1, bra3
\ /
o--o <-- bra2
Note that HEAD
still points where it used to, and no other branch is disturbed either, we just added a new moveable label bra3
. If we now move bra1
or bra2
, bra3
continues to point to M
.
Since HEAD
still points to bra1
, new commits will make bra1
change.
If we use git checkout -b
to create the new branch, though, we get this instead:
o--o
/ \
... --o--* M <-- bra1, HEAD->bra3
\ /
o--o <-- bra2
This looks almost exactly the same. The difference is that, besides adding the new branch name, we also change HEAD
. Now it points to bra3
.
Let's go back to git branch
, which just makes the branch and does not change HEAD
. The git branch
command takes one more optional argument:
$ git branch bra4 bra2
Instead of making bra4
point to the same commit as HEAD
, this says "make bra4
point to the same commit as bra2
". So now we have:
o--o
/ \
... --o--* M <-- bra1, HEAD->bra3
\ /
o--o <-- bra2, bra4
Now let's git checkout bra4
, just to see what that does (in terms of the graph—it also checks out the files, of course):
$ git checkout bra4
o--o
/ \
... --o--* M <-- bra1, bra3
\ /
o--o <-- bra2, HEAD->bra4
Again, nothing happens to any of the branch labels themselves. In the graph, we just change where HEAD
points.
(We could have used the combined form—we already have bra4
now so it's too late now, but it would have been one command instead of two—by doing git checkout -b bra4 bra2
.)
This means we get to choose which commit a new branch points to whenever we create that new branch. The default is "wherever HEAD
points", which usually means reading another branch name. We can make HEAD
point somewhere useful first, or we can just add that to the branch-creator and make it all happen together.
That leaves one question, and it does not always have a single right answer: where should we point that new branch?
Probably we want to point it to something like origin/master
or origin/feature1
. Sometimes it makes more sense to pick some other starting point, maybe even one with no label pointing to it.
origin/master
is a label?Yes, these things are branch labels—moveable pointers to commits—too. They're just not labels that you move. You let them move when you run git fetch
(or pull, but pull is just fetch followed by one other step). In other words, they "track the remote" (you git fetch origin
and origin/*
move if needed); so they are remote-tracking branches.
When we drew up the graphs above, we probably should have included these labels ... maybe something like this:
o <-- HEAD->feature1
/
... --o--* <-- origin/feature1
\
o--o <-- feature2
If you want to make a new feature3
that grows out from *
as well, git checkout -b feature3 origin/feature1
would do that. Commit *
would then be contained in four branches: feature1
, origin/feature1
, feature2
, and feature3
.
Upvotes: 5
Reputation: 706
When you did:
git checkout -b chore/update-framework
You did not checkout on master before.
To start a new branch from the commit you want (example with master):
git checkout -b chore/update-framework master
or:
git checkout master
git checkout -b chore/update-framework
or:
git branch chore/update-framework master
git checkout chore/update-framework
To correct your mistake, you can do:
git rebase --onto master feature/feature1 chore/update-framework
Git will pick all your commits of chore/update-framework which are not pointed by feature/feature1, then it will put them onto master and finally update your chore/update-framework branch.
Upvotes: 10