Skip to content

Branches

Branches allow us to work on code within a repo without disrupting the golden copy or history of a repo. Branches exist as parallel records for code commits and can be merged back into the main branch, or left as independent feature sets. The head pointer can be placed anywhere, and represents where the next commit is to record data. Generally speaking, it is wise to work from only one branch at a time! Keep main main and do all new development on new branches!

List all branches

All branches are listed, but the asterisk indicates which branch is currently “checked out” (were the current head pointer is located).

Terminal window
$ git branch

Switch HEAD to a new branch

To use a different branch, we need to check it out using the checkout command. Note that it is not possible to switch branches with saved, but uncommitted changes to tracked files in the repo. Checking out a different branch places all new commits on the selected branch:

Terminal window
$ git checkout <branch name>

Note that if there is an M line associated with a branch checkout, it means there are unresolved changes. Refer to the commit chapter for difference checking and resolution.

Create a new branch

New branches are created from the branch that is currently checked out. Before creating a new branch, always ensure that the HEAD is currently pointing to the branch you wish to branch from using the checkout command.

New branches can be created with letters, underscores, and numbers. They cannot contain any spaces.

git branch <name>

Terminal window
$ git branch new_feature_221119

It is also possible to create a branch based on another branch outside of the current branch that is checked out. This is especially useful for remote branch creations.

git branch <new branch name> <base branch>

Terminal window
$ git branch Feature-ticket-2384 origin/main

Because its most common to start working with a new branch as soon as its created, Git contains an option to shorten the workflow. To create a branch and switch the HEAD in one step, use the -b option. Optionally, it is possible to use the start point option to create a new branch from a specified point (that may not be main):

git checkout -b <new branch name> <specified branch to start from>

Terminal window
$ git checkout -b new_feature_221119
...
$ git checkout -b new_feature_221119 main

Compare branches

The diff command is tree-ish, and since branches are also tree-ish, it works to compare branches, as well as changes to a working directory as used earlier. The comparison results are listed in relation to the tree-ish order listed. As a result, the output may differ depending on the order listed. To see whats new, list the old tree-ish first, like the first example below.

git diff <compare this>..<to this>

Terminal window
$ git diff main..new-feature
+<New feature code>
$ git diff new-feature..main
-<New feature code>

To see which branches have been merged, use the --merged option. This command displays all of the branches that have been merged into the currently selected branch. In the first example below, the --merged option shows that none of the subsequent branches have been merged to main. In the 2nd example, it appears that the 1st feature branch contains all commits from main, but not the 2nd feature branch. This is most helpful when run from the main branch to see what is included, or not included with the --no-merged option, which shows branches that are not included/accessible.

Terminal window
$ git branch
* main
main_1st_feature
main_2nd_feature
$ git branch --merged
* main

vs

Terminal window
$ git branch
main
* main_1st_feature
main_2nd_feature
$ git branch --merged
main
* main_1st_feature

Rename branch

Renaming branches is similar to how renaming files and directories works in Unix and Unix-like systems. This is to say that renaming is handled by the move logic, or the -m or —move option. If the filepath remains the same, the file/directory is simply renamed. By default, Git will use the current branch, so {oldName} is optional.

git branch -m [{oldName}] {newName}

Terminal window
$ git branch -m new_feature TC310
...
$ git branch -m TC310

Delete a branch

Branch deletion can only be performed from a branch that is not currently checked out. For example, it is best to delete branches from the main branch. Similarly, Git will not delete branches that are unmerged. To delete unmerged branches, use the capital -D option. See the Remote Repositories chapter for information on deleting a remote branch, which differs slightly.

Terminal window
$ git checkout main #You cant delete a branch you're on!
$ git branch -d feature_1 #Use -d for merged branch
or
$ git branch -D feature_1 #Use -D un-merged branch

Reset a branch

Resetting a branch brings it back to a state at a particular commit. There are three reset types; soft, mixed, and hard reset. Resets affect the HEAD pointer, the staging index, and the working directory depending on which type of reset is used. All resets move the HEAD pointer. Any tree-ish can be reset, including a commit, branch name, tag, etc. as anything that points to a certain point in the repository’s timeline. Any time a reset is performed, previous commits are discarded. Resets should only be done in private repos, or to repos/branches that have not yet been shared with collaborators.

