Branching, Merging, and Rebasing with Git - aakash14goplani/FullStack GitHub Wiki

Overview In this module, we're going to talk about Branching, Merging, and Rebasing with Git. We're going to talk about how we can work with local branches. How we can stash changes that we might not want to commit right now but say for later. How we can merge branches together. Rebase commits on to another branch. How we can cherry-pick commits from one branch on to another. And also, how we can work with remote branches.

Visualizing branches In order to talk about branching and git, we need some way to visualize those branches. In the last module, we looked at the git log command with the graph oneline option which gives us a list of the commits on the current branch with a graph of those commits, they're in the left hand side represented by those asterisks. Now, we want to add a few more options to allow us to visualize branches. Adding the -all allows us to visualize all branches rather than just the current one and adding decorate applies any labels to the commits such as the HEAD label, tags, remote branches and local branches. You can see examples of all of these types of labels on the 2232 commit at the top of this log. Now, typing this out every time, we can get a bit cumbersome. So what we can do is we can add this as an alias in our git config. So, I'm going to say, git config and specify the global option, meaning that I want to add it to my .gitconfig in my home directory. I'm then going to say, alias and I'm going to give this alias a name, lga for log graph all. And then I can provide all the options that I want to use. I can omit that initial git and simply specify log with the graph option. I'll specify it oneline, all, and decorate. ( Pause ) With this in place, I can now run a git lga and it does exactly the same thing. If you want to take a look at how this is displayed in the git config, we can simply display the git config and you can see on the bottom there that new alias for lga has been added. So it's up to you. You can either add, use it using a git config command or you can directly edit that .gitconfig file.

Creating local branches Now that we have this in place let's go ahead and start adding some branches. So I'm going to decide, oh I'm going to add a new feature branch. So I'm going to say git branch and I'm going to make all this feature1. And I've now created a new feature branch which I can now checkout. If I do a git lga, you can see that I've got 2 branches, master and feature1, these are local branches and they are both point to the 2232 commit. Remember, branches are simply labels on the SHA-1 hashes of individual commits. Now, let's go ahead and I'm going to echo Feature1 and add it to the README. ( Pause ) If I do a git status, README is modified so I do a git commit -am, Added feature1. And if I now do a git lga, you can see that feature1 is now pointing to the 4D55 commit as well as HEAD. So, both of those are pointing to that particular location. You can see master is still pointing to the 2232.

Difference between branches and tags The big difference between branches and tags is that branches will follow the commits. As you add additional commits on that branch, the branch will move along. With tags, they always stay on the same commit. They're just a friendly name for that SHA-1 hash. So, let's go ahead and checkout the master. So, we've checkout the master branch again and if I do a git lga, you can see that HEAD is now pointing to the 2232 commit. There's a number of other things I can do. Let's say I want to do a fix. I'm going to call this branch, a fix1 and I'm going to do it off of a particular commit. I can do it off of the 974B commit. So, I can specify a commit to base it off of. More frequently, what you're going to be doing is you're going to be creating fix branches from well known tags or branches, or local branches. So, with this in place, I'm going to checkout fix1 and I will say echo, fixing bug, number 1234 and I'll just append it to the README. And I'll do a git commit -am and say, fixed bug1234 and if I do a log now you'll see that our branching structures getting a bit more complicated. We can see feature1 is pointing to the 4D55 commit. Master is still pointing to the 2232 but we got this new, fix1 that's pointing to the 5A78 commit.

Renaming and deleting branches Let's go ahead. I'm going to checkout master again and we've decided that we don't want to-- that fix1, really it was fixing bug1234, so we really want to call it bug1234. I can do this by giving the git branch and the move option. So to rename a branch, I can basically move it and I'll say, fix1 and I'm going to move it to bug1234. So, you can see that we've now easily renamed that bug. Now I decide, you know what I'm done with bug1234, I'm going to do a git branch and I'm going to delete that branch. Git prevents from me from doing this. It says, "This branch isn't merged, it hasn't been merged into master yet." So, it hasn't been merged into another branch. So we could-- we're going to loose that commit. If we really want to go ahead and do it, we can say git branch -D, bug1234 and by using the capital D, we're saying to git, for stat deletion. So I'm going to go, "Okay that is deleted." Now I might go ahead and say, "Okay, let's-- I want to work on another feature and I'm going to work on feature2. I'm setting on master, I'm going to git branch and say, feature2 and then I'm going to check it out. Git provide the faster way of doing this. I can do a git checkout -b which means create the branch and say feature2. I now go into echo, Feature2, on to README, git commit, Added feature2. ( Pause ) And we can see we now have feature1 and feature2 off of our master branch. So we can create branches, the feature branches very easily. They're local and only visible to-- and within this repository.

