Skip to content

Instantly share code, notes, and snippets.

@jherax
Last active February 12, 2024 14:53
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save jherax/6a756503065e991197e70e8feb319128 to your computer and use it in GitHub Desktop.
Save jherax/6a756503065e991197e70e8feb319128 to your computer and use it in GitHub Desktop.
Git Alias and Rebase

Git Alias and Git Rebase

WHEN TO DO REBASE

After each commit in our branch, in order to be up-to-date with the integration branch.

WHY? Because you integrate the progress of the base branch into YOUR branch, so that you will have fewer conflicts to resolve, and the rebase process will take less commits to apply when rebasing against your branch.

The sooner you REBASE your branch, the fewer conflicts to resolve.

WHAT NOT TO DO

First of all, DO NOT allow a day without performing at least ONE REBASE against your master or integration branch.

Try to have only one integration branch. This way we will keep an eye only in one branch, e.g. master or your integration branch.

AVOID as much as possible perform a rebase on stable branches, such as master, for example, rebasing master against another branch, or performing an interactive rebase on master. Remember that the git rebase command rewrites the branch’s history. That is becasuse when performing a rebase or rebase -i, you can reorder, edit, merge, or delete commits, then the identity of each commit (SHA) affected will change.

For instance, let's say the last commit on master is as follow: 6ace7bf4 [US-703] Fixed bug #12 on MultiSelect component and then we perform an interactive rebase to merge old commits: git rebase -i HEAD~4. What happens next is that the history on master has changed, and the last commit has a new SHA, something like this: c18b70b2 [US-703] Fixed bug #12 on MultiSelect component.

After reordering or merging old commits, the identity of the subsequent commits are changed, giving them a new SHA. That means that my local branch master has diverged from the remote branch master. So to upload my changes to the server we have to force the push: git push --force-with-lease origin master. But take into account that usually the master branch is protected against a forced-push, and also, performing forced-push on stable branches is NOT A GOOD PRACTICE.

The problem comes when somebody is working on a branch that has master as the base branch, with the last commit: 6ace7bf4 [US-703] Fixed bug #12 on MultiSelect component, and now when he performs a rebase against master, turns out that the master branch is different from the previous one which now has the last commit: c18b70b2 [US-703] Fixed bug #12 on MultiSelect component. Apparently they are the same, but the rebase action will prompt a lot of conflicts because you are duplicating commits, that is, rebase will keep the commits of the old master, and then it will apply the commits of the new master in your branch.

For these types of scenarios, git comes with the powerful rebase --onto.

REBASE ONTO

Using git rebase --onto you can rebase a part of a branch using the base as some other branch (or SHA). The syntax for Git command is: git rebase [--onto <new-base>] [<old-base> [<branch>]]

In our case, after the master branch was altered, we can run the rebase by specifying the new master and the old-base commit in our branch. The commands will be like below:

# let's say my working branch is feat-x

# get the last state of remote master
git fetch origin master

# print the log to find the old-base SHA commit
git log --oneline

git rebase --onto origin/master 6ace7bf4 feat-x

We are telling to rebase --onto that apply the new-base commits on origin/master over the old-base SHA in our branch feat-x.

Read more about rebase --onto:

HEADS UP

All git alias used in following snippets use the integration branch named integration. You may want to use another integration branch or perhaps you integrate changes against master directly.

So given another integration branch, e.g. team-integration, you should change -integration to -team-integration (or -master) in the following sources: git-alias.sh and git-alias.ini.

The last section ALL ALIAS contains the list of all alias registered here, and also the original git commands that are aliasing.

HOW TO REGISTER ALIASES

You can register aliases in three ways:

  1. Download git-alias.sh and open a bash/shell terminal where the file is, and run: sudo chmod +x git-alias.sh and bash git-alias.sh
  2. Open any terminal you like and run git config --global -e, the default editor will be opened and then, paste the content of git-alias.ini at the end of file, or at the bottom of the [alias] placeholder.
  3. Download git-alias.sh and git-alias.js, then open any terminal in the folder where the downloaded files are, and run sudo chmod +x git-alias.sh and node "git-alias.js". Tip: you can create a npm script to register git aliases in your project, e.g. "reg:alias": "node scripts/git-alias.js"

STEPS

Creating branches

When you want to create a new user-story branch, let's say us-753, the ideal way is starting from the integration branch, as this will be updated with the latest changes.

# first, update the integration branch,
# then, create us-753 from the integration branch
# and later, perform a checkout to us-753
$ git checkout integration
$ git newbr us-753

Why use lowercase when naming branches?