Soft reset moves the HEAD pointer, but does not change the working directory or the staging index. Ultimately, all this does is move the HEAD pointer to record to a previous location. This means that if we were to roll back to a particular commit, the working directory and the staging index right before the next commit are left intact. Because only the HEAD pointer is moved, soft resets are good for amending or combining one or more previous commits. This option is similar to git commit —amend., but provides more latitude to combine commits, as —amend only changes one commit at a time. Syntax: git reset —soft Mixed reset moves the HEAD pointer, and does not affect the working directory, but does change the staging index to match the repo, so staged (add)content is replaced. This is the default option and is what happens when an option is omitted from the command. Effectively this does not change saved files, but does re-write which changes are staged for the next commit. This most valuable for reorganizing commits. Syntax: git reset —mixed or git reset Hard reset moves the HEAD pointer, changes the staging index to match the repo, and changes the working directory to match the repo. This option essentially allows the user to make everything look like it did at a certain point in time including discarding all subsequent code changes! Hard resets are useful to permanently undo commits. Be careful how long old commits hang out, as Git’s garbage collection may automatically clean up the tree which could delete old, abandoned/unused commits. Hard resets are good for making one branch look like another. Syntax: git reset —hard

Merge a branch

Merges take code from one branch and add it in, non-destructively, to another branch. It is always a good idea to first inspect the receiving branch and potential changes using the diff function before a code merger. Additionally, it is a good idea to only run merges with a clean working directory by either committing or stashing those changes.

Syntax: git merge <receivingBranch> <featureBranch>

Fast-forward commit is what happens when there are no additional changes to the receiving branch from the feature branch. Git simply advances the HEAD and merges, or simply adds the feature content to the receiving branch. A true merge is what happens when there have been commits/changes made to the receiving branch since the branch fork.

Merge conflicts and resolutions

Conflicts happen when there are two changes to the same line in different commits. While it may be helpful to add script to display that there is a current merge in progress in the shell, it is always good practice to check for a successful merge before continuing! Merge conflicts can be resolved in several ways.

Abort the merge if a mistake has been made or the problem would really rather be solved later using using the —abort option.

Terminal window
$ git merge --abort

Resolve conflicts manually. This involves going into the code and making edits with the merge in progress before continuing. For large blocks of text, it may be helpful to use the diff command. Note that the diff command does not show changes in the working directory, but only elements in staging that are ready to be committed.

Terminal window
$ git diff --color-words main..featureBranch HW.java #Specify problem file
or
$ git show --color-words featureBranch #Specify current branch

In the text editor, simply locate the merge indicators (markers), choose one of the two conflicting code blocks (its easiest to just use the HEAD block) and edit it to the desired state. Once manual editing is finished, manually remove the merge indicators (markers), add the file, and complete the merge with git commit (no options or messages). Use a merge tool. Git has a built-in tool that is accessible via the mergetool command. However, many script editors (including VSCode) have built-in merge tools as well.

Reducing conflicts

Good practice for reducing merge conflicts include keeping code lines short. Short lines are a lot easier to identify and fix. Additionally, its wise to keep commits small and focused - atomic commits! Be aware of stray edits to white spaces (spaces, tabs, new lines, etc.). This is especially troublesome for indented lines, at becomes legitimately problematic in languages that derive context from indents like Python. Lastly, its important to merge often! It is not necessary to delete the feature branch, or make only one merge, but instead keep it as experimental and continue merging to the main branch. These are handled by “feature flags” in the main code that are essentially placeholders for future, unreleased features. Alternatively, merging main routinely into features helps reduce conflicts before a final merge to main and is called “tracking”.

Stashing Changes

Why would we stash changes? If you make changes to the working directory of a branch where those changes would pose a conflict, and try to checkout a new branch without staging or committing those changes, they may be lost. It is possible to save/stash changes via the stash command and a descriptive title. Syntax: git stash save "" The -u or —include-untracked option includes elements that are not tracked by the Git project.

Viewing stashed changes

Terminal window
$ git stash list