Recovering deleted commits Now, I might decide-- I might think just a second, I deleted bug1234 and I didn't-- I said, "Go ahead, delete it." I know I've got it and I realize, "Oh, no, I really didn't want to delete that." Although I have deleted it, git provides me a way of getting it back. If I do a git reflog, this is a log of all references where HEAD has pointed. So, if we look back in our reflog, we can see where the HEAD was changed to that commit for adding feature2, changing from masters to the feature2 branch. We can go all the way back, we can go back if you go to HEAD at 3. We see the 5A78 commit which is the commit to fix bug1234. So what I want to do is I want to git branch, I'm going to you say, bug1234 and I can specify that commit SHA, so I can-- 5A78C8B. If I do a git lga, you can see that bug1234 is back. I'd reapply that branch label to that particular commit. So it-- and I can now do a git checkout bug 1234 and I can even do a git show HEAD, which shows the addition to README.text of fixing that bug. Now I should warn you that git doesn't keep this dangling commits around forever. By default, git will keep them around for 30 days and after 30 days its garbage collection mechanism will clean up these commits. So you have to-- you can't sort of rely on this but usually what happen is, as we were working on feature, delete something and then realize, "Oh, you know what I really wanted that," you can use the reflog to get that information back.

Stashing changes I'm going to just switch over to feature2 and I'm going to start working on a new piece of it. I'm going to just echo Feature2 changes on to the README.text file. So I've made some code changes somewhere in my project. I've got things happening and someone comes along with the bug report, it's a production problem, I need to fix it right away. These changes that I'd made are not ready to be check in to the feature2, they're half way through, half thought ideas. But I don't want to loose them. I can do-- save this work off using a git stash. So, I'm going to do a git stash and you'll notice that those pending changes on my working copy have been rolled back. If I-- look at the README file, it doesn't have that additional feature2 changes added to that file. If I do a git stash list, those changes are in my git stash. This is a little holding area for pending changes. So, I can go off, I can git checkout bug1234, fix the bug, get things working so I can do a git. I can echo another fix to bug number 1234 to the README.text file, commit that ( Pause ) and then I can checkout feature2 again and then do a git stash apply to pull those changes back. If I cat the README file, you can see the feature2 changes is now reapplied to that file. This is done recursively so that stash could be deeply nested to changes to README files, code files, resources what have you. They've all been rolled back and now I can reapply them. If I do a git stash list, you can see that stash is still on the list. Now let me do a git reset hard back to the HEAD to toss it those changes. So if I do a git status, you can see that that working copy is clean. And catting the README file, I no longer have feature2 changes, the end the file. There's another command that I can use which is git stash pop. This pops, the top item off of the stash and applies it to my current working copy. So it's exactly the same as apply, the only difference is that it removes it from my stash list. Let's re-stash things, and let's say I made another change so I'm going to echo, more changes and I'm going to append or I'm going to add a new file called AdditonalFile.txt. If I run a git status, you can see I've got that AdditionalFile, I'll git stash that as well. First add that to my working copy so that git actually knows about it. And if I git stash this, you'll notice that my working copy is once again clean and my git stash list, I've now got 2 pending changes that are part of the-- that are in the stash. Now, if I decide I don't want, let's say I'd decided that AdditionalFile, I don't really need it, I can do a git stash drop. And it drops the reference to that stash, running git stash or stash list again, you can see I've only got one pending change. I might decide that these additional features on feature2 really need their own branch so I can do a git stash branch and give it a new branch name such as feature2_additional and it's going to create that new branch, check it out and apply the stash to it. Doing a git stash list, you'll notice that it popped that change off of the stash. I can now run a normal git commit -am, added additional features to feature2. And doing a git log, you can see that that has been added to that feature2_additional branch. So stashing is a very useful way for as a temporary holding area for changes that are not ready to commit to a branch but you don't want to loose.

