1

I have a feature branch (branch1) that was approved but not yet merged into to master (have to wait for deployment CI to be fixed). I want to get started on another branch (branch2) which needs branch1's changes in it. Would it make more sense to create my branch2 off of master and then merge in branch1?...or just create branch2 off of branch1?
Is there any difference between these approaches?

SwissCodeMen
  • 4,222
  • 8
  • 24
  • 34
  • Does this answer your question? [Creating Git branch from another branch or from master?](https://stackoverflow.com/questions/65637443/creating-git-branch-from-another-branch-or-from-master) – SwissCodeMen May 13 '21 at 21:31

4 Answers4

1

Consider this history:

-A--B--C--D   <- master
    \
     \--E--F  <- one

You need the changes made in E and F, so creating a branch from one is the correct choice here. If the commits C and D did not exist, branching off master and then merging one is the exact same:

-A--B--C--D      <- master
    \
     \--E--F     <- one
            \
             \-  <- two

However, iff you need the changes in C and D too you will have to merge these two branches. This can either be done by branching off master and merging one into it or the other way around. Keep in mind though that may create merge conflicts that you are not equipped to solve and it is probably better to wait for one to be merged into master first by those that are responsible for that.

-A--B--C--D--     <- master
    \        \
     \--E--F  \    <- one
            \  \
             \--M  <- two (now contains all changes from A to F)
perivesta
  • 3,417
  • 1
  • 10
  • 25
0

If you need those changes in branch1 as you said, you should create branch from there or you can end up with need of cherry-pick or re-implement the logic.

There is no harm in it, branch is already approved. Just make sure it will be merged before others that can conflict with. Even-so you can cherrypick the solving conflict commit.

Baran Bursalı
  • 360
  • 1
  • 7
0

It is fine starting from the other branch. Even from unapproved branches. The only thing is that you have to be a little more careful when doing operations like rebasing (specially if using an unapproved branch). The trick is that you should assume when doing this trick that your base branch can change (not in your case, it will be merged, right?). The base branch could be rebased, reordered, amended or basically anything.

If, for example, you choose to rebase your branch, then you have to be careful not to move along the changes that your base branch brought over. So it might mean that you have to do something like:

git rebase --onto some-new-base tip-of-your-base-branch your-branch

(This is the tip of the base branch as you have it in your branch, not the new tip if that branch has been rebased, for example)

This way you make sure that you are only moving your changes along and not moving any of the stuff of the branch that you used as your base.

eftshift0
  • 26,375
  • 3
  • 36
  • 60
0

Branches don't really branch off branches. In fact, in an important sense, the word "branch" is irrelevant. It's so badly overused that it loses all meaning: if you branch your branch from a branched branch, what are we even saying? We need to get back to reality and not attempt to parse sentences like Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo. So, here's the key.

In Git, what matters are commits. Commits have big ugly hash IDs. These are their "true names": if you use the hash ID, your Git will find that commit, if it has that commit at all. If you don't have that commit in your Git repository, you can hook your Git up to another Git that does have that commit, and get it, and then you'll have that commit, with that particular big ugly hash ID. These commit hash IDs are unique, and yet are the same in every Git everywhere.1

A branch name, no matter what it is, is just a way of not having to memorize the big ugly hash ID. To see how this works, run git rev-parse on your branch names:

$ git rev-parse master
7e391989789db82983665667013a46eabc6fc570

That big ugly hash ID is the latest commit on (my) master (in this particular repository). Your hash IDs will of course be different.

Each commit stores two things:

  • A commit has a full snapshot of every file, as of the time you (or whoever) made the commit. This is stored in a special Git-only format, with files de-duplicated in the repository, so the fact that when you make a new commit you've only changed a few files means that the new archive snapshot is actually mostly shared with all the old archive snapshots: the repository stays pretty slim.

  • And, each commit stores some metadata, or information about the commit itself: who made it, when, and so on. Your log message goes in here too, and there are actually two person-email-timestamp items (for "author" and "committer"). You don't need to care much about this part: Git handles all this stuff for you automatically, except that you have to write a commit message for Git. But you should know that, in this automatically generated section, each commit stores the hash ID—the big ugly string of letters and digits—for some earlier commit or commits, too.

Most commits store just one earlier-commit hash ID. This forms the commits into a backwards-pointing chain, which we can draw like this, with the latest commit at the right:

... <-F <-G <-H

Here the letter H stands in for the actual big ugly hash ID, whatever it might be. As long as master holds that hash ID, we can just use the name master to have Git find commit H. Commit H itself holds a snapshot plus metadata, and the metadata holds the hash ID of earlier commit G.

Commit G, of course, holds a snapshot plus metadata too. The metadata in G holds the hash ID of still-earlier commit F, so by starting from H and working backwards to G, Git can work backwards to F. This repeats, over and over, until Git gets to the very first commit ever. That one is a little bit special: it doesn't point backwards to any earlier commit, since it can't. This lets Git finally stop.

The commits Git visits, by doing all this backwards commit following stuff, are the history. That's where everything is, in your repository. The commits themselves are all strictly read-only though, and archived, so to get work done with files, you have to use git checkout or the newfangled (Git 2.23 and later) git switch.

You give these commands a branch name and they use that to find a commit and then check it out. This extracts all the archived files into a work area, which Git calls your working tree. Then you work on them and get work done.

You can also have Git create a new branch name at any time, pointing to any existing commit. When you use master, which points at (say) commit H, to make a new name, you get a new name that also points to commit H, like this:

...--G--H   <-- master, newbranch

You then git checkout newbranch (or use git checkout -b to create and switch to it; or if you're using git switch, git switch -c to create and switch to a new branch name like this). Git sees that you've asked to move from commit H to ... commit H, and doesn't actually do anything to your working tree, but does create the new branch name, and start using that one.

To denote which branch name we're actually using, let's attach the special name HEAD to one of them, like this:

...--G--H   <-- master, newbranch (HEAD)

This means we're using newbranch to find commit H.

If we now make a new commit, Git will allocate a new unique hash ID for the new commit,2 which we'll just call I. New commit I will point back to existing commit H:

...--G--H
         \
          I

and now Git does its special trick: it updates the name to which HEAD is attached so that it points to the new commit. That is, after this git commit, we get:

...--G--H   <-- master
         \
          I   <-- newbranch (HEAD)

As you continue to make new commits, they get added to the end of whatever branch you're "on" (git status says on branch newbranch so it's the name newbranch that gets updated):

...--G--H   <-- master
         \
          I--J   <-- newbranch (HEAD)

When you're ready to submit this, you will have your Git send your commits—with their unique IDs—to some other Git repository, and store a label in that Git, on the latest such commit, so that they—the other Git—can find the commits in Git's usual backwards fashion, and open the pull request (assuming GitHub and one of the two typical work-flows). So now they have commits I-J too, and a name—maybe newbranch, or maybe refs/pull/123/head, or something. (Their name does not have to match your name: their name is in their Git repository! Humans like to use the same names to avoid confusion, but sometimes this idea misfires, because the two Gits don't necessarily stay in sync. It all depends on who does what, where.)

Anyway, if you now need to create another new branch to keep track of the latest commit in the other new branch, you have to pick some starting commit. You can easily pick commit H because your name master finds commit H. Or, you can easily pick commit J because your name newbranch finds commit J.

If you pick commit H, though, you're telling your Git: Remove all my working tree files that go with commit J. Extract, instead, all the archived files from H. This of course gives up the work you just did in newbranch, setting your working tree up the way your master says via commit H. That is:

$ git checkout -b feature2 master

results in:

...--G--H   <-- feature2 (HEAD), master
         \
          I--J   <-- newbranch

You're now using commit H again via the name feature2.

If you were to run git merge newbranch at this point, Git would by default perform what Git calls a fast-forward.3 The result looks like this:

...--G--H   <-- master
         \
          I--J   <-- feature2 (HEAD), newbranch

which is the exact same thing you'll get if you start feature2 at commit J right away, with git checkout -b feature2 newbranch for instance.

So, if you are going to do this, use the shorter method to start out at commit J right away.


1This is why they are so big and ugly: so that all of your commits for your project have a unique hash ID, never to clash with any other commit from any other project anywhere, ever. Technically, it's OK for your Git to re-use a Git commit hash ID from, say, the Linux kernel, as long as you never hook your Git to a Git repository for the Linux kernel—but to make sure that your Git never re-uses a hash ID from any repository that you will hook up to, we just have Git work to ensure that they are all unique.

2This is where the real magic of Git is hidden. The current method is to use the SHA-1 cryptographic hash function. Git is moving to SHA-256 instead, as SHA-1 is no longer all that great, but this will take a while.

3Parts of Git documentation call this a fast-forward merge, but it's not a merge at all. It's just a checkout that moves the branch name!

torek
  • 448,244
  • 59
  • 642
  • 775