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).
$ 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:
$ 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>
$ 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>
$ 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>
$ 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>
$ 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.
$ git branch * main main_1st_feature main_2nd_feature
$ git branch --merged * main
vs
$ 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}
$ 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.
$ git checkout main #You cant delete a branch you're on!$ git branch -d feature_1 #Use -d for merged branchor$ 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
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.
$ 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.
$ git diff --color-words main..featureBranch HW.java #Specify problem fileor$ 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 "
Viewing stashed changes
$ git stash list