Because it reduces errors in the file system when dealing with different platforms.
Linux is case-sensitive for file names, while Windows is not.

When you create a branch, git internally creates a folder for that branch.
What if you create the branch us-753, and another user also creates the branch US-753?
Well, as git runs under a subset of unix-core, it can have both branches with both folders respectively in the file system, us-753 and US-753, but someone in a Windows machine will have troubles trying to get us-753 or US-753, because the file system is not case-sensitive.

Commit and push changes

After some progress, you may want to save your changes.
You can create a new commit message, or add your changes to the last commit saved (amend).

# Commit all: creates a new commit message including all changes
$ git commitall "[US-753] Fix issue #16 with null dataset of Linear component"

# Or commit amend: append all changes to the last commit (no message added)
$ git amend

# Push with --force-with-lease flag to remote branch (us-753)
$ git pushf

Rebase against integration branch

Once you are uploaded your changes and the workspace is clean, it is good time to check if the integration branch contains new commits to update our personal branch with those commits.

# Get the latest changes from integration branch and include them into us-753
$ git rebasebr

# Also you can rebase against another branch:
# git rebasebr master

# Fix conflicts if exist
# git mergetool
# git rebase --continue

# Upload changes to remote us-753
$ git pushf

Rebase interactive

Let's say these are our last commits (git log --oneline -4, top shows recent commits, bottom shows older commits)

{1} c8fc9bb0 [US-753] Fix issue #16 with null dataset on Linear component
{2} 6ace7774 753 WIP 2
{3} af4a49fa 753 WIP
{4} c18b70b2 [US-703] Fixed bug #12 on MultiSelect component

This record is ugly, and if you are working with pull request, your PR could be rejected, or the approver could squash your WIP commits.

The commit {1} contains the end of user-story-753, but commits {2} and {3} are Work-In-Progress of the same story, so it would be nice to merge them into one commit.

Here is where git rebase -i can help us to organize our commits before merging them into another branch.

The alias git rebaseii allows you to edit the last X commits, this way you can modify messages, remove, reorder or meld commits in your working branch.

# Rebase us-753 and open editor with last 4 commits
$ git rebaseii 4

# Upload changes to remote us-753
$ git pushf

See the rebase interactive screen to edit the last 4 commits.

Merge us-753 into the integration branch

First, you rebased your branch against the integration branch with git rebasebr, later you reorganized your commits with git rebaseii, and now you are ready to merge the branch us-753 into the integration branch.

# 1. Checkout to the integration branch and update it
# 2. Merges your feature branch (us-753) into the integration branch
# 3. Push the integration branch to the server (origin)
# 4. Checkout again to your initial branch
$ git mergeto integration

Alternatively, if you have finished the feature, and no more work is needed in the branch, you may delete the feature branch after being merged into the integration branch.

# 1. Checkout to the integration branch and update it
# 2. Merges your feature branch (us-753) into the integration branch
# 3. Push the integration branch to the server (origin)
# 4. Delete the feature branch from both, local and server
$ git finishto integration

Tip: if you have more than 3 commits on your branch, and you still working on a feature, consider rebase your own branch with git rebaseii or integrate some commits to the integration branch, because the more commits you have in your branch, the more times probably you will have to resolve conflicts.

For instance, let's suppose your branch is ahead integration branch by 5 commits, and other developer pushes 2 commits to the integration branch where a file you have edited also was modified. You decided to perform a git rebasebr, and it turns out that the first old commit in your branch has the file in conflict; now you have to resolve the conflict 5 times across your 5 commits. Just imagine if your branch is ahead by 10 commits or more...

ALL ALIAS

# Creates a new branch from the current branch.
# Alias:
# git pull origin <current-branch> --rebase && git checkout -b <branch-name>
$ git newbr branch-name
# Removes a branch from local and remote.
# Alias:
# git branch -D <branch-name>; git push origin --delete <branch-name>
$ git rmbr branch-name
# Gets the name of your current working branch.
# Alias:
# git branch | grep \* | cut -d " " -f2
$ git currentbr
# Update references from remote branches and prints the list of remote branches.
# (HEAD, master and the default integration branch are not listed)
# Alias:
# git remote update origin --prune && git branch --remote
$ git syncbr
# Adds all untracked / unstaged files and creates a new commit with message.
# Alias:
# git add -A && git commit -a -m "message"
$ git commitall "[US-741] Message"
# Append all untracked / unstaged files to the last commit. No message is added.
# The SHA of the last commit is altered, and the modified files are appended to it.
# Alias:
# git add -A && git commit -a --amend --no-edit
$ git amend
# Pushes the current working branch to the server with --force-with-lease flag,
# useful when the "git amend" alias is used.
# Alias:
# git push --force-with-lease origin <current-branch>
$ git pushf
# Performs an interactive rebase on your local branch.
# Alias:
# git rebase -i HEAD~<max-commits>
$ git rebaseii 5
# Performs a rebase against the default integration branch,
# in order to be updated with latest commits of the base branch.
# Alias:
# git fetch origin integration && git rebase origin/integration
$ git rebasebr

