diff --git a/posts/git-amend-fixup/index.qmd b/posts/git-amend-fixup/index.qmd new file mode 100644 index 00000000..e109174d --- /dev/null +++ b/posts/git-amend-fixup/index.qmd @@ -0,0 +1,219 @@ +--- +title: "Git - Making Amends and Fixing things up" +date: "2024-03-08" +categories: [git, amend, fixup] +image: https://live.staticflickr.com/65535/53448512934_c045c232c9_k.jpg +from: markdown+emoji +toc: true +toc-depth: 4 +toc-location: right +execute: + code_fold: true + code_link: true + code_tools: true + fig-cap-location: top + tbl-cap-location: top + warning: false +--- + +[Git][git] is the world's most popular version control software out there at the moment and if you write code and +version control it the chances are you are using it. It's a complex tool though and there are a bewildering array of +options. In this short post we will look at some options for changing commits that have been made. + +![[Crown Shy Trees by Me](https://www.flickr.com/photos/slackline/53448512934/in/datetaken/)](https://live.staticflickr.com/65535/53448512934_c045c232c9_k.jpg) + +## Python Examples + +We'll use my [pytest-examples](https://github.com/ns-rse/pytest-examples) as an example for this work but you can do +this in any repository you have going. We'll clone the repository and make a new branch called `amend-fixup-tutorial`. + +``` bash +git clone git@github.com:ns-rse/pytest-examples.git +cd pytest-examples +git switch -c amend-fixup-tutorial + Switched to a new branch 'amend-fixup-tutorial' +``` + +## The Git Dance + +A typical Git work flow involves making some changes to one or more files (or adding a new one into the +repository). These are staged with `git add `, or you can use a shortcut `git add -u/--update` to add all +currently tracked files that have been modified if you are not adding anything new, before committing them with a +message using `git commit -m ""`. + +Lets add a simple `CONTRIBUTING.md` file to the repository. + +``` bash +echo "# Contributing\n\nContributions via pull requests are welcome." > CONTRIBUTING.md +git add CONTRIBUTING.md +git commit -m "Adding CONTRIBUTING.md" +``` + +``` bash +git log --oneline + 01191a2 (HEAD -> amend-fixup-tutorial) Adding CONTRIBUTING.md +``` + +This should be familiar to users of [Git][git] whether you use the command line interface (CLI), [Emacs'][emacs] amazing +Git porcelain [magit][magit] or any other tool such as [GitKraken][gitkraken] or the support in you IDE such as +[RStudio][rstudio] or VSCode. + +## Making Amends + +Sometimes you will have made a commit and you realise that you want to add more to it or perhaps you forgot to run your +test suite and find that on running it your tests fail so you need to make a correction. In this example we want to be +more explicit about how to make contributions and let people know they should fork the branch. + +``` bash +echo "\n Please make a fork of this repository, make your changes and open a Pull Request." >> CONTRIBUTING.md +``` + +Now you could make a second commit... + +``` bash +git add -u +git commit -m "Ask for PRs via fork in CONTRIBUTING.md" +``` + +``` bash +git log --oneline +9f0655b (HEAD -> amend-fixup-tutorial) Ask for PRs via fork in CONTRIBUTING.md +01191a2 Adding CONTRIBUTING.md +``` + +...and there is nothing wrong with that. However, Git history can get long and complicated when there are lots of small +commits, because these two changes to `CONTRIBUTING.md` are essentially the same piece of work and if we'd been thinking +clearly we would have written about making forks in the first place and made a single commit. + +Fortunately Git can help here as there is the `git commit --amend` option which adds the staged changes to the last +commit and allows you to edit the last commit message (if nothing is currently staged then you will be prompted to edit +the last commit message). We can undo the last commit using `git reset HEAD~1` and instead amend the first commit that +added the `CONTRIBUTING.md` + +``` bash +git add -u +git commit --amend +``` + +``` bash +git log --oneline + 4fda15f (HEAD -> amend-fixup-tutorial) Adding CONTRIBUTING.md +cat CONTRIBUTING.md +# Contributing + +Contributions via pull requests are welcome. + +Please make a fork of this repository, make your changes and open a Pull Request. +``` + +We now have one commit which contains the new `CONTRIBUTING.md` file that contains all the changes we wished to have +in the file in the first place and our Git history is slightly more compact. + +## Fixing things up + +Amending commits is great providing the commit you want to change is the last commit you made (i.e. `HEAD`). But +sometimes you might wish to correct a commit further back in your history and `git commit --amend` is of no use +here. Git can however help here with the `git commit --fixup` command which allows you to mark a commit as being a "fix +up" of an older commit. These can then be autosquashed via an interactive Git rebase. + +Let's add a few empty commits to our `amend-fixup-tutorial` branch to so we can do this. + +``` bash +git commit --allow-empty -m "Empty commit for demonstration purposes" +git commit --allow-empty -m "Another empty commit for demonstration purposes" +``` + +```bash +git log --oneline + 8061221 (HEAD -> amend-fixup-tutorial) Another empty commit for demonstration purposes + 65587ce Empty commit for demonstration purposes + 4fda15f Adding CONTRIBUTING.md +``` + +And let's expand our `CONTRIBUTING.md` file further. + +``` bash +echo "\nPlease note this repository uses [pre-commit](https://pre-commit.com) to lint the Python code and Markdown files." >> CONTRIBUTING.md +``` + +We want to merge this commit with the first one we made in this tutorial using `git commit --fixup`. To do this we need +to know the hash (`4fda15f` see output from above `git log`) or the relative reference of the commit we want which in +this case is `HEAD~2` as it is three commits back from the current `HEAD` (which is commit `0`, most indexing in +computing starts at `0` rather than `1`). Use _one_ for the following `git commit --fixup` commands (adjusting the hash +to yours if you are using that option, you can find this using `git log --oneline`). + +``` bash +git add -u +git commit --fixup 4fda15f +git commit --fixup HEAD~2 +``` + +We see the commit we have just made starts with `fixup!` and is then followed by the commit message that it is fixing. + +```bash +git log --oneline + 97711a4 (HEAD -> amend-fixup-tutorial) fixup! Adding CONTRIBUTING.md + 8061221 Another empty commit for demonstration purposes + 65587ce Empty commit for demonstration purposes + 4fda15f Adding CONTRIBUTING.md +``` + +The final step is to perform the automatic squashing via an interactive rebase, again you can either use the hash or the +relative reference. + +``` bash +git rebase -i --autosquash 4fda15f +git rebase -i --autosquash HEAD~2 +``` + +This will open the default editor and because the `--autosquash` option has been used it will already have marked the +commits that need combining with `fixup`. All you have to do is save the file and exit and we can check the history and +look at the contents of the file. + +**NB** If you find that the necessary commit _isn't_ already marked navigate to that line and delete `pick`. The lines +below the file you have open give instructions on how you can mark commits for different actions, in this case you can +replace `pick` with either `f` or `fixup`. Save and exit and the commits are squashed. + +```bash +git log --oneline + 0fda21e (HEAD -> amend-fixup-tutorial) Another empty commit for demonstration purposes + 65587ce Empty commit for demonstration purposes + 4fda15f Adding CONTRIBUTING.md +cat CONTRIBUTING.md + # Contributing + + Contributions via pull requests are welcome. + + Please make a fork of this repository, make your changes and open a Pull Request. + + Please note this repository uses [pre-commit](https://pre-commit.com) to lint the Python code and Markdown files. +``` + +And you're all done! If you were doing this for real on a repository you could now `git push` or continue your work. As +this was just an example we can switch branches back to `main` and force deletion of the branch we created. + +``` bash +git switch main +git branch -D amend-fixup-tutorial +``` + +## Conclusion + +Git has lots of commands to help you maintain a clean history by using `--amend` and `--fixup` flags to `git commit` and +in the later case then performing an interactive `git rebase -i`. This takes a little discipline to get into the +practice of but once in the habit of doing so it greatly improves the readability of the Git history and avoids +including commit messages such as `Fixing typo` / `Linting code` / `Fixing tests` / `I've gone mad!`. + +If all of this sounds completely unfamiliar to you but you would like to learn more about Git I can highly recommend the +introductory course developed by [Dr Anna Krystalli][anna] [Git and GitHub through GitKraken : From Zero to +Hero][gitzerohero]. This course is run regularly by myself and colleagues in [Research Software Engineering][rseshef] +for post-graduate researchers and staff at the University of Sheffield. + +[anna]: https://www.r-rse.eu/ +[emacs]: https://www.gnu.org/software/emacs/ +[git]: https://git-scm.com +[gitkraken]: https://www.gitkraken.com/ +[gitzerohero]: https://srse-git-github-zero2hero.netlify.app/ +[magit]: https://magit.vc/ +[rseshef]: https://rse.shef.ac.uk +[rstudio]: https://posit.co/products/open-source/rstudio/