Branching and Commiting
Branch and commit and whatnot
Now that your project is setup and GitButler is installed and configured, you can start branching and committing.
The Simple Flow
Let’s begin with a simple workflow, one that should be familiar to Git users. We will:
- Do some work
- Create a new branch
- Commit to that branch
Status
Let’s begin by seeing what the status of the working directory is by running but status. This will tell you a little more than git status, it will list:
- All files in your working directory that differ from your base branch (
origin/main) the last time you updated it that aren’t assigned to a branch - A list of the active branches that you have and
- All assigned file changes in each branch
- All commits in each branch
So it's sort of like a combination of git status and a shortlog of what is on your branches that is not on origin/master.
It looks something like this:
╭┄00 [Unassigned Changes] ┊ wu M README-es.md 🔒 da42d06 ┊ te A README.new.md ┊ rt A app/views/bookmarks/index.html.erb ┊ ┊╭┄t5 [gemfile-fixes] ┊● da42d06 Add Spanish README and bookmarks feature ┊● fdbd753 just the one ┊│ ┊├┄qz [feature-bookmarks] ┊● 223fdd6 feat: Add bookmarks table to store user-tweet rela ├╯ ┊ ┊╭┄q6 [sc-branch-26] ┊● f55a30e add bookmark model and associations ├╯ ┊ ● 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
Here we can see three applied branches: gemfile-fixes stacked on feature-bookmarks and independent sc-branch-26. There are also three unassigned files.
Create a Branch
Let’s look at a very simple case first. Let’s say we’ve just modified some files and don’t have a branch yet. Our status might look like this:
╭┄00 [Unassigned Changes] ┊ nx M Gemfile ┊ ie A app/controllers/bookmarks_controller.rb ┊ xw A app/models/bookmark.rb ┊ ku M app/models/user.rb ┊ rt A app/views/bookmarks/index.html.erb ┊ rv M config/routes.rb ┊ ● 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
Now let’s say that we want to put those unassigned file changes into a commit on a new branch called user-bookmarks.
To do this, you can use the but branch new <branch-name> command.
Created branch user-bookmarks
Now if you run but status you can see your new empty branch:
╭┄00 [Unassigned Changes] ┊ nx M Gemfile ┊ ie A app/controllers/bookmarks_controller.rb ┊ xw A app/models/bookmark.rb ┊ ku M app/models/user.rb ┊ rt A app/views/bookmarks/index.html.erb ┊ rv M config/routes.rb ┊ ┊╭┄nd [user-bookmarks] (no commits) ├╯ ┊ ● 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
Commit to a Branch
Now we can commit our unassigned changes to that branch. You can simply assign your changes to the branch first to commit later (we’ll cover that later in Rubbing), but for now let’s keep it simple and just commit them directly using the but commit command.
Created commit 3a1658b on branch user-bookmarks
If you don’t specify the -m commit message, GitButler will try to open an editor with a tempfile where you can write a longer commit message. It will use the $EDITOR environment variable if it’s set, or the core.editor Git or GitButler config setting, or it will prompt you for a command to run if you’re in an interactive terminal.
Now our status looks like this, with all unassigned files in a new commit on our new branch:
╭┄00 [Unassigned Changes] ┊ ┊╭┄nd [user-bookmarks] ┊● 3a1658b all the user bookmarks ├╯ ┊ ● 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
Stacked and Parallel Branches
Ok, that’s the simple case, pretty straightforward. However, GitButler can also do some pretty cool things that Git either cannot do or struggles with, namely having multiple active branches that you can work on in parallel, and managing stacked branches. That is, both multiple independent and dependent active branches.
Parallel Branches
Parallel branches is very simple, you can create multiple simultaneously active branches that you can assign and commit changes to in your workspace.
To create a parallel branch, you simply create a new branch the same way we did before. Let’s say that we want to create a liked-tweets branch alongside our existing user-bookmarks. We simply run the same but branch new command again:
Created branch liked-tweets
Now if we run but status we can see our previous branch and our new empty branch.
╭┄00 [Unassigned Changes] ┊ t3 M app/controllers/likes_controller.rb ┊ ou M app/models/like.rb ┊ ┊╭┄nd [user-bookmarks] ┊● 3a1658b all the user bookmarks ├╯ ┊ ┊╭┄u1 [liked-tweets] (no commits) ├╯ ┊ ● 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
We can see our previous branch and the commit we made, our new empty branch and a couple of modified files. Now we can commit the unassigned changes to that branch with but commit -m "liked tweets changes" liked-tweets
Created commit 87b1f79 on branch liked-tweets
And now we have one commit in each lane.
╭┄00 [Unassigned Changes] ┊ ┊╭┄nd [user-bookmarks] ┊● 3a1658b all the user bookmarks ├╯ ┊ ┊╭┄u1 [liked-tweets] ┊● 87b1f79 liked tweets changes ├╯ ┊ ● 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
Here we specified the entire branch name as the commit target (as there is more than one), but you can also use the two character short code that is next to each one.
If you don’t specify a branch identifier and you have more than one active branch, then GitButler will prompt you for which branch you wish to commit the unassigned changes to.
We can also see which files were modified in each commit with the --files or -f option to but status:
╭┄00 [Unassigned Changes] ┊ ┊╭┄nd [user-bookmarks] ┊● 3a1658b all the user bookmarks ┊│ rn M Gemfile ┊│ ui A app/controllers/bookmarks_controller.rb ┊│ p7 A app/models/bookmark.rb ┊│ oq M app/models/user.rb ┊│ nr A app/views/bookmarks/index.html.erb ┊│ jq M config/routes.rb ├╯ ┊ ┊╭┄u1 [liked-tweets] ┊● 87b1f79 liked tweets changes ┊│ xn M app/controllers/likes_controller.rb ┊│ o1 M app/models/like.rb ├╯ ┊ ● 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
Stacked Branches
The other way you can create new branches is to make them stacked, that is, one depends on another one and has to be merged in that order.
To create a new stacked branch in GitButler, you can run but branch new with a target branch ID. If we go back in time and instead stack our liked-tweets branch, we can make it dependent on the user-bookmarks branch by providing it as a stacking "anchor" with -a option:
Created branch liked-tweets-stacked
╭┄00 [Unassigned Changes] ┊ nx M Gemfile ┊ ku M app/models/user.rb ┊ rv M config/routes.rb ┊ ┊╭┄kq [liked-tweets-stacked] (no commits) ┊│ ┊├┄nd [user-bookmarks] ┊● e677a2e user bookmarks feature ├╯ ┊ ● 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
Now we can commit to our stacked branch.
Created commit 71f4fe2 on branch liked-tweets-stacked
╭┄00 [Unassigned Changes] ┊ ┊╭┄kq [liked-tweets-stacked] ┊● 71f4fe2 liked tweets changes ┊│ ┊├┄nd [user-bookmarks] ┊● e677a2e user bookmarks feature ├╯ ┊ ● 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
Now if you push to a forge, GitButler will set up the reviews (Pull Request or Merge Request) as a stacked request, where user-bookmarks has to be merged either before or with liked-tweets but they can be reviewed independently.
Assigning and Committing Changes
The other way to commit to a branch is to explicitly assign changes to it. This is somewhat like running git add in Git, where you’re staging some changes for a future commit. However, unlike Git where you have to do this or override it with -a or something, the default in GitButler is to commit all changes by default and only leave out unassigned changes with the flag -o or --only.
Assigning Changes
So, how do we assign changes to a specific branch and then only commit those changes?
Let’s look at an example but status with six modified files and two empty, parallel branches and assign and commit one file to each branch as a separate commit.
╭┄00 [Unassigned Changes] ┊ nx M Gemfile ┊ ie A app/controllers/bookmarks_controller.rb ┊ xw A app/models/bookmark.rb ┊ ku M app/models/user.rb ┊ rt A app/views/bookmarks/index.html.erb ┊ rv M config/routes.rb ┊ ┊╭┄nd [user-bookmarks] (no commits) ├╯ ┊ ┊╭┄h4 [user-changes] (no commits) ├╯ ┊ ● 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
We will assign each file to a different branch and then see the result. We assign file changes to branches using the but rub command, which combines things. We'll go more into all that rubbing can do later.
You can either rub the file identifier that you see next to each file, or all or part of the file path. For example, in this case to identify the app/models/bookmark.rb file, you can do any of:
xwapp/models/bookmarkapp/models/bookmark.rb
In order to differentiate a shortcode from a path, a shortcode is exactly 2 characters and a path needs to be at least 3. This is the same pattern matching used for the branch ID too.
So lets rub the bookmark changes into the bookmarks branch:
Assigned app/models/bookmark.rb → [user-bookmarks]. Assigned app/controllers/bookmarks_controller.rb → [user-bookmarks]. Assigned app/views/bookmarks/index.html.erb → [user-bookmarks].
Now let's rub the user changes into the user-changes branch:
Assigned app/models/user.rb → [user-changes].
Now we have some file changes assigned to each branch and still some unassigned changes:
╭┄00 [Unassigned Changes] ┊ nx M Gemfile ┊ rv M config/routes.rb ┊ ┊╭┄nd [user-bookmarks] (no commits) ┊│ md A app/controllers/bookmarks_controller.rb ┊│ pe A app/models/bookmark.rb ┊│ v0 A app/views/bookmarks/index.html.erb ├╯ ┊ ┊╭┄h4 [user-changes] (no commits) ┊│ kx M app/models/user.rb ├╯ ┊ ● 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
Now, if we want to create a commit in the user-bookmarks branch, we can either run but commit nd which will create a commit with the files assigned as well as both files that are unassigned, but not the file assigned to the user-changes lane.
Or, we can make a commit with only the assigned files in user-bookmarks by using the -o option to but commit.
Created commit d9d0b94 on branch user-changes
Now if we look at our status we can see a commit on our branch instead of the assigned changes:
╭┄00 [Unassigned Changes] ┊ nx M Gemfile ┊ rv M config/routes.rb ┊ ┊╭┄nd [user-bookmarks] (no commits) ┊│ md A app/controllers/bookmarks_controller.rb ┊│ pe A app/models/bookmark.rb ┊│ v0 A app/views/bookmarks/index.html.erb ├╯ ┊ ┊╭┄h4 [user-changes] ┊● d9d0b94 liked tweets view ├╯ ┊ ● 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
Now let's commit all the rest of the changes (assigned and unassigned) to our other branch:
Created commit e36adcf on branch user-bookmarks
╭┄00 [Unassigned Changes] ┊ ┊╭┄nd [user-bookmarks] ┊● e36adcf bookmarks stuff ├╯ ┊ ┊╭┄h4 [user-changes] ┊● d9d0b94 liked tweets view ├╯ ┊ ● 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
Assigning Ranges
If you happen to have a large number of changes, you can also use ranges or lists for rubbing assignment. So for example, if we go back to this status:
╭┄00 [Unassigned Changes] ┊ nx M Gemfile ┊ ie A app/controllers/bookmarks_controller.rb ┊ xw A app/models/bookmark.rb ┊ ku M app/models/user.rb ┊ rt A app/views/bookmarks/index.html.erb ┊ rv M config/routes.rb ┊ ┊╭┄nd [user-bookmarks] (no commits) ├╯ ┊ ┊╭┄h4 [user-changes] (no commits) ├╯ ┊ ● 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
Then you can assign the first, third, and fifth file to a branch with:
Assigned Gemfile → [user-bookmarks]. Assigned app/models/bookmark.rb → [user-bookmarks]. Assigned app/views/bookmarks/index.html.erb → [user-bookmarks].
╭┄00 [Unassigned Changes] ┊ ie A app/controllers/bookmarks_controller.rb ┊ ku M app/models/user.rb ┊ rv M config/routes.rb ┊ ┊╭┄nd [user-bookmarks] (no commits) ┊│ ro M Gemfile ┊│ pe A app/models/bookmark.rb ┊│ v0 A app/views/bookmarks/index.html.erb ├╯ ┊ ┊╭┄h4 [user-changes] (no commits) ├╯ ┊ ● 204e309 (common base) [origin/main] Merge pull request #10 from schacon/sc-description
Last updated on