# Performs a rebase against another branch (master)
# Alias:
# git fetch origin master && git rebase origin/master
$ git rebasebr master
# Peforms a rebase against another branch changing the old-base
# as some other branch or SHA.
# Alias:
# git fetch origin <branch> && git rebase --onto origin/<branch> <old-base-SHA>
$ git rebaseonto master 6ace7bf4
$ git rebaseonto integration 6ace7bf4
# Performs a rebase of the current branch against the remote branch.
# Alias:
# git pull origin <current-branch> --rebase
$ git pullbr
# Reset your local integration branch to the server's integration branch.
# Alias:
# git checkout integration && git fetch origin integration && git reset --hard origin/integration
$ git resetbr

# Reset another local branch to the state of that branch in the server.
# Alias:
# git checkout <branch> && git fetch origin <branch> && git reset --hard origin/<branch>
$ git resetbr master
# Merges your current working branch into the destination branch, but first updates
# the destination branch. If no parameter is provided, the destination branch is
# the default integration branch. After the merge, the destination branch will be
# pushed to the server and then you'll be back to the initial branch.
# Alias:
# git resetbr integration && git merge <initial-branch> && git push origin integration && git checkout <initial-branch>
$ git mergeto
# Merges your current working branch into the target branch, but first updates
# the target branch. After the merge, the target branch will be
# pushed to the server and then you'll be back to the initial branch.
# Alias:
# git resetbr <target> && git merge <initial-branch> && git push origin <target> && git checkout <initial-branch>
$ git mergeto master
$ git mergeto develop
# 1. Checkout to the integration branch and update it
# 2. Merges your current branch into the integration branch
# 3. Push the integration branch to the server (origin)
# 4. Delete the feature branch from both, local and server
$ git finishto
# 1. Checkout to the target branch and update it
# 2. Merges your current branch into the target branch
# 3. Push the target branch to the server (origin)
# 4. Delete the feature branch from both, local and server
$ git finishto master
# Merges a specific branch into the default integration branch.
# After that, the integration branch is pushed to the server.
# (the "mergeto" alias makes sure the target branch is updated first)
# Alias:
# git checkout integration && git merge us-753 && git push origin integration
$ git mergebr us-753
# Merges the branch "us-753" into the "master" branch.
# After that, the "master" branch is pushed to the server.
# Alias:
# git checkout master && git merge us-753 && git push origin master
$ git mergebr us-753 master
# Checkout to a <branch> and deletes all branches already merged into that branch.
# This method is destructive. Do not use it unless you know what you are doing!
# Alias:
# git checkout master && git branch --remote --merged master | xargs git rmbr
$ git cleanmerged master
# Temporarily untrack changes in a file, so it won't be added in commits.
# Alias:
# git update-index --assume-unchanged <path>
$ git untrack path/to/file
# Tracks changes again from an untracked file, so it can be added in a commit.
# Alias:
# git update-index --no-assume-unchanged <path>
$ git track path/to/file
# Show structured info about commits in one-line, having the format: {sha date message author refs}
# Alias:
# git log --graph --abbrev-commit --decorate --date=short --format=format:"custom-format" -10
$ git logn
$ git logn 20
# Show commits in the current branch by an specific author, starting at some date.
# Alias:
# git log --pretty="custom-format" --date=short --author=david.rivera --since=2018-10-01
$ git logauthor david.rivera 2018-10-01
# Show commits in the current branch by an specific author, between a date range.
# Alias:
# git log --pretty="custom-format" --date=short --author=david.rivera --since=2018-10-01 --until=2018-11-01
$ git logauthor david.rivera 2018-10-01 --until=2018-11-01
# Show commits in all local branches by an author, starting at some date.
# Alias:
# git log --pretty="custom-format" --date=short --author=david.rivera --since=2018-10-01 --all
$ git logauthor david.rivera 2018-10-01 --all
# Show commits in the current branch by an author, for a specific file, starting at some date.
# Alias:
# git log --pretty="custom-format" --date=short --author=david.rivera --since=2018-10-01 --follow -- path/to/file
$ git logauthor david.rivera 2018-12-01 --follow -- path/to/file

