GIT - HowTo's about "branches"


Workflow of upstream + fork + feature branch ending with a merge --squash


Have a look to the definitions if you're not familiar with these terms :

The workflow :

  1. setup :
    • I have an upstream repository : upstream
    • upstream is forked into myFork
    • my local repository is a clone of myFork
  2. initial status of my local repository :
    	---o---o---o master
  3. I create a feature branch and work there :
    1. git checkout -b feature
    2. code then git add
    3. git commit
    4. repeat
    	---o---o---o---o---o---o master
    	             o---o---o feature
  4. the work on my feature is done, time to share it with others. To do so, I update master of local repo + fork :
    1. git checkout master
    2. git pull upstream master
    3. git push myFork master
    The point of this step is to receive the changes made by others on master, so that conflicts —should there be any— arise on my local repository rather than later while merging on upstream.
  5. I create a branch —from master— to squash my feature branch into :
    1. git checkout master
    2. git checkout -b feature_squash
    	---o---o---o---o---o---o master feature_squash
    	             o---o---o feature
  6. I merge squash my feature branch into the squash branch :
    1. git merge --squash feature
      Squash commit -- not updating HEAD
      Automatic merge went well; stopped before committing as requested
    2. git commit
    3. summarize the list of commit messages into something explaining the single resulting commit :
      did this and that as requested by ticket #xyz
    	---o---o---o---o--o---o master
    	            \          \
    	             \          o feature_squash
    	              \        /
    	               o--o---o feature
  7. I merge my squash branch into master :
    1. git checkout master
    2. git merge feature_squash
    	---o---o---o---o--o---o---o master feature_squash
    	            \          \ /
    	             \          o
    	              \        /
    	               o--o---o feature
  8. Delete the squash branch :
    git branch -d feature_squash
    	---o---o---o---o--o---o---o master
    	            \          \ /
    	             \          o
    	              \        /
    	               o--o---o feature
  9. Push the changes (now on master) to my fork :
    git push myFork master
  10. merge myFork into upstream : to be done with GitLab
  11. delete the local feature branch :
    Since feature has been merged into feature_squash —that doesn't exist anymore— Git will consider feature as not merged and you'll have to force it with :

How to forbid commits on the master branch ?

Situation :

After hours of sweating / swearing / cleaning, you decide it would be wise to set up some safeguard to avoid living this again.

The title of this article as well as the solution below refer to master because it's a pretty common situation, but you can —of course— apply this to any branch .

Details :

Here come the Git hook scripts :
Client-side hooks are not copied when cloning a repository (details).

Solution :

  1. cd root/of/myGitRepo
  2. cat << 'EOF' > .git/hooks/pre-commit
    #!/usr/bin/env bash
    currentBranch="$(git rev-parse --abbrev-ref HEAD)"
    if [ "$currentBranch" == 'master' ]; then
    	echo 'None shall pass!'
    	exit 1
    chmod +x .git/hooks/pre-commit
  3. Check :
    ls -l .git/hooks/pre-commit && cat $_

How to merge a branch as a single commit (i.e. merge + squash) ?

Situation :

I have :
	          F-------G myBranch
	A---B---C---D---E master
A standard merge would give :
	          F-------G myBranch
	         /         \
	A---B---C---D---E---H master
I want :
	A---B---C---D---E---H master myBranch

Solution :


How to track a remote branch ?

When starting working on an existing remote branch :

git checkout --track remote/branch
Branch branch set up to track remote branch branch from remote.
Switched to a new branch 'branch'
This :
  1. creates the local branch branch
  2. sets remote/branch as the upstream of the new local branch branch
  3. checkouts the new local branch branch
You can not run :
git checkout --track remote/branch myLocalBranch
fatal: Cannot update paths and switch to branch 'branch' at the same time.
Did you intend to checkout 'myLocalBranch' which can not be resolved as commit?

When publishing a local branch :

git push -u remote branch

Anytime :

git branch -u remote/branch localBranch
Branch localBranch set up to track remote branch branch from remote.
localBranch must exist before doing this.
git branch -u remote/branch
Branch currentLocalBranch set up to track remote branch branch from remote.
This construct declares remote/branch as the upstream of the currently checked out local branch currentLocalBranch, which may not be your intention.

How to list commits from a specific branch only ?


How to move commits to another branch ?

This typically happens when you've committed on master, then realized that those commits better suit a feature branch. In other words, how may I go from this :

	A---B---C---D---E master

to this :
	      C---D---E newBranch
	A---B master

Before going further, make sure you have no uncommitted changes left.

Method 1 : (source) :

  1. create a new branch + "jump" on it :
    git checkout -b newBranch
  2. make master point to the commit that is the base of newBranch :
    git branch -f master HEAD~3
    3 is the number of commits to move from master to newBranch

Method 2 (source) :

git branch newBranch      # Create a new branch, saving the desired commits
git reset --hard HEAD~3   # Move master back by 3 commits (GONE from master)
git checkout newBranch    # Go to the new branch that still has the desired commits

NB : instead of resetting to a nb of commits, you can reset until a specific commit ID :
git reset --hard a1b2c3d4

How it works (source) :

You want to go back to C, and move D and E to the new branch. Here's what it looks like at first:


After git branch newBranch:


After git reset --hard HEAD~2:


Since a branch is just a pointer, master pointed to the last commit. When you made newBranch, you simply made a new pointer to the last commit. Then using git reset you moved the master pointer back two commits. But since you didn't move newBranch, it still points to the commit it originally did.

Method 3 : cherry-picking (source) :

NB : if you checkout newBranch from the existing master branch it ALREADY has those three commits included in it, so there's no use in picking them. At the end of the day to get what the OP wanted, you'll still have to do some form of reset --hard HEAD.

Step 1 - Note which commits from master you want on a new branch :

git checkout master
git log

Note the hashes of (say 3) commits you want on newBranch. Here I shall use:
C commit: 9aa1233
D commit: 453ac3d
E commit: 612ecb3

    Note: You can use the first seven characters or the whole commit hash

Step 2 - Put them on the new branch

git checkout newBranch
git cherry-pick 612ecb3
git cherry-pick 453ac3d
git cherry-pick 9aa1233

NB : the order is important. You want to do the oldest commits first