From 1e9100035db939002c5c45ded9945939b1e064a8 Mon Sep 17 00:00:00 2001 From: Thomas Schwery Date: Sun, 18 Aug 2019 18:53:29 +0200 Subject: [PATCH] Article on Git workflow --- articles/2019-08-17-my-git-workflow.md | 304 +++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 articles/2019-08-17-my-git-workflow.md diff --git a/articles/2019-08-17-my-git-workflow.md b/articles/2019-08-17-my-git-workflow.md new file mode 100644 index 0000000..cb93388 --- /dev/null +++ b/articles/2019-08-17-my-git-workflow.md @@ -0,0 +1,304 @@ +--- +title: My Git workflow +date: 2019-08-17 16:00:00 +--- + +[Git](https://git-scm.com/) is currently the most popular Version Control +System and probably needs no introduction. I have been using it for some +years now, both for work and for personal projects. + +Before that, I used Subversion for nearly 10 years and was more or less +happy with it. More or less because it required to be online to do more +or less anything : Commit needs to be online, logs needs to be online, +checking out an older revision needs to be online, ... + +Git does not require anything online (except, well, `git push` and `git pull/fetch` +for obvious reasons). Branching is way easier in Git also, allowing you to work +offline on some feature on your branch, commit when you need to and then push your +work when online. It was a pleasure to discover these features and the +workflow that derived from this. + +This article will describe my workflow using Git and is not a tutorial or +a guide on using Git. It will also contain my Git configuration that matches +this workflow but could be useful for others. + +## Workflow + +This workflow comes heavily from the [GitHub Flow](https://guides.github.com/introduction/flow/index.html) +and the [GitLab Flow](https://docs.gitlab.com/ee/topics/gitlab_flow.html). + +These workflows are based on branches coming out of master and being +merged back into the master on completion. I found the [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) +to be too complicated for my personal projects and extending the GitHub Flow +with a set of stable branches and tags has worked really well at work, like +described in the [Release branches with GitLab flow](https://docs.gitlab.com/ee/topics/gitlab_flow.html#release-branches-with-gitlab-flow). + + +### 1. Create a new branch. + +I always create a new branch when starting something. +This allows switching easily between tasks if some urgent work is coming in without +having to pile up modifications in the stash. + +When working of personal projects, I tend to be more lax about these branches, +creating a branch that will contain more than one change and review them +all in one go afterwards. + +Why create a branch and not commit directly into the master ? Because you +want tests to check that your commits are correct before the changes are +written in stone. A branch can be modified or deleted, the master branch +cannot. Even for small projects, I find that branches allow you to work +more peacefully, allowing you to iterate on your work. + +A branch is created by `git checkout -b my-branch` and can immediately be used +to commit things. + +### 2. Commit often. +This advice comes everytime on Git: You can commit anytime, anything. +It is way easier to squash commits together further down the line than it is to +split a commit 2 days after the code was written. + +Your commits are still local only so have no fear committing incomplete or +what you consider sub-par code that you will refine later. With that come the next points. + +### 3. Add only the needed files. +With Git you can and must add files before +your commit. When working on large projects, you will modify multiple files. +When commiting you can add one file to the index, commit changes to this file, +add the second file to the index and commit these changes in a second commit. +Git add also allows you to add only parts of a file with `git add -p`. This +can be useful if you forgot to commit a step before starting work on the +next step. + +### 4. Write useful commit messages. +Even though your commits are not yet published, commit messages are also +useful for you. + +I won't give you advice on how to write a commit message as this depends +on the projects and the team I'm working on, but remember that a commit +message is something to describe *what* you did and *why*. + +Here are some rules I like to follow : + +1. Write a short description of *why* and *what*. Your commit message +should be short but explain both. A `git log --oneline` should produce +a readable log that tells you what happened. +2. Be precise. You polished up your cache prototype ? Don't write *General polishing*, +say *what* and *why*, like *Polishing the Redis caching prototype*. +3. Be concise. You fixed tests that were failing because of the moon and +planets alignment and solar flares ? Don't write a novel on one line like +*Adding back the SmurfVillageTest after fixing the planet alignement and +the 100th Smurf was introduced through a mirror and everybody danced happily +ever after*. The longest I would go for is *Fixed failing SmurfVillageTest for 100th Smurf* +4. Use the other lines. You can do a multi-line commit message if you need +to explain the context in details. Treat your commit like you would an +email: Short subject, Long message if needed. +The Linux kernel is generally a really good example of good long commit messages, like +[cramfs: fix usage on non-MTD device](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3e5aeec0e267d4422a4e740ce723549a3098a4d1) +or +[bpf, x86: Emit patchable direct jump as tail call](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=428d5df1fa4f28daf622c48dd19da35585c9053c). +5. In any case, don't write messages like *Update presentation* in 10 +different commits, or even worse *Fix stuff*. It's not useful, neither for +your nor your colleagues. + +Here are some links about commit messages. Don't ignore this, in my opinion +it is a really important part of every VCS: + +* [Commit Often, Perfect Later, Publish Once - Do make useful commit messages](https://sethrobertson.github.io/GitBestPractices/#usemsg) +* [A Note About Git Commit Messages](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) +* [Introduction: Why good commit messages matter](https://chris.beams.io/posts/git-commit/) + +### 5. Refine your commits. +At this point, if you coded as a normal human being, +you will have a large number of commits, with some that introduce new +small features, like *Add cache to build process*, some that fix typos, +like *Fix typo in cache configuration key*, some others that add some missing +library, like *Oops, forgot to add the Redis library to the pom*. Nothing +to worry about, to err is human, computers are there to catch them and allow +you to fix them easily. + +Before pushing the work online, I like to [hide the sausage making](https://sethrobertson.github.io/GitBestPractices/#sausage). +Personally, I find that the downsides are outweighted by the fact that you +reduce the time needed to commit things while coding and organize stuff +once your mind is free of code-related thoughts. + +These commits are not useful for other people, they are only there because +you made a mistake. No shame in that but the reviewers don't need to see +these, they need to have a clear view of *what* and *why*. + +The cache library was added because we added a cache, the configuration +key is there because we added a cache. The commits should reflect our work, +not your mistakes. In this example, I would only keep one commit, *Add cache +to the build process* and squash the errors into it. + +At this step, I like to rebase my branch on the current master +with `git rebase -i origin/master` so that I can reorder and squash commits +as well as get the latest changes in my branch. + +### 6. Rebase your branch +Usually, before your work on a feature is finished, a number of changes +landed on the master branch: New features, fixes, perhaps new tests if +you are lucky. Before pushing, I thus do a quick `git fetch && git rebase origin/master`, +just so that my branch is up to date with the branch I will merge to. + +With the lastest changes in my branch, I like to run the test suite one +last time. + +### 7. Check your commit messages +Before pushing, I like to do a quick `git log --oneline` to check my +commits. + +Your change descriptions should make sense, you should be able at this +point to remember for each commit what changed and why you did it and the +message should reflect this. + +If one commit message is vague, this is the last chance to rewrite it. I +usually do that with an interactive rebase: `git rebase origin/master -i`. + +### 8. Pushing the branch +Once everything is in order, the branch can be pushed, a Pull Request/Merge request/Review request +can be opened and other people brought into the changes. + +### 9. Review +If you work in a team you will have a code review step before merging changes. +I like to see this as a step to ensure that I did not miss anything. When you +code your fix or features, it is really easy to forget some corner-case or +some business requirement that was introduced by an another customer to a +colleague. I like to see the review step as peace of mind that you did not +forget something important and that if you forgot something, it was not +that important as 4 eyes did not spot it. + +The review is also a way for your colleagues to keep up to date with your +work. Whatever is in the master branch has been seen by 2 people and should +be understood by 2 people. It's important to have someone else that can +fix that part of the code in case you are absent. + +These people will need to quickly know what changed and why you changed +that. Usually the tooling will quickly allow people to check what changed, +comment on those changes and request improvements. The why will come from +your commit messages. + +I also like to keep this step even when working alone. I review my own +code to ensure that the changes are clear and that I committed everything +I needed to and only what I wanted to. + +### 10. Changes +Usually you will have to change some parts after the review. It can be +because you remembered something walking down the corridor to get tea or +because your colleagues saw possible improvements. + +For these changes, I like to follow the same procedure as before. Write +the changes, commit them, fix the old commits to keep the log clean. I +see the review as part of the work, not something that comes after +and will be recorded in the logs. In short : + +* A new feature is requested by the reviewer ? New commit. +* A typo must be fixed ? Fix the commit that introduced it. +* Some CI test fails ? Fix the commit that introduced the regression or introduce +a new commit to fix the test. + +## The dangers +This workflow is `rebase` heavy. If you have some modifications that +conflict with your changes, you will have to resolve the conflicts, perhaps +on multiple commits during the rebase, with the possible errors that will +come out of it. If the conflicts are too much, you can always abort the +rebase and try to reorder your commits to reduce the conflicts, if possible. + +The fact that you rebase will also hide the origin of problems coming from +your parent branch. If you pull code with failing tests, you will have +nothing in the history that tells you that your code worked before pulling +the changes. Only your memory (and the `reflog` but who checks the `reflog` ?) +will tell you that it worked before, there are no commit marking the before +and the after like there would be on a `merge` workflow. On tools like +GitLab, you will see that there were pipelines that were succeeding and then +a pipeline failing but you will need to check the changes between the +succeeding and the failing pipelines. + +If you are not alone on your branch, rebasing can cause a lot of issues when +pulling and pushing with two rebased branches with different commits in it. +Be sure to only rebase when everyone has committed everything and the branch +is ready to be reviewed and merged. + +## Git aliases +Since I do some operations a number of times each day, I like to simplify +them by using aliases in my `.gitconfig`. + +The first two are aliases to check the logs before pushing the changes. +They print a one-liner for each commit, one without merge commits, the +other with merge commits and a graph of the branches. + +The last two are aliases for branch creation and publication. Instead +having to know whether I have to create a new branch or can directly +checkout an existing branch, I wrote this alias to `go` to the branch, +creating it if needed. The `publish` alias allows to push a branch created +locally to the origin without having to specify anything. + +The `commit-oups` is a short-hand to amend the last commit without changing +the commit message. It happens often that I forgot to add a file to the +index, or committed too early, or forgot to run the tests, or forgot +a library. This alias allows me to do a `git add -u && git commit-oups` +in these cases. (Yes, Oups is french for Oops). + +```ini +[alias] + # Shorthand to print a graph log with oneliner commit messages. + glog = log --graph --pretty=format:'%C(yellow)[%ad]%C(reset) %C(green)[%h]%C(reset) %s %C(red)[%an]%C(blue)%d%C(reset)' --date=short + + # Shorthand to print a log with onliner commit messages ignoring merge commits. + slog = log --no-merges --pretty=format:'%C(yellow)[%ad]%C(reset) %C(green)[%h]%C(reset) %s %C(red)[%an]%C(blue)%d%C(reset)' --date=short + + # Prints out the current branch. This alias is used for other aliases. + branch-name = "!git rev-parse --abbrev-ref HEAD" + + # Shorthand to amend the last commit without changing the commit message. + commit-oups = commit --amend --no-edit + + # Shorthand to facilitate the remote creation of new branches. This allow + # the user to push a new branch on the origin easily. + publish = "!git push -u origin $(git branch-name)" + + # Shorthand to faciliate the creation of new branches. This switch to + # the given branch, creating it if necessary. + go = "!go() { git checkout -b $1 2> /dev/null|| git checkout $1; }; go" + +``` + +## Releases +This article only detailled the daily work on a feature and the +merge but did not go into detail into the release process. This is deliberate +as every release is different. In my personal projects alone I have multiple +ways to represent releases. + +On my blog there are no releases, everything on the master is published +as they are merged. + +On my Kubernetes project, a release is something more precise but not +static. I want to be sure that it works but it can be updated easily. +It is thus represented by a single stable branch that I merge the master +onto once I want to deploy the changes. + +On my keyboard project, a release is something really static +as it represents a PCB, an object that cannot be updated easily. It is +thus a tag with the PCB order reference. Once the firmware is introduced, +this could change with the introduction of a stable branch that will follow +the changes to the firmware and configuration. Or I could continue using tags, +this will be decided once the hardware is finished. + +## Conclusion +As always with Git, the tool is so powerful that more or less any workflow +can work with it. There are a number of possible variations on this, with +each team having a favorite way of doing things. + +In this article I did not talk about tooling but nowadays with CI/CD +being more and more important, tooling is an important part of the workflow. +Tests will need to be run on branches, perhaps `stable` branches will +have more tests that `feature` branches due to server/time/financial limitations. +Perhaps you have Continuous Deployment of stable branches, perhaps you want +to Continuously reDeploy a developement server when code is merged on the +master. + +Your tooling will need a clear a clear flow. If you have conventions that +new features are developed on branches that have a `feature/` prefix, everybody +must follow that otherwise the work to reconcile this in your tooling will +be daunting for the developer in charge of these tools. +