Merging branches Let's go back to master and we're going to take a look at merging. Let's see our current branch structure. You can see that we've got this branch going from master. You can see our HEAD is currently at the 2232, and we've got feature1 setting over here at 4D55. We've got some bug fixes that are branch off from the earlier one and then we've got these two feature2 and feature additional branches. So let's decide, let's say we decided that it's time to merge in feature1. So I'm going to do a git merge feature1. You'll notice that this was a fast-forward merge because git was basically able to take this master label and just move it to feature1, it didn't have to merge any files, it could just literally move at label to a new location. But in the log command, you can see that that exactly what it did. Now that that feature is merged I can do a git branch -d, it's a little d, feature1, git knows the feature1 already been merged so it's safe to delete this label, we're not going to loose any commits, I don't need to use the dash capital D option. So that is gone. And we've done a fast-forward merge, we now have new changes that include feature1 integrated into master. Now, let's say that we want to merge in that feature2 additional branch, so get merge, feature2_additional, and there are merge conflicts. So, let's take a look at the merge conflicts in README.text. Git has added this standard merge notes in here, saying this is what the HEAD looks like. And here is what was in feature2_additional. So there were changes to the same lines and git doesn't know what to do with this. So we can either resolve this merge manually using our text editor or we can use merge tool. Merge tool allows us to use a variety of different tools for performing the merge. I've gotten the-- I've got the setup with kdiff3 which is a three way merge tool that is available for Windows, Linux and Mac OS. So this is a three way merge. We've got in the middle here our local copy which is the current branch, as you can see its got feature1 on it which is where we are at now. We've got the remote branch, the branch that we're trying to merge in, that feature2_additional and the base is the common commit that both of these come from. So you can see that we're trying to change the same line and add both feature1 and feature 2, and that's were we have this merge conflict. We can right click and then select. Do we want the lines from the base version, version 2, or the local or the remote version? So I'm going to go ahead and pick version 2 to get feature1 and then I'm going to just copy in the changes, from this one. So we want to merge it, probably look something like this, there are other merge tools such as Beyond Compare which works for both Windows and Linux but it's not available for Mac and it allows you to say, "I want the changes from the first branch followed by the changes from the second branch or vise versa or one branch with the other." It's a much more full feature tool. So I got these changes in here now. I'm going to save off those changes and quit the merge tool. And if I do a git status, you can see that I've got a modified README.txt file. I've also got this .orig file kicking around which we'll need to clean up. Now, what we wanted to do is do a git diff cached, cached asks git to compare the repository to the staging area. So you can see that the-- we've added these 2 lines and we're ready to commit. So now that we've resolved this merge conflict, we do a git commit -m, merged feature2_additional into master. ( Pause ) And now that we've done that, we can remove the README.txt.orig file because it's no longer necessary. And, so we've seen a fast-forward merge and we've also seen dealing with merge conflicts. ( Pause )

