At this point we're going to talk about best practices to work with git.
This guide try to help to working teams to use git and git flows correctly. Is very recommended that all team's members working with git in the same way, for this goal this guide will contain some usefull advices and tips for the correct use of git and help to unify the way of work with it.
The first thing to do is setting up git.
git config --global user.name Nombre
git config --global user.email [email protected]
git config --global core.filemode false
When you work with git there are some files that's you shouldn't upload to the repository like configuration files, ide files, files with passwords or connection params, etc. For this goal, exists the .gitignore file, and you need create it and include here the list of files or folders to exclude of the version control.
It's very important include at .gitignore the files that contains confidential information like credendials to services, by security reasons these type of information should never be uploaded to the remote repository.
Usually, you can create some templates files for this type of information with its fields empty. For example a file db.config.sample with content:
db.host=
db.url=
db.password=
Next, there are some folders and files thats usually have to be added at .gitignore list depending on the programming languaje or ide used:
- General files or folders:
- .log
- log/
- tmp/
- .settings/
- Java applications:
- target/
- .project
- .classpath
- target/
- bin/
- .metadata/
- RemoteSystemsTempFiles/
- Servers/
- NodeJS:
- node_modules/
- Python:
- Files with pyc extension: .pyc
- The jetbrains ides like Pycharm or WebStorm, have a settings folder with the name:
- idea/
Write descriptive commit messages that explain the changes maked at the commit and his reason. For this goal, is recommended so configure the git repository to force to use messages in commits.
Do commit early and often, have periodic checkpoints help you to have control and solve problems when your code fails, will be easier understand what is different and where the problem may be.
Its very important that all code that you push into the remote repository works properly and be tested. All people that synchronize within the repository should be able to execute it without problems.
For ensure this is very recommended using continuous delivery tools for testing code, you can find more information at our Sonar guide.
Often is a good idea create different repositories depending of his functionallity, project or team who work with it. For example, may be interesting have a different repositories for the backend application than the frontend application, and may be interesting have one for the devops configuration files like puppet.
Don't change the history published, for mantain a true traceability of the changes and preserving for problems.
For this goal you should use the option --no-ff with merge commands to maintain all commit history in the merged branch. The differences from a merge with --no-ff option and other without it is shown in the next picture:
You should configure your repository against history changes. If you initialize a bare git repository with --shared it will automatically get the git-config "receive.denyNonFastForwards" set to true.
There are a lot of IDEs that have its own git plugins, but very often this plugins doesn't works fine in determinated features. The most reliable way to syncronize with git is install git and use the shell to work with git.
If you need move some file or directory to another path, use the git mv command. For preserving correctly traceability of the changes, you should commit separate the moving of file before make changes of the file, because when you move a file the git internally delete and create new file and you can't see the differences between latest version.
Don't commit binary files or very large files if you can avoid it. Git is not intended for it.
Write a text file called README.md in the main directory of project. This file must have the main information about the code, how use and configure it and how can execute it. This file must contains:
- Information about the application configuration, must cover configuration files, dependencies, libraries or tools that are needed for use or install it.
- Information about how install and execute the application.
- Information to test the application and test the qa quality.
- If is a application or library that you can invoke, like an web application or an service web, is important include the information about you can call it.
- For applications which will run on Docker container, is important include here the information necessary to build the Docker image and how run the container.
It is highly recommended that all team members follow the same procedural rules when using git. The workflow described below is an accepted procedure worldwide for small work teams.
Git Flow establishes the following restrictions:
- There are only one central repository: origin. Every developer pull and pushes to origin.
- There are only two main branches: develop and master.
- There are three support branch types: feature, release and hotfix.
- Always merge with --no-ff option.
Git Flow defines two branch types, main branches and support branches.
The Main branches are develop and master.
The master branch is the default git branch. This is a special branch. Git flow establishes the following restictions on this branch:
- Never delete this branch.
- Never do commits over this branch directly: only merge commits are allowed.
- Only do merges from release and hotfix branches.
The develop branch is a long running branch and must be created from master.
- Never delete this branch.
- Never do commits over this branch directly: only merge commits are allowed.
- All features must be merged in develop
- When develop reaches a stable point, will be merged back into master, throug a release branch.
- Once a release process is finished, the release branch must be merged back into develop.
- Once a hotfix process is finished, the hotfix branch must be merged back into develop.
The support branches are short running branches. Those branches are created to follow one of the three processes defined on Git Flow. Every support branch has defined from what branch must be created and on what branch/es must be merged.
This is the developer working branch.
- The feature branch always is created from develop branch.
- We can create several feature branches.
- It's recommended to create only a feature for each developer and feature to develop, and to create shorter features as posible.
- The feature branch always must be merged back into develop.
- Don't merge any other branch into feature branch. (i.e.: develop branch)
- The feature branch must be deleted once finished: merged back into develop.
Git Flow defines three sub work flows:
Each task of development must be created in a feature branch following the Feature flow.
Steps in the feature flow:
# gitflow feature Start
git checkout -b feature/lorem-ipsum develop
# Editing
edit, git add .., git commit ..
# gitflow feature Finish
git checkout develop
git merge --no-ff feature/lorem-ipsum
git branch -d feature/lorem-ipsum
# Publish code
git pull origin develop
git push origin develop
When the source code in the develop branch reaches a stable point and is ready to be released, all of the changes should be merged back into master somehow and then tagged with a release number.
Steps in the release flow:
# gitflow release start
git checkout -b release/0.1.0 develop
# Editing
edit, git add .., git commit ..
# gitflow release finish
git checkout master
git merge --no-ff release/0.1.0
git tag -a v0.1.0
git checkout develop
git merge --no-ff release/0.1.0
git branch -d release/0.1.0
git push origin 0.1.0
git push origin develop
git push origin master
They arise from the necessity to act immediately upon an undesired state of a live production version. When a critical bug in a production version must be resolved immediately, a hotfix branch may be branched off from the corresponding tag on the master branch that marks the production version.
Steps in the release flow:
# gitflow hotfix start
git checkout -b hotfix/0.1.1 master
# Editing
edit, git add .., git commit ..
# gitflow hotfix finish
git checkout master
git merge --no-ff hotfix/0.1.1
git tag -a 0.1.1
git checkout develop
git merge --no-ff hotfix/0.1.1
git branch -d hotfix/0.1.1
git push origin develop
git push origin master
In a real Git project in addition to use Git Flow, users must know stage where they are working on, that is to say, leads to having to play two different types of repositories.
The purpose of Git is to manage a project or a set of files as they change over time, therefore, Git stores this information in a data structure called a repository which often located in a .git
subdirectory at the root of working repository.
As shown in the image above, Git project is made up of:
-
Local repository: local work area is split into:
- Working directory: folders/files that you are currently working on.
- Staging area (Index): is a holding area for changes that will be committed, so you can control what parts of the working directory go into the next level.
- HEAD: a reference to a specific commit (usually points to the most recent commit on the current branch) and it serves two major purposes: tells Git which commit to take files when checkout is executed and tells Git where to put new commits when commit is executed.
-
Remote repository: place where code is stored like GitHub, Bitbucket, etc.
In summary, each file will go moving through of the distinct phases until to Remote repository.
Once we know the stage of Git project we can start using Git commands, as shown in the image below:
Therefore, we will check each of main Git commands regarding to different cases: set up repository, save changes, sync up, use branches, check repository and undo changes.
Creates a new Git repository that way can transform an existing and unversioned project to a Git repository or initialize a new empty repository.
$ git init
Clones an existing Git repository into a new directory with a isolated environment that has its own history and manages its own files.
$ git clone <repository> <directory>
Lets users can create, list and delete connections to repositories so a specified URL is linked to a quick access.
# List all connections.
$ git remote
# List all connections along with the URL of each one.
$ git remote -v
# Create a new connection called <name> to a remote repository <url>.
$ git remote add <name> <url>
# Remove the connection called <name> to the remote repository.
$ git remote rm <name>
Aggregates the new/updated content from the Working directory to the Staging area(Index) for the next commit.
$ git add <file/directory>
Deletes files from the Staging area(Index) or from Working directory and Staging area(Index).
# Delete a single file from Git repository and also delete it from the Working directory.
$ git rm <file>
# Delete a single file from Git repository without deleting from the Working directory.
$ git rm --cached <file>
Stores the current contents from the Staging area(Index) to HEAD in a new commit along with a message describing the changes.
$ git commit -m <message>
Lets users can create, list and delete tags. A tag is just a reference which points to the current commit.
The two main types of tags are lightweight and annotated:
- Lightweight tag is just a reference to a specified commit.
- Annotated tag is almost like a lightweight tag but contains a message.
# List the available tags in Git.
$ git tag
# Create a lightweight tag
$ git tag <tag_name>
# Create an annotated tag
$ git tag -a -m "<tag_message>" <tag_name>
# Delete a tag
$ git tag -d <tag_name>
# Push all tags to the Remote repository
$ git push origin --tags
Stores the current state from the Working directory and Staging area(Index) on a stack to get it back later, so that, user can switch branches and it is not necessary to commit half-done work.
# Store Working directory and Staging area state
$ git stash
# List the stashes that have been stored
$ git stash list
# Apply the most recent stash to the current line of development
$ git stash apply
# Apply the second most recent stash to the current line of development
$ git stash apply stash@{1}
Transfers all commits from HEAD to Remote repository.
# Push the work from the specified branch to the named remote repository
$ git push <remote> <branch>
Transfers all commits from Remote repository to HEAD.
# Fetch all branches
$ git fetch <remote>
# Fetch only the specified branch
$ git fetch <remote> <branch>
Updates the Working directory to the newest commit from Remote repository executing git fetch
and git merge
between the retrieved changes and the current branch.
# Update the specified branch from named remote repository
$ git pull <remote> <branch>
Lets users can navigate between the different branches in this way the Working directory will be updated to the specified branch.
# Switch from current branch to another
$ git checkout <branch>
# Create a new branch and switch to it
$ git checkout -b <new-branch>
Lets users can create, list, rename and delete branches. A branch stands for an independent line of development but is just pointer to commits.
# List all branches from Git repository
$ git branch -a
# Create a new branch
$ git branch <branch>
# Rename a branch
$ git branch -m <old_name> <new_name>
# Delete a branch called <branch> in a safe way because Git prevents deleting the branch if it has unmerged changes.
$ git branch -d <branch>
# Force deleting the specified branch
$ git branch -D <branch>
Combines the current development line and a feature branch into a single branch. Git can execute distinct merge algorithms (fast-forward or non-fast-forward) according to the state of the branches, in order to display differently the merges.
- Fast-forward merge is applied if the current branch has not diverged of feature branch and will just update the branch pointer without creating a new merge commit, achieving the whole commit sequence is linear.
- Non-fast-forward merge forces to create a new merge commit whether the current branch has diverged regarding feature branch or the current branch has not diverged regarding feature branch. In this case, the commit history will emphasize the merge.
In this section can visualize the two algorithms graphically.
# Combine the specified branch into the current branch and Git will decide the merge algorithm (by default fast-forward)
$ git merge <branch>
# Combine the specified branch into the current branch but always generate a new commit (non-fast-forward)
$ git merge --no-ff <branch>
Displays the state of the Working directory and Staging area, therefore, users can see which changes have been staged, which have not and which files are not being tracked by Git.
$ git status
Shows commit history
# Show commit logs
$ git log
# Show commit logs with a pretty format
$ git log --graph --decorate --pretty=oneline
Show an overview of the last actions (commits, pull, push, checkout, reset, etc) user did inside of Git repository. Highlight actions are only stored in the local machine.
$ git reflog
Shows the difference of files between the distinct phases of the Local repository.
# Differences between the Working directory and the Staging area(Index)
$ git diff
# Differences between the Staging area and the most recent commit(HEAD)
$ git diff --cached
# Differences between the Working directory and the most recent commit(HEAD)
$ git diff HEAD
Displays distinct types of objects:
- Commits
- Tags
- Trees
- Plain blobs
# Display any object in Git
$ git show <object_id>
In addition to use checkout command to switch branches also retrieves the specified state to the current development line.
# Undo the modified file located in the Working Directory to the HEAD version
$ git checkout -- <file>
# Retrieve all files to a specific version according a determined commit
$ git checkout <commit>
# Retrieve the file to a specific version according a determined commit
$ git checkout <commit> <file>
Reset the Working directory and Staging area(Index) according the last commit in the HEAD to the specified state. Warning: some Git history might be lost.
# Reset the Staging area(Index) to the most recent commit but keep the Working directory unchanged
$ git reset
# Reset the specified file from the Staging area(Index) and keep it in the Working directory unchanged
$ git reset <file>
# Reset the Staging area(Index) and the Working directory to the most recent commit
$ git reset --hard
# Reset the Staging area(Index) to a specified commit but keep the Working directory unchanged
$ git reset <commit>
# Reset the Staging area(Index) and the Working directory to a specified commit
$ git reset --hard <commit>
Rool back a commit which has already been pushed and create a new commit to the history with the undone changes. Highlight: no Git history will be lost.
# Roll back a specified commit
$ git revert <commit>
It's very important to version your tags with a concrete versioning system to made easy traceability of all your code. We suggest using of a lightweight version specification called Semantic Versioning thinking to API versiosing, but totally applicable to git tags
This is an specification authored by Tom Preston-Werner based on three digits MAJOR.MINOR.PATCH
Theses are the main rules about this specification
-
A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative integers, and MUST NOT contain leading zeroes. X is the major version, Y is the minor version, and Z is the patch version. Each element MUST increase numerically. For instance: 1.9.0 -> 1.10.0 -> 1.11.0.
-
Major version zero (0.y.z) is for initial development. Anything may change at any time. The code of this tag should not be considered stable.
-
Version 1.0.0 defines the first tag with stable code. The way in which the version number is incremented after this release is dependent on how your code change.
-
Patch version Z (x.y.Z | x > 0) MUST be incremented if only backwards compatible bug fixes are introduced. A bug fix is defined as an internal change that fixes incorrect behavior.
-
Minor version Y (x.Y.z | x > 0) MUST be incremented if new, backwards compatible functionality is introduced to the code. It MUST be incremented if any functionality is marked as deprecated. It MAY be incremented if substantial new functionality or improvements are introduced within the private code. It MAY include patch level changes. Patch version MUST be reset to 0 when minor version is incremented.
-
Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the code. It MAY include minor and patch level changes. Patch and minor version MUST be reset to 0 when major version is incremented.
In order to improve the traceability of our developments and integrate it with project management tools as Jira or Redmine (inside Beeva) you can start each commit messages with #id_task. In this way is possible link task description into Redmine to know what commits is associated with this task.
For example, if there is a task into Redmine with id 17025 and we start commit message (commit -m "#17025 change label value") each message that starts with "#17025..." can will be shown into Redmine task description
Hooks are a good tool to customize version control behavior. Using hooks we can intercept each step of git cycle, client-side and server-side, and launch custom scripts when certain important actions occur.
developments, for example, related with Amazon Web Services should use a pre-commit hook to search for Amazon AWS API keys and to avoid push to the remote repository these credentials, see the example
To learn deeply about git hooks, please see documentation
There are a lot of tools and sites to help you to work better with git. List below show you some tools and web sites interesting for work with git:
- Gitflow tool. A tool that help you to implemet gitflow workflow.
- Gitignore.io. A website that generate .gitignore file for operating systems, IDEs or programming languages.
- Tips and scrpts. A very interesting collection with scripts to do your git experience easier.
- .bashrc. Code to add inside ~/.bashrc file and help you to know in which branch you are.
Sometimes when we are using as a remote repository as a github, we must write our password in each commit. It's a good practice configure a ssh key to do this task easier and avoid writing password with each git sentence. See example for GitHub
- Git official documentation
- GitHub Help
- Git Cheatsheet
- Git Flow Cheatsheet
- Successful branching model
- Seth Robertson Best Practices
BEEVA | Technology and innovative solutions for companies