Git yourself some cool new tricks

Git yourself some cool new tricks

Hello friends!

This time we'll be talking about some various ways that we can use to do common tasks with Git on the terminal.

We'll understand more about Git functionality and also add some new tricks to our toolbox when using it to manage changes in our local workspace and in GitHub (or GitLab, Bitbucket, etc.)

So if you are ready to get started, let's dive right into it!

Diving into a pool

The basics

For most of us, we are already familiar with the common workflow of git which is something like...

Pull latest changes from remote repo, work on the code, add our changes, commit them and push them to the remote repo

Since normally the bulk of the work doesn't happen in the master branch, we use feature branches which can be created with the command

git branch {branchName}

and then we can move to the recently created branch, or we could use everyone's favorite shortcut

git checkout -b {branchName}

Using branches it's when Git becomes more interesting. The branches can exist both in the remote repo and also in our local copy of it

Normally, we create the branches locally and then push them to the remote repo although we can also create them the other way (i.e. from the remote and them pull them locally.)

Now, how do we know what branches exist and whether or not they have a matching remote branch?

First let's make sure that we actually have a remote repo linked to our local one. We can check that with the git remote command.

Although it's more useful when we use the --verbose flag or -v for short like so

git remote -v

Once we know we have a remote repo, we can see the existing branches on our local with git branch which will list all branches created.

git branch

We can get more info about them with the -v flag that will show us the id and name of the last commit made on that branch.

git branch -v

And we can get even more info on the branches with the -vv flag that will show us not only the info of the last commit but also the name of the remote branch the local one is pointing to (if it has one.)

git branch -vv

Taking things up a notch

Now, we know how to create branches and move to them once created.

In order to navigate between different branches we would have to use the git checkout command and the name of the branch.

Though there's a nifty little trick to navigate back and forth between two branches without having to write their names every time. To do so, use the command

git checkout -

Once we make changes to a file, we can save it to the working directory, add it to the staging area and then make a commit before pushing it up to the remote.

There are several things we can do once we have changes committed to the Git history (like being able to recover changes if something unwanted happens for instance)

The standard command for that is git commit which if used alone will open the Git default editor so that we can introduce a commit message.

You can also tell Git which editor you want it to use as a default by running the command

git config --global core.editor "your_editor_of_choice"

In my case I use Vim (the grandfather of all text editors) but you can use nano, emacs and even VSCode.

Now, let's assume we created a commit with a generic message (because we were trying out the editor config) and now we want to change that message for something more descriptive. To do that, run the command

git commit --amend -m "More descriptive commit message"

And now a new commit will be created with the message we provided, we could now be ready to push our changes to the remote repo...

But... did you know that the amend flag also works to add additional changes that we may have forgotten and should've been part of the previous commit?

In that case, we have to add the changes before amending the commit with git add .or git add -A which is the shorthand for --all .

Difference between git add . & git add -A

The difference relies in that git add . only adds files in the current directory where you're located whereas git add -A adds the files in the current and in any subdirectories as well, hence the "all".

Ok, we added the necessary files, committed them and now we're ready to push them to the remote. We do so with the command

git push origin {branchName}

If it's the first time we're pushing this branch to the remote, we can pass in the --set-upstream flag or -u for short like so

git push -u origin {branchName}

And now, every time we want to push new changes from that same branch we can just run git push and Git will know what remote we're talking about.

We can now see the commit history in the remote repo navigating to the URL we see when running the git remote -v command.

Undoing changes pushed to a remote

Now let's assume that we pushed the commit to the wrong branch (maybe because we were so busy paying attention to the difference between the flags to pass to git add...)

Don't worry we can fix that issue with the git revert command.

Suppose we have the following history in our git log

git log

And we want to remove that last commit from the branch that is on and maybe move it to a different one.

In that case, we would run the revert command like this.

git revert 0a995a8

And now Git will create a revert commit with the changes of the previous commit undone.

(You could also have used 'HEAD' instead of the id of the commit with the same result, but I think it's easier to understand what Git is doing using ids instead of pointers to commits.)

What's left to do now is to push this new revert commit to the remote (and if you work with others, tell them to do a pull so they can get the fix.)

That wasn't too bad, was it? although if we want to be more cautious for next time, we can avoid doing a revert commit and handle the commits locally.

Moving a commit from one branch to another

Let's see how we can move a commit. There are a couple of ways of doing it, like most things in Git.

Let's imagine that we are in the 'feature/about' branch and we found something to fix in the contact section and committed it there.

We'll now want to move that commit to a different branch, like 'feature/contact'. For that let's run the command

git checkout feature/contact

Now we're in the contact branch and if we inspect the log, we'll see that the commit is now in both branches and what's left is to remove it from the first branch.

So now let's go back to the previous branch (with git checkout - remember?) and we'll take the id of the commit that's before the one we want to remove and run the command

git reset --hard {commitId}

We'll get a message from Git saying that 'HEAD is now at {previousCommitId} {previousCommitName}'.

So essentially the HEAD pointer moved back one commit and it's like that latest commit was never created in the first place, but it still exists in the branch that we want it to be.

Another option to do it is to use the git cherry-pick command.

For instance, if we're in the 'feature/about' branch, make note of the commit id (the one we want to copy) and then move to the wanted branch and run the command

git cherry-pick {commitId}

Now the changes of the commit from the other branch will be applied to the branch that we're on.

Note that cherry-pick applies the changes of the selected commit by creating a new commit on the branch instead of copying the entire commit.

We'll now have to go back to the other branch and remove the commit from there like we did before.

Also note that git cherry-pick works if we want to take one commit from a branch and copy the changes into another branch whereas with git checkout we're taking all the recent commits and moving them to the other branch.

With that, we have moved a commit to its corresponding branch before pushing to the remote which makes things easier to deal with locally.

Getting into more advanced stuff

We'll now be looking into how to handle commits across different branches and remotes with some of the extra firepower that Git gives us.

But you know what? I just looked through the whole article and realized that's it's getting quite long and unwieldy.

So I'll be saving the advanced stuff for a Git tricks part 2.

In that one we'll be looking at things like...

  • The different options that git reset has.
  • How to recover commits that may seem lost with git reflog.
  • Doing forced pushes with caution using --force-with-lease instead of --force.
  • And how to manage changes, commit messages, and more with git rebase.

So, there you have it, some extra tricks that you probably didn't know when using Git for the traditional software development workflow.

I hope this has been useful, I know a couple of years back I'd be using Git without understanding most of its power and just limiting myself to the most used commands and nothing more.

And now that was it for this week's post!

Tom Hanks waving

T Hanks for reading so far and I hope to see you the next week.