I am working on dev branch which is a parent branch and created something on branch a1 added new functionality completely independent and pushed to a1 only . Now i have another task created a bew branch b1 but as soon as i switch to dev i do not get code of a1 so if i create a new branch b1 ,I need some of the files present in a1 or all of them is fine . How to achieve this ?
- should i checkout to dev and take pull of a1 and merge a1 into dev then checkout yo b1 and changes will be in there ?
- should i checkout to b1 and take pull of a1 and then merge it .
is this the correct approach , if not please suggest . What is it ?
Also when exactly merge comes into action ?
2
Answers
If I understood you correctly, you need your changes of branch b1 in branch a1? If so, you need to do a merge request for b1 -> a1 (or pull b1 in a1 and merge directly). When all work on a1 is done you can merge back all changes from a1 to dev branch.
(Side note: you tagged your question with github and gitlab. Don’t do that. Pick one or the other—whichever one you’re using—if it’s applicable. Here, neither really matters at the moment, but on or the other will once you go to make a pull request or merge request.)
You’re starting with a bad (incorrect) assumption. There’s no such thing, in Git, as a parent branch. In fact, there isn’t any one particular meaning for the word branch in the first place (see What exactly do we mean by "branch"?), but regardless of which of the various meanings you choose, none of them take the modifier "parent".
What Git has, and is all about, are commits. Commit do have parent commits, so you’re in good shape there. A branch name in Git simply selects one particular commit, though.
I believe that what you mean is:
You have a series of commits ending at a currently-distinguished commit. This special (for now) commit has the name
dev
selecting it.You created a new branch name
a1
that identified the same commit asdev
.You then created one or more new commits while "on" branch
a1
. These new commits implement your desired feature.Things get more complicated here. These names—
dev
anda1
—are local to your own repository. They only have meaning in your own repository.The commits they identify have universally unique identifiers (UUIDs). These names are valid in every Git repository. Git calls these UUIDs hash IDs, or more formally object IDs or OIDs.
When you use
git push
, you select some commit (or set of commits) in your own repository that you wish to hand over to some other Git repository. You send these commits to that other repository, then ask their Git software to set a name—such as a branch or tag name—in their repository by which to find this commit. You set up a name in their repository because nobody likes to type in the UUIDs. They’re just too big and ugly for humans to work with.It’s natural for humans (being human) to use the same name in the other repository that they’re using in their own repository. It’s important for you, the human, to realize that this is what you are doing: the name
a1
in the other Git repository is a different name, even though your owna1
and theira1
are both spelleda1
. The one thing that’s really universal here—that works in all repositories, all the time—is the raw hash ID. By setting up the same short, human-readable name in both your repository and some far-off other repository, though, you can pretend that the namea1
is universal. It isn’t—it’s only good for a limited time, unlike the hash ID.That’s all an aside for now, but keep it in mind for later. Let’s move on to your immediate issue:
You’re mixing up three different names here. It’s time to pause and get a good solid mental picture of what’s going on in your repository. (Remember, this is your repository. It’s not anyone else’s repository, it’s yours.)
Every Git commit has a unique hash ID, such as
e188ec3a735ae52a0d0d3c22f9df6b29fa613b1e
. If someone gives you such a hash ID, you either have that commit with that hash ID, or you have no Git object at all with that hash ID. This is how one Git repository can give copies of stuff to another Git repository when needed: the sending Git tells the receiving Git some hash IDs, and the receiving Git tells the sending Git which of those hash IDs it needs. All of the rest—though there is a lot more—is mere optimization.But: what exactly is inside a commit? To find out, we can look at one:
(compare the above with what you see if you follow the link to the GitHub copy of this commit).
That’s the entirety of this one commit. It consists solely of metadata: information about this particular commit, such as the fact that Junio Hamano made it. But note the
tree
line: every commit is required to have exactly one of thesetree
lines, and that line gives the raw hash ID of the full snapshot of all files that goes with this particular commit.So a commit—if your Git has it—provides the
tree
object that provides the snapshot for that commit. These are all the files that you will see after you usegit switch
orgit checkout
to select that commit.(This particular
tree
is pretty big:This goes on for almost 500 lines: there are 496 top level entries in this tree object, which contains more tree objects, so that the snapshot holds about 4200 files in all. All but one of these files is literally re-used from some previous commit, however, so the commit itself just takes a few hundred bytes, despite holding 4200 files!)
So, this is what a commit is and does for you:
But there’s more to that metadata than just who made it (and when): see the
parent
lines above. This particular commit, in the Git repository for Git itself, is a merge commit with two parents. Merge commits are a bit unusual: most commits are ordinary commits, with exactly one parent.See the big ugly number after the word
parent
. Can you guess what it is? You probably can: it’s another hash ID. This parent number, stored inside each commit’s metadata, provides the parent/child relationships in Git. Those relationships are between commits, not between branches.A branch name, as I mentioned above, is simply a way of specifying one particular commit. It’s a commit that we (humans) find specially interesting for some weird, human-type reason. In this particular case, it’s interesting because it is the latest commit.
In fact, the very definition of a branch name, in Git, is that it’s a name that holds one commit hash ID, and that commit is then the branch’s tip commit. The tip commit of a branch is the latest commit on that branch. This property—of being the latest commit—isn’t actually part of the commit itself though! It’s just a temporary thing: that commit is the latest until we say some other commit is the latest.
Let’s look at an example
As an example, let’s take your own branch names
dev
anda1
. At some point, there was some commit with some hash ID that was the latest commit on your owndev
. I don’t know what that commit’s hash ID was (was ita123456...
orb987654...
ordeadcab
orbeeffad
or what?) so I’m just going to call it commitH
, for Hash. And I’m going to draw it like this, for no obvious reason as yet:That little arrow sticking out of
H
, pointing leftwards (backwards), represents theparent
line in commitH
‘s metadata. Thisparent
line holds the hash ID of some earlier commit. I don’t know the earlier commit’s hash ID either, so I’ll just call itG
, which is the letter beforeH
, and then I will draw that commit in:Commit
G
is of course an ordinary commit as well, so it points backwards to some still-earlier commit, so I draw that in now:Every commit here has metadata and a snapshot, and by starting at the latest one on your
dev
branch, I can have Git work backwards and find every earlier commit that is also on yourdev
branch. You can do the same thing very easily: just rungit log dev
, for instance, orgit switch dev
and thengit log
. Either way, Git will show you the metadata fromH
, then move back one step to commitG
and show you the name and email address and log message forG
. Thengit log
will move back one step to commitF
and show you that commit’s metadata, and move back one step, and so on, forever—or rather, until it runs out of steps-backwards.All we need to do to get all this to happen is to know the hash ID of commit
H
. But I don’t know the hash ID of commitH
, so what will I do? What I will do is use your namedev
, which you told me about. By using yourdev
I can find hash IDH
.We say that your
dev
points to commitH
, and I can draw that in now:I get (deliberately) lazy and don’t bother drawing the connections from commit to parent as arrows here, but I do draw the arrow sticking out of a branch name as an arrow—a longer one—because these arrows move.
When you created a new name
a1
, you made the namea1
select commitH
, just like your namedev
selects commitH
:All of these commits are now on two branches instead of just one. Commit
H
is the latest commit on botha1
anddev
. Nothing insideH
itself has changed: its snapshot and metadata are the same as always. What changed is that we added another name,a1
, and made its arrow point toH
.If we now use
git switch
orgit checkout
to select the namea1
, this tells Git that we’d like all the files from commitH
. The namea1
points toH
, so that’s where Git gets this idea that we wantH
‘s files. To show that we’re "on" brancha1
, I draw it like this:You now make some changes to some files and
git add
andgit commit
to make a new commit. This new commit gets a new, random-looking (but unique and not actually random at all) hash ID, which no Git software can ever use again for any other commit.1 I’ll just call this "commitI
" here, and draw it in now:The name
a1
now points to commitI
. No other name has changed; no commit has changed;2 but namea1
now selects commitI
and commitI
is therefore the last commit on brancha1
.This is by definition! If we run
git reset --hard HEAD^
to discard commitI
, what happens is that Git stores the hash IDH
back in the namea1
, so that commitH
becomes the last commit on brancha1
. Nothing actually happens to commitI
itself. It’s just that if we don’t have a name by which to find it, we’ll have to come up with the raw hash ID ourselves. Quick, what was that hash ID from the Git repository for Git. You have it memorized, don’t you? Well, I don’t. Without the name by which to find it, I couldn’t tell you what the hash ID is.So that’s why we have branch names: to find the last commit "on" that branch. And that’s what a commit is: it is metadata—who made the commit when, its log message, and crucially for Git, its parent or parents—plus a snapshot that saves all the files forever, or at least as long as the commit itself continues to exist.3
1This part of Git is deeply magic, or mathematical at least, and also terribly flawed as this trick will someday stop working. But as long as that day doesn’t happen until we’re all long dead and gone, nobody worries too much.
2The magical numbering scheme that Git uses—a cryptographic hash of the commit’s content—requires that no part of any commit ever change.
3When we eject some commit(s) off the end of some branch using
git reset
or similar, those commits continue to exist for some time. On GitHub, that amount of time is "forever", but in a local repository, we generally allow Git to clean away unused commits after 30 to 90 days or so.Returning to your basic issue
The name
dev
in your Git repository means "the most recent commit on mydev
branch". That commit has a snapshot, and that’s the snapshot you get when yougit switch dev
.If you don’t want that snapshot, you have only two options:
dev
, ordev
select a different commit.We use the names to find particular commits of interest, and you probably want to keep your own
dev
such that it still finds commitH
(whateverH
‘s real hash ID is). If that’s the case, you do not want to use the second option, leaving only the first option: use a different name.Now, you say that you want to start with the files that are in your latest
a1
snapshot. The way to do that is to create your new branch using the namea1
, so that your new branch selects the same commit, like this:You can now
git switch b1
and you’ll switch from commitI
, using all of commitI
‘s files, to … commitI
, using all of commitI
‘s files. This is no change at all, and Git will cleverly not change out any files.4 You can now change the files yourself andgit add
andgit commit
as usual:for instance (once you’ve made two more commits).
4You’ll find that you can use this cleverness of Git’s to create new branches after you’ve started working, if you’ve forgotten to switch branches first. For instance, if you accidentally kept going on branch
a1
—and even made a few new commits—you could create new branchb1
pointing to the current commit and then usegit reset
orgit branch -f
to force the namea1
to select an older commit.But that’s slightly-advanced Git-work, for fixing errors. For now, the main thing you need to know is that the branch name selects the commit, and that Git is clever when changing branch names without changing commits.
"But won’t the branch have the wrong parent?"
No: branches do not have parents! Given:
there’s no branch relationships at all. The name
dev
selects commitH
. That’s what it does, and that’s what it’s for. The namea1
selects commitI
. The nameb1
selects commitK
. There’s no "parent of nameb1
" at all.(Branch names do have an optional upstream setting. The upstream of
dev
is probablyorigin/dev
, and the upstream ofa1
will beorigin/a1
and the upstream ofb1
will beorigin/b1
. But that’s something else entirely: it is not a "parent branch". There is no such thing as a parent branch.)There are parent and child commits: commit
I
is a child ofH
, which means thatH
is the (note the, not a) parent ofH
.J
is a child ofI
, which meansI
is the parent ofJ
.Once some commit exists, no part of that commit can ever change. The parentage of a commit is part of the commit itself, so this can never change.
J
‘s parent is alwaysI
here. But we can create new branch names at any time, and then create new commits, so we can, if we want, create a new namebr3
:and then switch to
br3
:We now have the files from commit
H
. If we change some files and commit, we get:Commit
H
now has two children,I
andL
. ButI
still has just one parentH
, and will forever, becauseI
exists now and no commit, once made, can ever be changed.What if I need to change a commit?
You can’t—but note that we find commits through names. Suppose there’s a mistake in commit
J
, and you wish to fix that mistake. Let’s make yet another new commit and call itJ'
and make its parent beI
, using a temporary branch name,fixup
:Now let’s copy the effect of commit
K
to a new commit that’s otherwise just likeK
, but hasJ'
as its parent:Now let’s force Git to have the name
b1
point toK'
, and then switch to branchb1
:Note that there’s no name for commit
K
any more. We can’t find commitK
. CommitK
was how we found commitJ
and we can’t findK
so we can’t findJ
either. But the nameb1
now findsK'
, which findsJ'
, which findsI
just likeJ
foundI
. If we delete the temporary branch name we get:and as long as we have forgotten what the actual hash IDs were for commits
J
andK
, it sure looks like we changed commitJ
. We didn’t—commits are immutable—but we did something "just as good" for most purposes.(You don’t need to worry about this yet, but you should remember it.)
So that’s your answer: work by adding on just past branch
a1
Instead of starting your new branch
b1
from the tip commit of branchdev
, just start your new branchb1
from the tip commit of brancha1
.Possibly never. If you’re using GitHub in particular, and you make a pull request, whoever operates on this pull request may select an action other than MERGE. If you’re using GitLab in particular, and you make a merge request, whoever operates on this merge request may select an action other than merging.
You should leave this for later: for now, if you need the work you did in
a1
to begin working onb1
, you’ll need to start from where you left off ina1
, not from the last commit indev
.