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 ?
CodePudding user response:
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.
CodePudding user response:
(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.)
I am working on dev branch which is a parent branch ...
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.
... and created something on branch a1 added new functionality completely independent
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.
and pushed to a1 only.
Things get more complicated here. These names—dev
and a1
—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 own a1
and their a1
are both spelled a1
. 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 name a1
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:
Now i have another task created a new branch b1 but as soon as i switch to dev i do not get code of a1
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:
$ git cat-file -p e188ec3a735ae52a0d0d3c22f9df6b29fa613b1e | sed 's/@/ /'
tree 23abcae16bcca7bf541eb62d2a0b2c1ae7f5a2a9
parent 21dd13e025aaada474fae6014f1b14799e37bedf
parent a0feb8611d4c0b2b5d954efe4e98207f62223436
author Junio C Hamano <gitster pobox.com> 1663097028 -0700
committer Junio C Hamano <gitster pobox.com> 1663097028 -0700
Sync with 'maint'
(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 these tree
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 use git switch
or git checkout
to select that commit.
(This particular tree
is pretty big:
$ git cat-file -p 23abcae16bcca7bf541eb62d2a0b2c1ae7f5a2a9 | head
100644 blob 4860bebd32f8d3f34c2382f097ac50c0b972d3a0 .cirrus.yml
100644 blob c592dda681fecfaa6bf64fb3f539eafaf4123ed8 .clang-format
100644 blob f9d819623d832113014dd5d5366e8ee44ac9666a .editorconfig
100644 blob b0044cf272fec9b987e99c600d6a95bc357261c3 .gitattributes
040000 tree 700f96ea68cfa31767d24659c4981083f65fd276 .github
100644 blob 80b530bbed2c80814ac74956d329d277d85bba86 .gitignore
100644 blob cbeebdab7a5e2c6afec338c3534930f569c90f63 .gitmodules
100644 blob 07db36a9bb949c4c911d07baeb1c4c10c13bec4c .mailmap
100644 blob 5ba86d68459e61f87dae1332c7f2402860b4280c .tsan-suppressions
100644 blob 0215b1fd4c05e668f37973549384aae24dcc65cf CODE_OF_CONDUCT.md
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:
- it's a numbered entity (using the unique hash ID as its number);
- it holds metadata saying stuff like who made it and when; and
- it holds a full snapshot of every file, but in an indirect and space-saving manner.
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
and a1
. At some point, there was some commit with some hash ID that was the latest commit on your own dev
. I don't know what that commit's hash ID was (was it a123456...
or b987654...
or deadcab
or beeffad
or what?) so I'm just going to call it commit H
, for Hash. And I'm going to draw it like this, for no obvious reason as yet:
<-H
That little arrow sticking out of H
, pointing leftwards (backwards), represents the parent
line in commit H
's metadata. This parent
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 it G
, which is the letter before H
, and then I will draw that commit in:
<-G <-H
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:
... <-F <-G <-H
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 your dev
branch. You can do the same thing very easily: just run git log dev
, for instance, or git switch dev
and then git log
. Either way, Git will show you the metadata from H
, then move back one step to commit G
and show you the name and email address and log message for G
. Then git log
will move back one step to commit F
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 commit H
, so what will I do? What I will do is use your name dev
, which you told me about. By using your dev
I can find hash ID H
.
We say that your dev
points to commit H
, and I can draw that in now:
...--G--H <-- dev
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 name a1
select commit H
, just like your name dev
selects commit H
:
...--G--H <-- a1, dev
All of these commits are now on two branches instead of just one. Commit H
is the latest commit on both a1
and dev
. Nothing inside H
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 to H
.
If we now use git switch
or git checkout
to select the name a1
, this tells Git that we'd like all the files from commit H
. The name a1
points to H
, so that's where Git gets this idea that we want H
's files. To show that we're "on" branch a1
, I draw it like this:
...--G--H <-- a1 (HEAD), dev
You now make some changes to some files and git add
and git 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 "commit I
" here, and draw it in now:
...--G--H <-- dev
\
I <-- a1 (HEAD)
The name a1
now points to commit I
. No other name has changed; no commit has changed;2 but name a1
now selects commit I
and commit I
is therefore the last commit on branch a1
.
This is by definition! If we run git reset --hard HEAD^
to discard commit I
, what happens is that Git stores the hash ID H
back in the name a1
, so that commit H
becomes the last commit on branch a1
. Nothing actually happens to commit I
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
but as soon as i switch to dev i do not get code of a1
The name dev
in your Git repository means "the most recent commit on my dev
branch". That commit has a snapshot, and that's the snapshot you get when you git switch dev
.
If you don't want that snapshot, you have only two options:
- don't use the name
dev
, or - make the name
dev
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 commit H
(whatever H
'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 name a1
, so that your new branch selects the same commit, like this:
...--G--H <-- dev
\
I <-- a1 (HEAD), b1
You can now git switch b1
and you'll switch from commit I
, using all of commit I
's files, to ... commit I
, using all of commit I
'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 and git add
and git commit
as usual:
...--G--H <-- dev
\
I <-- a1
\
J--K <-- b1 (HEAD)
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 branch b1
pointing to the current commit and then use git reset
or git branch -f
to force the name a1
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:
...--G--H <-- dev
\
I <-- a1
\
J--K <-- b1 (HEAD)
there's no branch relationships at all. The name dev
selects commit H
. That's what it does, and that's what it's for. The name a1
selects commit I
. The name b1
selects commit K
. There's no "parent of name b1
" at all.
(Branch names do have an optional upstream setting. The upstream of dev
is probably origin/dev
, and the upstream of a1
will be origin/a1
and the upstream of b1
will be origin/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 of H
, which means that H
is the (note the, not a) parent of H
. J
is a child of I
, which means I
is the parent of J
.
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 always I
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 name br3
:
...--G--H <-- dev, br3
\
I <-- a1
\
J--K <-- b1 (HEAD)
and then switch to br3
:
...--G--H <-- dev, br3 (HEAD)
\
I <-- a1
\
J--K <-- b1
We now have the files from commit H
. If we change some files and commit, we get:
L <-- br3 (HEAD)
/
...--G--H <-- dev
\
I <-- a1
\
J--K <-- b1
Commit H
now has two children, I
and L
. But I
still has just one parent H
, and will forever, because I
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 it J'
and make its parent be I
, using a temporary branch name, fixup
:
...--G--H <-- dev
\
I <-- a1
|\
| J' <-- fixup (HEAD)
\
J--K <-- b1
Now let's copy the effect of commit K
to a new commit that's otherwise just like K
, but has J'
as its parent:
...--G--H <-- dev
\
I <-- a1
|\
| J'-K' <-- fixup (HEAD)
\
J--K <-- b1
Now let's force Git to have the name b1
point to K'
, and then switch to branch b1
:
...--G--H <-- dev
\
I <-- a1
|\
| J'-K' <-- b1 (HEAD), fixup
\
J--K ???
Note that there's no name for commit K
any more. We can't find commit K
. Commit K
was how we found commit J
and we can't find K
so we can't find J
either. But the name b1
now finds K'
, which finds J'
, which finds I
just like J
found I
. If we delete the temporary branch name we get:
...--G--H <-- dev
\
I <-- a1
|\
| J'-K' <-- b1 (HEAD)
\
J--K ???
and as long as we have forgotten what the actual hash IDs were for commits J
and K
, it sure looks like we changed commit J
. 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 branch dev
, just start your new branch b1
from the tip commit of branch a1
.
Also when exactly merge comes into action ?
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 on b1
, you'll need to start from where you left off in a1
, not from the last commit in dev
.