Version Control

Story Telling with Git rebase

Story Telling with Git rebase

by Elle Meredith

In this presentation titled "Story Telling with Git rebase" from RubyConf 2019, speaker Elle Meredith discusses the importance of effectively managing complexity in software projects through the use of Git, a version control system. She explains that as projects grow, maintaining clear communication regarding the intent behind code changes becomes crucial, and Git serves as a tool for documenting this intent through its commit history.

Key points discussed include:

- The Role of Version Control: Git maintains a searchable history of commits that document how and why code evolves, providing context that outlasts the code itself.

- Best Practices for Git: Three fundamental practices are emphasized:

- Small Commits: Making distinct changes in small commits enhances clarity and makes it easier to revert changes if necessary.

- Good Commit Messages: Commit messages should narrate the story behind changes, addressing important questions such as why changes were made and what alternatives were considered. A suggested structure includes a one-line title, a brief explanation of the change, and links to related discussions or resources.

- Feature Branch Workflow: Development should take place in dedicated branches to avoid clutter in the master branch, allowing for clearer integration and better collaboration via pull requests.

- Rebasing vs. Merging: Meredith discusses the differences between merging and rebasing in Git, noting that merging can create cluttered commit histories with merge commits, while rebasing results in a cleaner and linear commit history, enhancing clarity in project evolution.

- Interactive Rebasing: The process of interactive rebasing is elaborated upon, showcasing how to reorder commits, handle merge conflicts, and use techniques like auto-squashing to maintain a tidy project history.

- Cautions with Rebasing: It is important to avoid rebasing public shared branches as this can rewrite history and disrupt workflows for other developers.

Elle concludes the presentation by reiterating the significance of clear and purposeful commits, which not only aids in maintaining a well-structured project but also facilitates smoother collaboration among team members. She emphasizes investing time in crafting commit messages and histories as it pays off in easier future maintenance and collaboration.

Overall, this talk provides a comprehensive overview of how to utilize Git effectively to simplify the development process and improve team communication in software projects.

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.