00:00:12.139
Hello everyone, I'm going to talk about Git rebase today. As developers, we keep attempting to manage complexity in our projects because they can get very intricate very quickly, even in small teams with rapidly changing codebases.
00:00:28.350
Being able to communicate how and why our code evolves over time is crucial. We spend a lot of time considering the names of variables, methods, and functions, as well as our code architecture and design. We write automated tests that act as documentation for our code. All of these efforts help convey the intent of our programs.
00:00:41.449
Another tool to help communicate our intent is a version control system. In this talk, I will be using Git. Git maintains a commit history which is useful for documenting intent. The history is a living, ever-changing, searchable record that tells the story of how and why our code is the way it is.
00:01:00.389
Effectively documenting code using Git is just as important as being able to ship features, write clean code, or craft readable tests. Git history is retained indefinitely unless you actively remove those commits. These commits are tied to the code changes they describe, and when there are code changes, a new commit message will be written.
00:01:26.789
This is great because the documentation never becomes stale; it lasts exactly as long as the code it references. Furthermore, commit messages do not clutter up the code, yet they are just a step away if we need to reference them.
00:01:45.030
Every commit message we write is available to anyone on the team who clones the repository and can be easily searched in many ways. For instance, we can use 'git log --grep' with a regex to search all the context of the commit messages.
00:02:01.100
Before I proceed to discuss rebasing, I would like to introduce three fundamental practices when using Git. The first principle is to make small commits. The most important rule, then, is to ensure your commits involve a single change to your codebase.
00:02:26.980
To illustrate this, consider an example of a commit that addresses multiple issues at once. It's difficult to revert just one change separately, so it would have been better if this commit had been split into smaller, distinct commits.
00:02:39.640
Think about 'smaller commits' as a minimum viable commit. What's the smallest useful change you can make to your codebase? Another rule of thumb is to avoid using 'and' in your commit messages. If you've done A and B, then ideally, this should reflect in two separate commits.
00:03:09.340
If you have several changes that have not been staged yet, you can use the '-p' flag when adding files to the index. The '-p' option allows you to interactively choose hunks of changes between the index and the working tree and add them to the index. This gives you the opportunity to review the differences before finalizing the changes.
00:03:34.959
The second principle is to write good commit messages. Although your code should be self-documenting, it doesn't explain why the code is the way it is or how it came to be. Since we have already broken the changes down into small, purposeful commits, we should have a good idea of the value of each one.
00:03:56.110
Some people write commit messages that merely record what actually happened, but we can see that in the code. A better way is to describe the story of how the project evolved. We want to be in the latter camp. A good commit history gives us clarity of intent, thought, and the ability to reason about our code.
00:04:09.459
I find it helpful to look at the commits from the perspective of another developer, or even myself, six months from now. What questions might they have when viewing my code changes? What might not be immediately obvious?
00:04:33.340
Let’s explore some examples of poor commit messages. There are many out there. On the other hand, let's look at some better examples. Commit messages should be written as if you're explaining the change to a colleague sitting next to you who has no idea what's happening. Provide as much context and useful detail as possible.
00:05:17.460
Attempt to answer five key questions: Why is this change necessary? How does it address the issue? What side effects might this change have? Were other solutions considered? And include a reference to a discussion, resource, or ticket.
00:05:51.009
Here’s a suggested template: have a short, one-line title. When reviewing commits in list format, it's helpful to see a good commit subject written in the active voice. Follow this with an explanation of why the change was made. If people want to know how to change it in the future, they need to understand the intent behind the changes.
00:06:30.999
The description can be informal; it doesn't have to adhere strictly to the present tense command structure. In fact, it's often better written informally as a message to your team, explaining the why behind the change.
00:06:54.490
Lastly, when writing a commit message, remember that you know more about the changes being made and how to improve the code than anyone else.
00:07:00.639
It can be useful to outline some of the context and alternative approaches you considered, and also include a link to a tracking system.
00:07:14.529
Here's an example of a commit message that I used in a recent project. You can see the one-line title, a link to the relevant Trello card, and an explanation of what was done and why.
00:07:20.229
Principle number three is to work in small feature branches. Again, keep them focused and single-purpose. The core idea behind feature branch workflow is that all future development should take place in dedicated branches instead of the master branch.
00:07:28.180
This encapsulation makes it easy for multiple developers to work on a particular feature without disturbing the main codebase, ensuring that the master branch will never contain broken code.
00:07:45.220
This is a tremendous advantage for a continuous integration environment. Working in feature branches allows for the use of pull requests, which initiate discussions around the code and give other developers the opportunity to review a feature before it gets integrated into the official project.
00:08:02.260
If we get stuck while developing a feature, we can open a pull request asking our teammates for suggestions. This makes it incredibly easy for the team to comment on each other's work and facilitates better communication about what everyone is working on.
00:08:22.539
A suggested workflow would start with creating a new branch. When I create a new branch, I start with my initials to denote what I'm working on. I then work on the branch, make some changes, check the status, add some files, and finally commit.
00:08:34.150
I push the changes to my branch using the command 'git push origin HEAD', which creates a new branch for me and pushes the existing branch. If I’ve already created one, it simply updates that branch. After creating the pull request, I implement changes based on the feedback.
00:09:02.230
Then, I rebase master onto my current branch and push the updated branch back up. Since we rebased the current branch and rewritten the history, I need to use the '-f' (force) flag to push it back to origin.
00:09:37.150
Note that pushing should only be done on feature branches to avoid rewriting history on master, which can create complications for others who need to fetch your changes.
00:09:55.990
Finally, I merge into master using the fast-forward option. I check out master and execute 'git merge --fast-forward-only' along with my branch name. I’ll discuss the importance of this approach shortly.
00:10:13.630
Next, let's differentiate between merging and rebasing. Both 'git merge' and 'git rebase' serve the same purpose: they incorporate commits from one Git branch into another. The key distinction lies in how this is accomplished.
00:10:35.980
By default, the merge command creates a merge commit that ties together the histories of two branches, resulting in a commit history that can become tangled and difficult to read.
00:10:55.089
This can become an issue if multiple feature branches are developed simultaneously and team members rely on logs for insights into the project.
00:11:19.750
Using merge commits can clutter our logs and obscure the flow of the project history. Let's look at how rebasing changes this dynamic.
00:11:39.670
Interactive rebasing allows us to alter commits as they transition to a new branch, which is even more powerful than an automated rebase. This gives you full control over the branch's commit history, allowing for cleanup before merging a feature branch into master.
00:11:57.770
To initiate an interactive rebase, use '-i', and while rebasing you have options to handle conflicts: either continue or abort.
00:12:04.880
Let’s demonstrate this. First, I’ll create a directory for co-working, and then I will create a couple of branches.
00:12:31.120
Let's create a file; if we look at it, we'll see that we have some text. I will initialize a git repository, add the file, and this is how we start with a Git repo.
00:12:56.620
Next, I'll create a new branch using 'git checkout -b' with my initials, and for this example, I'll call it 'fix conference'. I will then make a change, converting text to title case.
00:13:14.890
After making that change, I'll add the file and commit it, indicating that I've fixed the title case. Now I’ll check out the master branch.
00:13:29.980
Next, I’ll create yet another branch for this exercise. This new branch will help us see how Git handles changes.
00:13:49.990
Once I'm on the third branch, I will open a file and add '2019' to it. After checking the differences with 'git diff', I will make my changes and commit it.
00:14:02.110
Now I want to merge these changes into master. However, sometimes this can lead to conflicts. Let’s see how we can resolve this.
00:14:26.820
If we try to merge 'fixed conference' into master and have conflicting changes, we need to rebase first before merging.
00:14:39.420
I will first reset the changes, and once in a clean state, I'll ensure all conflicts are fixed. Then I can attempt to rebase master.
00:15:04.970
This allows me to undergo a clean interactive rebase, reorder commits, or squash them.
00:15:34.020
After that, any conflicts can be resolved as needed, and I can continue with the rebase.
00:15:50.320
The process will result in a linear, cleaner commit history without unnecessary noise from merge commits.
00:16:11.640
Next, if we had a commit that really should fix something, we can use 'fixup' commits to simplify and condense our history.
00:16:26.820
This method ensures only useful commits are retained.
00:16:54.310
When dealing with multiple sessions or various changes, using autocommit and continuous rebasing can streamline the process significantly.
00:17:05.990
Finally, I want to highlight the importance of keeping commits clear and purposeful. This not only aids in maintaining a clean history but also contributes to easier collaboration across teams.
00:17:27.700
Remember to check the initial config for auto-squashing and continual tweaks on commit structures.
00:17:48.899
Auto-squashing allows for easier cleanup and prevents minor changes from cluttering up the commit log.
00:18:09.310
By ensuring that old branches are well-matched to their intended features, we can enhance organization within our projects.
00:18:32.260
Rebasing frequently is a good practice, but remember never to use ‘rebase’ on public shared branches because that rewrites history and can disrupt others' work.
00:18:51.400
Finally, it’s crucial to spend effort ensuring that our commits are well-factored, just as we do with our code and tests. This practice will save both time and effort for the team in the long run.
00:19:09.160
Thank you for listening, and I hope you found this helpful.