Getting started with Git - Part 5 - Protecting Branches

Other posts in this series:  Getting started with Git

You now have a solid foundation of the core concepts of Version Control with Git. We already have the basic workflow drilled in. This final part will cover a few important concepts that any Git master should know. Let's get started.

Protected Branches

Go ahead and fork and the repository we'll be using. As we've done this a bunch of times now, there shouldn't be any hiccups. Inside SmartGit's log view, right click on the done for part 5 commit and select Reset. We've tried resetting in part 1, this time we will do a soft reset. In a soft reset, the changes made after the commit that we've reset to, are not discarded but staged for the next commit. We can see that the remote master branch is now ahead of our local branch. Push the master branch as we have reset the local branch to a previous commit. SmartGit will prevent the operation, warning us that the push operation might result in commits being overwritten. Go ahead and modify the setting as told in the dialog box. We will, for once, ignore SmartGit's warning. Note: You can also do this from the command line by running git push -f origin master. The flag -f or --force forces the commit, overwriting the remote branch if needed.

Try to push from SmartGit again. SmartGit will ask for a confirmation, informing that the push will replace the remote branch. The push, however, will still not succeed. Why did the push fail even when we force it?

The master branch on Gitlab is "protected" by default. Any destructive operation on the remote master will fail.

You can check these settings in the Repository section in Settings on the repository page on Gitlab. Making master a protected branch is considered a best practice. You may chop and slice the other branches as you like, but you never cut-off the trunk (aka the master branch) of the tree.

By doing so, we ensure that, there will always be a way, to trace/find any commit pushed to the remote master.

Preventing overwrites is not the only utility of branch protection rules. You may also, disable pushing to a branch (forcing the use of pull requests), require code reviews before merging etc. These features can be further explored in the repository settings in Gitlab.

Note: Github can also be configured to follow the branch protection rules from the Settings pane in the repository. However, branch protection is disabled by default.

But what if I really want to remove a commit? Simple, we accept the faulty commit and do another commit after that, reverting the changes made in the faulty commit.

Reverting commits

Start with a pull operation. The local branch should now be in sync with the remote. Right click on the update README.md commit and select Revert. This will create a revert commit, which is an inverse of the faulty commit, undoing the changes made in the previous commit. We can now push the local branch. Gitlab accepts the push with the reverted commit. Think of reset as the sneaky way to undo changes, and revert as the upfront way. Revert does not rewrite history, but alters the current state by making a new commit.

Rebasing

Apart from Merging, there is another way to incorporate changes from a branch to another. Instead of making a merge commit, a rebase "moves" the branch itself, changing the commit that it emerges from to a different commit.

Confused yet ?

Consider this scenario, you're working on the dev branch and a critical bug in the previous release is discovered. This bug, as best practices dictate, is fixed in master. However, if the bug also affects dev, a merge commit will be made, merging from master to dev. Also, we've already learnt that at some point in the future dev should be merged back into master. Here's what that looks like.

  • bug fix in master
  • a bootstrap (feature) commit in dev
  • a merge from master to dev
  • another feature commit in dev
  • merge from dev to master

This process leads to a non-linear commit history polluted with several merge commits. Let's try rebasing instead and see if we can do better.

  • Reset master to Revert "update README.md".

  • Create a dev branch and make a commit in it.

  • Checkout master and make a commit in it.

  • Checkout dev again and make a commit.

The changes made in these commits don't matter. Making changes in the README.md file for this is a good idea.

This is what the commit log should look like. We have re-created the same scenario, with an important commit in master that's needed in dev. With dev branch checked-out. Right-click on master and select Rebase HEAD (dev) to. Let's go ahead with the green button ! This gives a cleaner commit log. It looks like the dev branch has been cut off a different commit than it was previously. The dev branch is now made up of entirely new commits.

The rebase operation moved the entire dev branch. Previously, dev originated from Revert "update README.md". But now, it originates from add intro in readme.

Rebasing is a great option, but only for local, non-public branches as it rewrites all the commits made in a branch.

Simpler language: Make sure that the dev branch (which we moved) is a local branch, not connected to a remote before trying a rebase operation.

Cherrypicking commits

In the dev branch, open package.json and add a line, "private": true. Commit this change.

{
  "name": "antenna",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "express": "^4.15.2",
    "socket.io": "^1.7.3"
  },
  "scripts": {
    "start": "node index.js"
  }
}

Quick question ! What do we do if we need this commit in master?

Simple, we'd merge from dev to master.

But, what we we only need this commit, and not the previous two commits (bootstrap.css) ? Maybe, the feature added in those commits is not complete yet, but the update package.json commit is needed in master right now?

Cherry picking

Cherry picking allows us to merge a single commit from a branch to another. With master checked out, right click on update package.json and select Cherry-Pick The Cherry-Pick & Commit option will commit the changes (made in this commit) into master. The Cherry-Pick option will bring the changes to the Working Tree from where, you can modify or add more changes before making a commit in master. As simple as picking cherries!

I hope this small series helped you in some way with Git. There is never a single way to do something with Git. Keeping learning and keep committing.

What next?

Stay tuned and keep committing. We will appreciate if you share this article, leave a comment or Subscribe now to stay updated through our newsletter. Also, check out our services or contact us at contact@attosol.com for your software and consultancy requirements.

© 2023, Attosol Private Ltd. All Rights Reserved.