An excellent technique to maintain a Git branch tidy — While pull/merge requesting
Nowadays, Git is one of the most widely used version control systems in the information technology world. One of the most lauded. However, as with any solution, Git has shortcomings that must be considered in order to avoid collapsing in a project with a team and a Git history that makes no sense after a few months of intense development. The following writing provides an overview of Rebase (autosquash) and Fixup while performing a Pull/Merge request (Note: And note talk about Git branch strategy / Git commit strategy or anything else). The following writing architecture is as follow: We begin by (1) examining the advantages and disadvantages of Git, and then (2) consider how we may counteract the disadvantages (i.e, Review of the problem and Analysis of the solution via the introduction of git rebase --autosquash
and git commit --fixup
). Finally, (3) we will conclude with a brief discussion regarding such a solution.
Git’s advantages and disadvantages
To begin, Git’s well-known for its lightning-fast and efficient performance, as well as an astonishing number of features for tracking code changes and seeing what has happened on the project while you were away. Git is cross-platform, which makes it ideal for usage in multidisciplinary teams with disparate operating systems. Additionally, Git is maintainable and robust. Finally, one of Git is most potent weapons is its incredible command line utility, which is very-well flourished and full of possibilities from which we will only cover a fraction of it today. However, such a powerful solution is never coming without problems; Git is complicated for newbies, and Git’s history can easily become obscure if it is a mess.
Problem with messy Git history
Whatever Git branching strategy you use, having such a mess (i.e., image above) can make it extremely difficult to go back to a feature published six months ago due to a crash and readily determine which branch introduced the problem, which commit, and similar things. This article will not recommend that you utilise a single linear Git branch model to keep track of what is happening; nevertheless, it will discuss the issue of introducing too many meaningless commits as a result of a pull/merge request.
To begin, [on a long-term project] locating the correct commit becomes more complex when a new branch introduces 20/30 commits where 10 would have sufficed. Each team has its own commit convention, and if yours does not, it is time to take a look into it [1, 2, 3, 4]. Submitting a pull/merge requires, in the most humble manner possible, the needs to take care of the new history that will result in the branch being eventually (re-)reviewed or merged into production. Consider now, the following two example:
(Problem example 1): The reviewer of the pull/merge request requests changes; these changes are minor; they address typos or some loop optimisations associated with the initial changes. If you write a fresh commit for such a little change, you are creating a needless commit that could have been made more effectively within the proper initial commit of the new function’s introduction.
(Problem example 2): As a developer, I follow the technique of committing as much of your present work as possible to keep track of what I am doing and to ensure that if my laptop crashes the following night, I will still have everything on the remote branch. However, I am committing a few files into a commit and several other commits, but I just noticed that a few species should have been made better; I made the changes, but they are related to the commit I did a few hours ago. If you write a fresh commit for such a little change, you are creating a needless commit that could have been made more effectively within the proper initial commit made a few hours earlier.
Introducing a mess that no one can read or work with, after months of development, equates to months of pointless commit introduction that could be dangerous for the stability of your project. It is fantastic to work with a strong Git branch strategy and a decent commit protocol. However, when it comes to the content of your pull/merge request, it must also be well-thought and a team-convention should be stated at the beginning of the project.
The issues above should not be considered for a project with only one developer, but rather for a project with a team of at least two/thirds continuous engineers.
Solutions to avoid messy Git history
The following section will return to the previously stated problem instances and demonstrate which Git awesome commands are ideal for combating such easy ways to introduce mess in your Git history.
(Problem example 1 summary)The reviewer of the pull/merge request requests changes; these changes are minor. If you create a new commit to correct a typo in the introduction of a new function, this is an unnecessary commit that could easily have been made in the initial commit of the new function’s introduction.
Git commit — fixup (can also be seen as the “Ni vu ni connu”) method is the solution.
The fixup option from the git commit command is — from the documentation — :`creates a commit which changes the content of but leaves its log message untouched. The -m option may be used to supplement the log message of the created commit, but the additional commentary will be thrown away once the “fixup!” commit is squashed into.`[5].
The reviewer requests changes that are linked to a specific commit, fixes up the additional change to that commit (i.e. according to his commit-hash), and ensures that no mess is introduced into the range of commits in this new branch. Additionally, those “fixup” must be “squash” with their target; instructions on how to do so may be found in the practical sub section of problem example 2.
Practical example
Given the following short Git History, the reviewer of the Pull/Merge requests ask to change the hash 9cf7eab
, the classname of the component created is unappropriate.
[Initial] History that I have sent for review:
b946290 (HEAD) refactor(pages/about): improve about's architecture
e7babb3 feat(scripts): add new script to run admin
ef550ec feat(pages/homepage): implement new search bar
9cf7eab feat(component): add new search bar component
44c9623 (tag: main) initial commit
[...]
[Review 1] [Mistake] History that I would send for re review with the mistake of creating a new commit that is definitely not needed: The bold line or hash 8b56065
is the mistaken commit made.
8b56065 (HEAD) style(component/search_bar): change component's name
b946290 refactor(pages/about): improve about's architecture
e7babb3 feat(scripts): add new script to run admin
ef550ec feat(pages/homepage): implement new search bar
9cf7eab feat(component): add new search bar component
44c9623 (tag: main) initial commit
[...]
[Review 1] [Good Practice] History that I would send for re review with the correct way of fixing the problem made from the hash 9cf7eab
: The bold line or hash 8b56065
is the fixup made.
8b56065 (HEAD) fixup! feat(component): add new search bar component
b946290 refactor(pages/about): improve about's architecture
e7babb3 feat(scripts): add new script to run admin
ef550ec feat(pages/homepage): implement new search bar
9cf7eab feat(component): add new search bar component
44c9623 (tag: main) initial commit
[...]
Here is the command used to follow such a process:
git add <your_files>
# refer to https://git-scm.com/docs/git-add for more information.git commit --fixup <target_hash>
# target_hash here is == 9cf7eab
Now, once those fixups are applied, nobody will notice any difference because the fixup commit will be automatically merged into his target, and the history will be cleaner than if an additional commit for the same scope was added. Please check the problem example 2 below for details on how to apply those fixes.
(Problem example 2 summary)As a developer, you should commit as much of your present work as possible. You create commits and few hours later you make a quick change on something already committed few hours earlier. If you write a fresh commit for such a little change, you are creating a needless commit. This could have been made more effectively within the proper initial commit made a few hours earlier.
Git rebase interactive method is the solution.
The rebase method from the git utility tool commands is — from the Attlasian documentation — :`Rebasing is the process of moving or combining a sequence of commits to a new base commit. Rebasing is most useful and easily visualized in the context of a feature branching workflow […].`[6].
You are working on a project and have made a few commits when you realise that some previously committed modifications should be considered. You make them, but rather than creating a new commit for them, you are better off fixing them up on the proper commit (see problem example 1 section practical example). Following the fixup, you should use Git rebase to apply the fixup and manipulate your history as desired.
Practical example
Given the following brief Git History, you have made a mistake and need to return to alter some commits already in your history using fixup (see the practical example section of the example 1). However, rather than merging in production any pointless fixup commits, you must apply them to their respective target. Note: To ensure that your reviewer understands what you have altered, it is advisable to leave the fixup commits alone during any re-review; apply them only after your reviewer accepts those changes; otherwise, things might become a mess rapidly.
[Initial] History that I have sent for review:
b946290 (HEAD) refactor(pages/about): improve about's architecture
e7babb3 feat(scripts): add new script to run admin
ef550ec feat(pages/homepage): implement new search bar
9cf7eab feat(component): add new search bar component
44c9623 (tag: main) initial commit
[...]
[Review 1] (Result of the practical section of the example 1)
8b56065 (HEAD) fixup! feat(component): add new search bar component
b946290 refactor(pages/about): improve about's architecture
e7babb3 feat(scripts): add new script to run admin
ef550ec feat(pages/homepage): implement new search bar
9cf7eab feat(component): add new search bar component
44c9623 (tag: main) initial commit
[...]
[Review 1] (After the reviewer has accepted the fixup and requested that it be applied to their target prior to merging):
The only thing that has changed is the commit-hash, as two commits have been merged together, which indicates that the history has been altered. However, when this branch is merged into production, the history will be cleaner.
b946290 (HEAD) refactor(pages/about): improve about's architecture
e7babb3 feat(scripts): add new script to run admin
ef550ec feat(pages/homepage): implement new search bar
2134477 feat(component): add new search bar component
44c9623 (tag: main) initial commit
[...]
Here is the command used to follow such a process:
git rebase --autosquash -i main#orgit rebase --autosquash -i HEAD~n# where n is the number of commits from the head that you wish to take into consideration when rebasing.# --autosquash: Will automatically re-target your fixup commits.# -I: Will give you an idea of how the history will look following the rebase. Caution: If you omit the -I option, it will rebase automatically without allowing you to perform any further operations like as moving your commit up or down or performing any other operations that rebase enables.
Note: everything stated above can easily be done via GUI; for instance, with Jetbrains Git VSC solution [7].
Note 2: GitLab includes a feature that automatically squashes fixups submitted during a review before they are merged into production. After having used this option multiple times, it performs very well.
Discussion and Conclusion
Failure to maintain a clean Git history for your project might result in exceedingly perplexing behaviour and a considerable decrease in motivation for (new) team members. While performing a Pull/Merge request, the developer’s primary responsibility is to maintain the history and ensure that it is maintainable in the long term. If you need to make changes to an already-committed commit, git commit --fixup
and git rebase -i --autosquash
are two extremely strong and fast options. Using rebase has never been easy because it requires the rewriting of an entire history at times, but when done properly, it can significantly improve your daily work life. Utilizing these two expedient options will also provide your project with a boost in maintainability +++. Additionally, such an alternative is increasingly being used in large teams nowadays. For students or novices with Git, I strongly advise looking into those alternatives and experimenting on your own. Nonetheless, Git provides more alternatives, as it is a powerful and significant tool. However, this approach appears to be the quickest and most straightforward way to keep a clean history while performing a Pull/Merge request.
In light of this brief article, you should be aware of the ramifications of adding unnecessary commits to your history when requesting that a new branch be merged into production. You should now be able to use two new Git settings, which should help you see the value of maintaining a clean history for the duration of your project. Please post a remark if you would like me to amend/improve any parts of my storey. Finally, I hope this article helped you enhance your experience with Git; if it did, please leave a like/comment.
Thank you.
References
[1] Eraslan, S., Rios, J., Kopec-Harding, K., Embury, S., Jay, C., Page, C., & Haines, R. (2020). Errors and Poor Practices of Software Engineering Students in Using Git. In Proceedings of the 4th Conference on Computing Education Practice 2020 (pp. 1–4).
[2] Salvatierra, H. (2019). A PROPOSAL OF AUTOMATIC QUALITY EVALUATOR FOR GIT COMMIT MESSAGES. Digital UPM.
[3] Allamanis, M., Barr, E., Bird, C., & Sutton, C. (2014). Learning natural coding conventions. In Proceedings of the 22nd ACM SIGSOFT International Symposium on Foundations of Software Engineering (pp. 281–293).
[4] Karma Convention, Online: http://karma-runner.github.io/6.3/dev/git-commit-msg.html.
[5] SCM, GIT. “Git — Git-commit Documentation.” Git — git-commit Documentation. git-scm.com, August 16, 2021. https://git-scm.com/docs/git-commit.
[6] Atlassian. “Git Rebase | Atlassian Git Tutorial.” Atlassian. www.atlassian.com. Accessed September 26, 2021. https://www.atlassian.com/git/tutorials/rewriting-history/git-rebase.
[7] Jetbrain, Intellij. “Version Control Window | IntelliJ IDEA.” IntelliJ IDEA Help. www.jetbrains.com, September 14, 2021. https://www.jetbrains.com/help/idea/version-control-tool-window.html.