My Ultimate Git Guide for Developers
My Ultimate Git Guide for Developers
It never hurts to remember the basic Git commands, and today is that day. In this article that I wrote for my team at one of the companies where I worked, I'll show you how we worked with this version control system on a project that involved six people and almost two years of development.
What to Know About Branches?
I usually work using GitFlow, where we have a main branch, a develop branch, and others that can be feature/ or hotfix/.
The flow is always the same: from develop, new branches are created to work locally and then merged back into develop (remote). When we have reached our goals, develop is merged into main to go into production.
If you are new to Git, I recommend that you first read the article Introduction to Git.
Naming Conventions
Regarding the standard for naming commits, I have recently been using Conventional Commits, which is a standard for writing commit messages that you can see at this link. Here is the format and the most common types:
<type>[optional scope]: <description with verb in infinitive>
feat: add new functionality
fix: fix a bug
docs: changes in documentation
style: changes that do not affect the meaning of the code (whitespace, formatting, missing semicolons, etc.)
refactor: a code change that neither fixes a bug nor adds a feature
perf: a code change that improves performance
test: add missing tests or correct existing tests
chore: changes to the build process or auxiliary tools and libraries such as documentation generation
Recommendation if you have never used git and no one tells you
Before executing any Git command on your branch and you think you might mess it up (especially until you get used to working with this version control system), make a backup of the project folder with the current state of the task. This way, if any disaster occurs that you cannot control, you can revert to a previous state and repeat the process correctly.
Git File Lifecycle or States
The most relevant states (there are several more) are:
- Untracked Files: these are files that DO NOT live inside Git, only on the hard drive. Git has no record of their existence. For example, a new file that we have created and has not yet been added with
git add
.
touch my_new_file # untracked
- Staged Files: these are files in Staging. They live inside Git and there is a record of them because they have been affected by the
git add
command, although not their latest changes. Git already knows about these latest changes, but they have not yet been definitively saved in the repository because the git commit command is missing.
git add my_new_file # staged
- Tracked Files: these are the files that live inside Git, have no pending changes, and their latest updates have been saved in the repository thanks to the
git add && git commit
commands.
git commit my_new_file # tracked
Initializing a Project
The first step is to initialize the project. This is done the first time by following a series of steps. The easiest way to do this is to create the project on your Git server (remote/local Docker container, GitLab, GitHub, etc) and download it to our system.
It's recommended to install the Git Flow package. We execute the following, which gives you the default values:
git flow init -d
git branch -m master main # Change the name of the master branch to main if desired
Info
This process only needs to be done once by a team member.
Working with Branches
The first thing we are going to learn is the git status
command, which will tell us the current state of the repository, with the changes that need to be made, deletions, or if everything is up to date.
- View local and remote branches:
git branch -a
develop
* main
remotes/origin/HEAD -> origin/main
remotes/origin/develop
remotes/origin/main
Info
We can also use git branch --no-merged
to see branches that contain changes.
- Switch to a branch:
git switch develop
* develop
main
- To create a branch and switch to it, we have two options. From develop, execute one of the following options:
# Option 1
git branch feature/api-token && git switch feature/api-token
# Option 2
git switch -c feature/api-token
develop
* feature/api-token
main
- Delete a branch:
git checkout develop && git branch -D feature/api-token # D = force delete
Info
If we are inside a branch that we want to delete, we must first switch out of it.
We can also delete remote branches:
git push origin --delete feature/api-token
origin is a convention that refers to the remote repository.
If we want to delete a branch with changes in staging, it may not allow us to delete it. There are several methods. I prefer moving the changes to the stash (a kind of clipboard or temporary memory) that "cuts" all the changes and deletes the branch. The other method is more drastic and deletes all changes made in that branch since it was created:
git stash -u
git checkout develop
git branch -D branch_name
Essential Git Commands
git clone
git clone repository_URL
git fetch
This command downloads the remote content but does not update the local repository's working state, so your current work will not be affected. Similar to the svn update
command.
git fetch
git fetch -a -p # for all branches (all) and removes (prune) local branches that no longer exist remotely
If you want to know more about this command, read the article: git fetch.
git add
Add file(s) to the staging area:
git add README.md
git add .
git add -a # All changes.
git add -A # New, modified, and deleted files.
git add --ignore-removal . # New and modified files.
git add -u # Modified and deleted files.
Difference between git add .
, git add -a
, and git add -A
git add .
: Adds all new and modified files in the current directory and its subdirectories, but does not include deleted files.git add -a
: Adds all new, modified, and deleted files in the current directory and its subdirectories.git add -A
: Adds all new, modified, and deleted files in the entire repository, both in the current directory and anywhere else in the repository.
- "José, I always use git add ."
See below my comment about the use of git add .
and why It's not recommended.
git stash
Temporarily saves changes to bring them back later.
git stash # It's like cutting to Git's clipboard
git stash -u # Also saves untracked files.
git stash pop # It's like pasting the last stash from Git's clipboard
git stash save "message" # Saves changes with a message
git stash list # shows what we have in the stash
git stash pop stash@{2} # Recovers from stash with index 2
By default, changes in stash will become staged. If you want to remove them, use the git restore --staged
command.
More info on Stash at the following link: Git stash.
git pull
Fetches data from the remote repository and then merges the changes with the local repository. It's the same as doing git fetch && git merge origin/$CURRENT_BRANCH
git pull -p # -p=prune, removes references to deleted remote branches
git pull --force # overwrites the current local branch
git push
Push local changes to remote.
git push # -p=prune, removes references to deleted remote branches
git push --force # overwrites the remote branch with the same name
Regarding the --force
parameter, be very careful and use it only when you know exactly what you are doing.
git rm
Remove files marked as tracked. Better to use git rm
than rm
to avoid problems.
git rm file_name
git merge
Case 1: DO NOT want to keep local changes. Maybe you modified a file to test, but you no longer need the modification. The only thing that matters is being up to date with remote changes.
git fetch
git reset --hard HEAD
git merge origin branch_name # usually develop. @{u} is the same as origin $CURRENT_BRANCH
Case 2: You care about local changes:
git fetch
git pull origin develop
# Resolve conflicts if any
git log
Shows a history of all branches:
git log
git log --oneline --decorate --graph
git log --pretty=format:'%h - %an, %ar : %s'
.gitconfig File
~/.gitconfig
or ~/.config/git/config
is where the git configuration is on your computer. From there, we can, for example, create aliases for a list of commands that we usually use so we don't have to type the same thing all the time.
Some useful commands:
git config --global user.name "John Doe"
git config --global user.email john_doe@dev.com
git config --global core.editor "code --wait" # VSCode as default editor
git config --global -e # Opens .gitconfig file for editing
git config --global core.autocrlf [true, input] # true for Windows / input for Mac/Linux
git config -h # Help on config
Info
Regarding core.autocrlf, if there are developers working on different OS, the special characters carriage Return (CR) and Line Feed (LF) will be added by Windows users, so It's necessary to run git config --global core.autocrlf true
. On macOS/Linux It's not necessary, but It's advisable to assign the value input so that it automatically handles this value.
# .gitconfig file
[alias]
pull_force = !"git fetch --all; git reset --hard HEAD; git merge @{u}"
Example of Daily Workflow for Working with Git
Let's see an example of how to work with Git on a project. Let's imagine we are going to work on a new feature that involves authenticating an API.
Although my team and I work with common Git commands, I'll also show how it would be done with GitFlow.
1. Create feature branch
We create a branch parallel to develop where we will work. Generally, using GitFlow it will be a feature/ branch, for example:
git fetch -a -p
git switch develop # In case we are on another branch, you can check where you are with git branch
# Common mode
git switch -c "feature/#HM-01-auth-api"
# GitFlow mode
git flow feature start HM-01-auth-api
2. Add commits
We have worked on the necessary changes, so we will add them to the stage and then commit:
git status # to know which files are modified, untracked,...
git add . # or better the names of the files for that commit
git commit -m "feature/#HM-01-auth-api"
Regarding the use of git add .
, It's considered bad practice, as we can unintentionally introduce unnecessary files. Also, commits are usually made according to the files needed for a logic to work and It's easier when doing a code review. For example, if we need to authenticate an API in our task, the correct way would be:
git add auth/api_auth.py app.py
git commit -m "feature/#HM-01-Add new class & modify routes" # Message text in English and does NOT end with a period
git add readme.md
git commit -m "feature/#HM-01-Update doc" # Verbs in infinitive: add, merge, bump, change, delete, modify, fix,...
git add test/system/api/auth/test_api_auth.py
git commit -m "feature/#HM-01-Add tests for all routes" # It's advised that the message does not exceed 50 characters
3. Compare with develop
Other developers probably advance in the code and your branch is not up to date. It's necessary to download the new modifications from the develop branch and choose the changes that remain.
# We are in the feature/ branch, if not we switch to it
git fetch -a -p # Not necessary, but always advisable to keep branches up to date
git pull origin develop # IMPORTANT: This downloads changes from remote develop, NOT from your local develop branch.
Now two things can happen, everything is fine (we move to point 4) or there are conflicts that need to be resolved (merge conflict).
3.1. Merge conflict. Resolve conflicts in your branch.
We will review with git status
the files that need to be resolved and resolve them one by one. It's recommended to use a graphical application or a more friendly IDE, as our code now contains merge traces that could be forgotten to remove. Some recommended Git apps are Sourcetree, GitKraken.
4. Push changes to the remote git server
We just need to push the changes to merge them with develop. There are three ways to do this: git merge
, git rebase
, and the gitflow way.
# Common mode
# We are in the feature/ branch. If not, we switch to it
git merge origin develop --fast-forward # Option 1
git merge origin develop --squash # Option 2
git rebase origin develop && git merge --fast-forward # Option 3
# GitFlow mode
git flow feature finish HM-01-auth-api
The recommended way is to use git merge, although I'll explain the two options:
With
git merge
we combine the commits and make a new commit in develop. If the develop branch is very active, this option can "contaminate" the history quite a bit.With
git rebase
, WE DELETE the commits from the feature branch, equalize with develop, and put new commits on top. The merge would be done withgit merge --fast-forward
.It should be clear that git rebase solves the same problem as git merge: they are designed to integrate changes from one branch to another, but in different ways. git rebase is mainly used so that in the Git history, branches other than main and develop of the development team (features, hotfix) are not seen, as these parallel branches may not be of interest in the history or if there are many commits and they want to be reordered (5 commits can be merged into 2, for example), we would have the option to use an interactive rebase with
git rebase -i
. It's all a matter of aesthetics when we rungit log
.Another golden rule of git rebase is NEVER USE IT ON PUBLIC BRANCHES.
More information at atlassian.com > merging-vs-rebasing
Tips and Other Commands
untrack a file
git rm --cached file_name
Fix a bug in main
To do this, create a hotfix/xxx
branch locally to fix the problem and merge it into main
. If everything is correct, we must also merge it with develop
. Using gitflow, you need to run the following command:
git flow hotfix start HM-01-fix-bug
# Do your Kung-Fu
git flow hotfix finish HM-01-fix-bug
Get remote changes without keeping local changes
You want to delete all uncommitted local changes. Maybe you modified a file to test, but you no longer need the modification. The only thing that matters is being up to date with the remote branch.
git fetch -a
git stash -u # Optional but advisable
git reset --hard HEAD
git merge --no-ff origin develop # Or alternatively we can do git pull
Delete my changes
If you want to revert to the previous state:
git reset --soft # Keeps changes
git reset --hard # Deletes even your code. Use with caution
Go to a previous state via an ID without keeping changes
git log # See the commit ID to which we want to revert
git reset --hard 23j45b3b45hb345b