Learn more about pretty-format at https://git-scm.com/docs/pretty-formats.

[alias]
co = checkout
cp = cherry-pick
st = status -s
untrack = !git update-index --assume-unchanged
track = !git update-index --no-assume-unchanged
logn = "!f() { n=${1:-10}; git log --graph --abbrev-commit --decorate --date=short --format=format:\"%C(blue)%h%Creset %C(green)(%ad)%Creset %s %C(red)%an%Creset%C(yellow)%d%Creset\" -$n; }; f"
logauthor = "!f() { a=\"$1\"; b=\"$2\"; shift 2; git log --date=short --pretty=\"format:%C(green)%ad%Creset %C(yellow)%h%Creset %C(white)%s%Creset\" --author=$a --since=$b $@; }; f"
currentbr = "!f() { git branch | grep \\* | cut -d \" \" -f2; }; f"
remotebr = "!f() { br=${1-integration}; git branch --remote | cut -c10- | grep -vE \"HEAD$|master$|${br}$\"; }; f"
syncbr = !git remote update origin --prune && echo -e \"\\e[36mRemote branches...\" && git remotebr
newbr = !git pullbr && git checkout -b
rmbr = "!f() { git branch -D $1 2>/dev/null; git push origin --delete $1 2>/dev/null; }; f"
commitall = !git add -A && git commit -a -m
amend = !git add -A && git commit -a --amend --no-edit
pushf = "!f() { git currentbr | xargs git push --force-with-lease origin; }; f"
pullbr = "!f() { br=$(git currentbr); git pull origin ${br} --rebase; }; f"
resetbr = "!f() { br=${1-integration}; git checkout ${br} && git fetch origin ${br} && git reset --hard origin/${br}; }; f"
rebaseii = "!f() { git rebase -i HEAD~$1; }; f"
rebaseonto = "!f() { git rebase --onto $1 $2 $(git currentbr); }; f"
rebasebr = "!f() { br=${1-integration}; git fetch origin ${br} && git rebase origin/${br}; }; f"
mergebr = "!f() { br=${2-integration}; git checkout ${br} && git merge $1 && printf \"\\nPushing to origin ${br} ...\\n\"; git push origin ${br}; }; f"
mergeto = "!f() { br=$(git currentbr); git resetbr $1 && printf \"\\nMerging branch ${br} into $1 ...\\n\"; git mergebr ${br} $1 && git checkout ${br}; }; f"
finishto = "!f() { br=$(git currentbr); git resetbr $1 && printf \"\\nMerging branch ${br} into $1 ...\\n\"; git mergebr ${br} $1 && git rmbr ${br}; }; f"
cleanmerged = "!f() { br=${1-integration}; git resetbr ${br} && git branch --remote --merged ${br} | cut -c10- | grep -vE \"HEAD$|master$|${br}$\" | xargs git rmbr; }; f"
const { execFile } = require("child_process");
const { error, log } = console;
const onSuccess = () => log("++ Added git aliases");
const unhandledError = (e) => {
error(Error(e));
process.exit(1);
};
const execute = (exe, args, options) =>
new Promise((resolve, reject) => {
execFile(exe, args, options, (err, stdout, stderr) => {
if (err) reject(stderr);
else resolve(stdout);
});
});
const options = { cwd: __dirname, shell: true, windowsHide: true };
// first should grant permissions: chmod +x git-alias.sh
execute("sh git-alias.sh", [], options).then(onSuccess).catch(unhandledError);
#!/bin/bash
# @see
# https://cutt.ly/git-aliases
# @see
# https://git-scm.com/docs/pretty-formats
# Default Integration Branch: integration.
# If you use another integration branch, just replace
# the "integration" name by the branch name you are using.
git config --global alias.co 'checkout'
git config --global alias.cp 'cherry-pick'
git config --global alias.st 'status -s'
git config --global alias.untrack '!git update-index --assume-unchanged'
git config --global alias.track '!git update-index --no-assume-unchanged'
git config --global alias.logn '!f() { n=${1:-10}; git log --graph --abbrev-commit --decorate --date=short --format=format:"%C(blue)%h%Creset %C(green)(%ad)%Creset %s %C(red)%an%Creset%C(yellow)%d%Creset" -$n; }; f'
git config --global alias.logauthor '!f() { a="$1"; b="$2"; shift 2; git log --date=short --pretty="format:%C(green)%ad%Creset %C(yellow)%h%Creset %C(white)%s%Creset" --author=$a --since=$b $@; }; f'
git config --global alias.currentbr '!f() { git branch | grep \* | cut -d " " -f2; }; f'
git config --global alias.remotebr '!f() { br=${1-integration}; git branch --remote | cut -c10- | grep -vE "HEAD$|master$|${br}$"; }; f'
git config --global alias.syncbr '!git remote update origin --prune && echo -e "\e[36mRemote branches..." && git remotebr'
git config --global alias.newbr '!git pullbr && git checkout -b'
git config --global alias.rmbr '!f() { git branch -D $1 2>/dev/null; git push origin --delete $1 2>/dev/null; }; f'
git config --global alias.commitall '!git add -A && git commit -a -m'
git config --global alias.amend '!git add -A && git commit -a --amend --no-edit'
git config --global alias.pushf '!f() { git currentbr | xargs git push --force-with-lease origin; }; f'
git config --global alias.pullbr '!f() { br=$(git currentbr); git pull origin ${br} --rebase; }; f'
git config --global alias.resetbr '!f() { br=${1-integration}; git checkout ${br} && git fetch origin ${br} && git reset --hard origin/${br}; }; f'
git config --global alias.rebaseii '!f() { git rebase -i HEAD~$1; }; f'
git config --global alias.rebaseonto '!f() { git rebase --onto $1 $2 $(git currentbr); }; f'
git config --global alias.rebasebr '!f() { br=${1-integration}; git fetch origin ${br} && git rebase origin/${br}; }; f'
git config --global alias.mergebr '!f() { br=${2-integration}; git checkout ${br} && git merge $1 && printf "\nPushing to origin ${br} ...\n"; git push origin ${br}; }; f'
git config --global alias.mergeto '!f() { br=$(git currentbr); git resetbr $1 && printf "\nMerging branch ${br} into $1 ...\n"; git mergebr ${br} $1 && git checkout ${br}; }; f'
git config --global alias.finishto '!f() { br=$(git currentbr); git resetbr $1 && printf "\nMerging branch ${br} into $1 ...\n"; git mergebr ${br} $1 && git rmbr ${br}; }; f'
git config --global alias.cleanmerged '!f() { br=${1-integration}; git resetbr ${br} && git branch --remote --merged ${br} | cut -c10- | grep -vE "HEAD$|master$|${br}$" | xargs git rmbr; }; f'
echo;
echo "@git: all aliases registered!"
echo "@see: https://cutt.ly/git-aliases"

