GIT - HowTo's about "branches"

mail

Should I merge or rebase my feature branch to keep it up-to-date with master ?

Situation

We have :
A---B  master
 \
  C---D  feature
We want the feature branch to be up-to-date with master.
To do so, there are 2 options :

Comparison of options

command merge rebase
result
A---B  master
 \
  C-B-D-E  feature
A---B  master
     \
      C'-D'  feature
pros
  • reliable solution
  • resolve conflicts only once
  • fine for small teams and not very highly active master branch (few changes to sync from master to feature)
  • merging is a non-destructive operation : it only adds the merge commit and makes no change to the branches
  • linear history / tramlines : easier to follow
  • feature stays parallel to master
  • feature only contain feature-related changes
  • no merge commits
cons
  • "mixes" master and feature commits into feature, making it more difficult to track changes (git log, git bisect, ...)
  • the above is made even more complex if feature lasts for a long time
  • on a large repo with many committers / branches, this will create many redundant merge commits (and wide tramlines in the commit history)
  • MUST NOT be used on public branches
  • _can_ lead to fixing conflicts repeatedly when rebasing
  • updating feature with rebase throughout its life makes it a "private" branch, i.e. :
    • code that is not shared to others
    • code living on your hard disk only (SPOF)
  • impairs history :
    • loses chronological order of creation of code (but no big deal, actually)
    • loses context provided by a merge commit : you can’t see when upstream changes were incorporated into feature

Which is the best ? Which should I use ?

  1. if applicable (private branch), try rebase
  2. if it turns into conflict resolution hell, give up and use merge
  3. move on
But don't forget :
  • the answer to the merge vs rebase question is mostly context-dependent
  • you may avoid most of the pain by having smaller features, meaning shorter-lived feature branches
  • don't forget to interactively rebase feature (private commits only, as usual) from time to time to :
    • re-order commit
    • squash commits
so that the development history makes more sense.
mail

How to stop tracking a remote branch ?

mail

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

This procedure looks overkill.
  • Maybe I was trying to achieve something very specific when writing this (but forgot to note how this was special ).
  • Or maybe I've gained some extra experience with Git since that time, and would do that differently today.
Anyway, I can't see what using this temporary "squash branch" brings.
Remember the use case that made all this necessary.

Preliminary

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 :
mail

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
    fi
    EOF
    chmod +x .git/hooks/pre-commit
    
  3. Check :
    ls -l .git/hooks/pre-commit && cat $_

Alternate solution

I've found ONE edge case where I still need to be able to commit to master : when finalizing a merge --squash. So here's a hack to workaround the pre-commit hook script without entirely disabling it :
  1. add to the code :
    #!/usr/bin/env bash
    
    currentBranch="$(git rev-parse --abbrev-ref HEAD)"
    if [ "$currentBranch" == 'master' -a "$c2m" != 'yes' ]; then
    	echo 'None shall pass!'
    	exit 1
    fi
  2. explicitly raise a flag to actually commit to master :
    c2m=yes git commit
mail

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

mail

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.
mail

How to list commits from a specific branch only ?

mail

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:

A-B-C-D-E (HEAD)
        ↑
      master

After git branch newBranch:

    newBranch
        ↓
A-B-C-D-E (HEAD)
        ↑
      master

After git reset --hard HEAD~2:

    newBranch
        ↓
A-B-C-D-E (HEAD)
    ↑
  master

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.

https://stackoverflow.com/questions/1628563/move-the-most-recent-commits-to-a-new-branch-with-git#comment-36690393



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