Rebasing changes Let's take a look and say we're working on a new feature, feature3 which is base off of that tag v1.0. So I'm going to do a git branch and I'll call this feature3 and I'll base off the v1.0 tag and I'll do a checkout of feature3 and let's go ahead and edit file1.txt. And I'll say, adding yet more code. And I'll do a git commit, Added feature3, and if we look at our log you can see that we've got this new feature3 that is base off of the origin master or this also the same at the moment as tag v1.0. Now, I might decide that, you know, git show feature3. I'll really don't want to do a merge which is going to result in this intertwining branches. I've pulled down, may be I've pulled down this feature2 from another coworker and why I want to do is I really want just take the changes I've made in feature3 and replay them. Move that commit to make it look like it's always been off of the master branch. I can do this using a rebase. I can say, I'm right now on feature3 and I'm going to say, rebase this branch onto master and basically putting it off its current location and relocate it on top of the master. It was able to-- git was able to just move this over because there were no conflicting changes. So let's take a look at our graph now, and you can see that, that feature3 has just been added on top of master. Now if I, checkout master and do a git merge of feature3, it's going to be a fast-forward merge because it can once again just move that master label 2.2 the same SHA-1 hash revision as feature3. Now rebasing doesn't always go as plan. Let's say that we decided to rebase the bug1234 fix on top of the master branch as well. So let's go ahead. I'll git checkout, bug1234 and I'll do a rebase on to the master branch. So I try to replay these changes but there's been a merge conflict taking and look at the README.txt file were the merge conflict occurred, we can see that we've got these set of changes from the HEAD but we're trying to apply this line 11 change to the same line as line 4. So git doesn't know what to do. We can use the same technique, I can run git mergetool and I'll use kdiff3 again. And we can say that, "Okay, we want to use those lines," and then I'm going to just simply copy and paste. Let's say it belongs at the middle here. I'll just paste it in, save that and quit. Looking at the git status, we've got this README.txt changes stage. So if I do it, get diff cached same as when we were doing a merge, we can see these additional lines that were inserted when we are fixing the merge conflict. I can now do a re-- git rebase continue, to continue the merge and it notes that we've got additional conflicts. So, let's go ahead and look at these additional conflicts because we're basically replaying. We do a git lga. We're trying to replay this first 5A78 change. Over top that resulted in a merge conflict and now we're trying to replay the CDDE change and that resulted in a conflict. So I'll do the same technique. I'll do a git mergetool. ( Pause ) And I'll say, "Okay, what I want to do is I want to take the changes from B." And then apply C. Actually those changes I can just apply the C changes in this case. ( Pause ) So I will go ahead and save these changes. So I've resolved my merge conflicts, I can quit this, I can say, git rebase continue and it's done applying that bug fix. If I do a git status, you can see that the working copy is dirty because it left the stranded orig.file kicking around so let's remove that. Let's look at the graph and you can see that, originally the changes were off of this bug fix down here. But we've rebased them, we've moved these changes up to off of the master. So it's now, those changes have been replayed on top of master and if I git checkout master and do a-- I now do a merge, a bug1234, rather than merging this 2 branches, I'm going to do a fast-forward merge and I now have the HEAD pointing to the right place. I can now do some clean up, I can do a git branch -d on feature3 and do a git branch -d on bug1234 because these have been merged into master, I no longer need them. If I wanted to, I could also remove feature2 and feature2_additional, 'cause these are just labels to particular commits. And you can see as you have the branching behavior, I can see how those different commits were merged into the 2790 commit but you'll notice that this bug fix, these 2 bug fixes were-- when we rebase them are simply replayed on top and now our HEAD is pointing to master. ( Pause )

Cherry-picking changes So we've seen merging and rebasing. Let's take a look at another technique. So let's say we were working-- I'm going to do a git branch and I'm going to call this v1.0_fixes and I'm going to base it on the v1.0 and I will checkout v1.0_fixes. So I'm going to make some fixes here. So I will-- fix1 and I'll append that to file1.txt and do a git commit -am, ddded fix1. And now I'm going to make another change, I'm going to echo fix2 to file2.txt and do the same thing here. ( Pause ) And looking at the history, you can see that we've got this 2 fixes, fix1 and fix2. So I'm working on some bug fixes to this and I realized that I need one of this fixes on the master branch. So let's checkout the master branch. ( Pause ) Now I can't do a-- I don't want to do a merge because I really don't want to merge that change, fix1 and fix2 into the master. All I want to do is I want to grab that one fix, fix1 and apply it to master. A rebase also doesn't work because I'll be applying that whole piece. The other challenge is although I could in this case do a git rebase or merge on strictly that commit, I might have-- that commit might may buried in amongst, a whole bunch of other ones. I really want that one commit in amongst, a whole variety of them. I can use a technique called git cherry-pick. Git cherry-pick allows me to select one single commit and apply it. So I'm going to grab the 6FA4324 commit and cherry-pick it on the master. If I've looked at my log, you can see that I've added fix1, it's taken that one commit and applied it on the master. It's not done in rebase, it's not done in merge, it's just added it on. So cherry-picking is a very convenient technique from moving patches if you've applied the patch to a maintenance branch, reapplying those same patches to other branches such as your master is a very-- it's very easy in git because you're just taking it certain commits and applying them to different places in your treat. Now git does keep track of which commits have been applied. So if later on I decide to merge the fixes. I'll do a git merge and I'm going to merge in the v1.0 fixes, it's going to ask me for a merge message here. I'll just say sure that's look-- that looks good. It will notice that earlier fix was already added. So it will notice that 6FA4 commit was already added. It won't try to reapply it. So if I look at file1, you'll see just the one fix1, it won't have do, it won't add a second fix1. So git's very good about merging and rebasing. In cherry-picking, it knows where the history came from which commits have been applied on which branches and it can safely apply just the ones that haven't been done yet. So I look at file2, so those changes are there but it's not-- it hasn't duplicated that fix1 line twice by reapplying that merge a second time.

Creating a remote branch All right, so let us talk a bit about how we can work with remote branches. So here we've got the origin master, it's far behind the current HEAD. So if I just do a normal git log, you can see all the changes that are pending. If I do a git status, you can see that we're on the master branch and we've got pending changes that need to be applied. So how do we go about, so let's say I want to do a git fetch from our master branch. So I'm going to fetch from origin master. ( Pause ) So I don't have any pending. So nothing came down. I don't have any additional changes. I might have but in this case, since I'm only working on this one repo, I don't have any changes. So I now want to push my changes backup to origin master. So I can do a git push origin master or because this is being tracked I can simply do a git push. It's going to push those changes up. And looking at the graph, you can see that the origin master remote-- remote branch has now been fast-forwarded to the current master. If we come over and take a look at GitHub, let's refresh the browser. You can see that we've got a tag, we'll switch over to the master branch but there is only that one branch and that branch has all of our changes in it. And you can see the latest commit message, merge branch v1.0_fixes the same as over here. Now what I'd like to do is I'd liked to expose the v1.0_fixes branch. So how can I do that? I can do a git push, I'm going to push to the origin and I'm going to push the v1.0 fixes. So I'm going to give the name of the local branch. It's going to create a new branch coming over to GitHub. We can see that we now have a new v1.0_fixes branch. So this is a way that we can push changes up to our remote repository. If we do a git branch -r that will list the remote branches. So you can see we've got both the master branch, an origin and the v1.0_fixes branch.

Deleting a remote branch Let's say, that we want to delete that remote branch. We decided we don't want to-- we don't need it anymore. Deleting remote branches because people might have based changes off of them, is something you should to with caution but it is possible. So here I decide that v1.0_fixes branch is no longer necessary and I want to delete it. Git makes these rather corky. Let's-- before we delete this remote branch, let's see how we actually push a remote branch. So what I'm going to do is I'm going to git-- do a git push and I'm going to push to the origin. So that is the name of the remote and I'm going to push v1.0_fixes, this is the local branch name. If we don't specify anything for the remote branch name, git assumes that it's the same as the local branch name. If we want to specify something different, we put a colon and we say, v1.0_fixes_remote_branch_name something really obtuse and off it goes, and it pushes that local branch to a new branch called v1.0_fixes remote_branch_name. Refreshing over in the browser, we can see that new branch name. So if we want to delete a remote branch, we'll going to push to origin and we want to push nothing. So we leave the local branch name empty. Put the colon and say, v1.0_fixes_remote_branch_name. I hit tab and it'll automatically failed in the full name which is origin. The origin isn't actually necessary. ( Pause ) And it is failed but the origin in there. That is a problem with the completion. So I'm just going to push that remote branch name and you'll note that it is deleted. So I can do the same thing, git push origin v1.0_fixes and that will delete that remote branch from GitHub. So let's refresh here and you can see both of those remote branches have now been deleted. So deleting remote branches is kind of corky in git but it's straight forward. You just have to git push origin and then not specify at the local branch name. Leave it empty, put a colon and then specify that remote branch name. ( Pause )

Summary In summary we have looked at working both with local and remote branches, stashing changes and also managing your history through effective uses of merging, rebasing and cherry-picking.