Configuring tools

In order to use rebase and other commands, it is recommended to have properly configured git.

Editor

You can set any text editor as the git editor. The following are the most used, pick one of these:

  • VS Code

    git config --global core.editor "code --wait"
  • Sublime Text

    git config --global core.editor "subl -n -w"
  • Atom

    git config --global core.editor "atom --wait"

Read more about Associating text editors with Git.

Difftool

Next are the shared configuration for difftool and mergetool.

git config --global mergetool.keepTemporaries false
git config --global mergetool.keepBackup false
git config --global mergetool.prompt false
git config --global difftool.prompt false

Now you should configure the specific commands for difftool/mergetool:

Example configuring Meld in Windows:

git config --global diff.tool meld
git config --global difftool.meld.cmd 'C:/Program\ Files\ \(x86\)/Meld/Meld.exe "$REMOTE" "$LOCAL"'

git config --global merge.tool meld
git config --global mergetool.meld.trustExitCode true
git config --global mergetool.meld.cmd 'C:/Program\ Files\ \(x86\)/Meld/Meld.exe "$REMOTE" "$MERGED" "$LOCAL" --output "$MERGED"'

Add Git Bash to VS Code

Using Powershell or the default cmd in Windows are not the best-suited to use with git, because those terminals lack a lot of commands and utilities being used in git.

The best way to leverage the git environment from VS Code is to link the Git Bash terminal to VS Code. To do that, add the following steps:

  • Press Ctrl + Shift + P and write "default shell", then click on the option Terminal: Select Default Shell and pick Git Bash.
  • Restart all instances of VS Code.

Git Bash for Windows is not just bash compiled for Windows. It's package that contains bash (which is a command-line shell) and a collection of other, separate *nix utilities like ssh, scp, cat, find and others (which you run using the shell), compiled for Windows, and a new command-line interface terminal window called mintty.

# Commits are listed in reverse order as git log.
# Top shows oldest commits, Bottom shows recent commits.
pick c18b70b2 [US-703] Fixed bug #12 on MultiSelect component
reword af4a49fa 753 WIP
fixup 6ace7774 753 WIP 2
fixup c8fc9bb0 [US-753] Fix issue #16 with null dataset on Linear component
# Rebase f9424f77..c8fc9bb0 onto f9424f77 (4 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
#
# Note that empty commits are commented out
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment