git: some tricks

I used RCS, CVS and SVN in the past, so I am not new to version control. GIT is a bit different from them, by the way, and is new to me, so I need to write down things before I learn them by earth. I am publishing my notes in the hope it will be useful to you, as well. Also, feel free to comment if you’d like to suggest better ways to do the same things.

I am writing things as I use them, so beware this post will still change several times in the future. I hope this doesn’t drive your RSS reader nuts…

(Updated: September 15th, 2014: revert last commit, get a branch/tag’s commit ID)
(Updated: September 7th, 2012: make a local branch remote, check out a file from another branch)
Updated: April 3rd, 2012: new repo with gitolite in the middle)
(Updated: March 14th, 2011: git remote prune)
(Updated: January 27th, 2011)
(Updated: many other times…) …

To create a branch remotely and track it locally

…which basically means that you create a remote branch that you can easily synchronize with locally afterwards:

Create the branch remotely:

git push origin origin:refs/heads/sync-fixes

Now you created the branch remotely, but you still don’t see it locally: update your local archive of objects and refs:

git fetch origin

Now create the local branch and put it in sync with the remote one:

git checkout --track -b sync-fixes origin/sync-fixes

Now you have a local branch called sync-fixes that tracks a remote one with the same name. Cool, uh?

To merge changes in the master into a development branch

Switch to the branch:

git checkout testenv-start

Merge:

git merge --no-commit --no-ff origin

If everything is OK, you can now explicitly commit by hand:

git commit

To merge changes from a development branch into the master

Switch to the branch:

git checkout master

Merge:

git merge --no-commit testenv-start

To be extra sure not to wipe off good stuff, I use to clone the repository in a new directory, and do the merge there. That is: you cd in a new directory and:

git clone your:repo.git

Then, cd into the repo directory: you are in the master branch and it’s the only one you currently see:

$ git branch
* master

But if you add a “-a” to the command, you’ll see everything:

$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/fixes-bronto-20100715
  remotes/origin/master

So, all you need before starting the merge is to create the branch locally:

$ git checkout -b fixes remotes/origin/fixes-bronto-20100715
Branch fixes set up to track remote branch fixes-bronto-20100715 from origin.
Switched to a new branch 'fixes'
$ git branch 
* fixes
  master

See the branch was checked out, and switched to it.

To delete a branch once you are done with it

Once you have finished working on a branch and merged it into the master, you can delete the remote branch, both remotely and locally. This will do the trick:

Be sure to switch to the master branch:

git checkout master

Check how the branch is named remotely:

git branch -r

In my specific case, this returned:

bronto@brabham:/puppet$ git branch -r              
  origin/HEAD -> origin/master
  origin/master
  origin/sync-fixes

Now delete the remote branch:

git branch -r -d origin/sync-fixes

This returns something like:

bronto@brabham:/puppet$ git branch -r -d origin/sync-fixes
Deleted remote branch origin/sync-fixes (was 9e1a241).

Now you can delete the branch locally:

git branch -d sync-fixes

This returns something like:

bronto@brabham:/puppet$ git branch -d sync-fixes
Deleted branch sync-fixes (was 9e1a241).

(notice how both the remote and the local branch had the same hash)
Now wipe it away forever from the remote repo:

git push origin :sync-fixes

How to revert changes

It will happen sometimes, you do a few changes, a few commits and a few pushes, and then you find that what you wrote is just crap. How to go back to the last known godd commit?

The first thing is to get the “commit ID” of the good one. You can use git log on the command line, or retrieve it via a GUI like gitk. Whatever you choose is OK.

Let’s say you found the ID is 7f25d102340f3d49f086c4fe5d357e7272063915. Now you run:

git reset --hard 7f25d102340f3d49f086c4fe5d357e7272063915

and it will take you back to the situation when you issued that commit. You’ll have a confirmation message back from the command:

HEAD is now at 7f25d10 Small typo, fixed

that is: “HEAD is now at “, plus the first bytes of the ID, and the commit header. You could also issue a git status and see that you are behind by a number of commits.

Now if you want to push everything back to the central repository, you can’t just use a plain push. If you try, you’ll get an error message, like this:

$ git push
To gitolite:puppet.git
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to 'gitolite:puppet.git'

To make it work, you need to force it:

$ git push --force
Total 0 (delta 0), reused 0 (delta 0)
To gitolite:puppet.git
 + 1070ed3...7f25d10 master -> master (forced update)

That’s all. You now reverted your changes on both your local copy and the remote repository.

Cloning and updating a repo via SSH

I tried this one today for the first time. I have a repository in a directory in my workstation in the office. Today I was working from home and I wanted to do some more development. So I tried this for the first time:

git clone ssh://brabham/home/bronto/devel

This cloned the currently checked out branch locally, and set up references to the other ones:

bronto@cooper:~/Lab/devel$ git branch
* dev-aggr
bronto@cooper:~/Lab/devel$ git branch -a
* dev-aggr
  remotes/origin/HEAD -> origin/dev-aggr
  remotes/origin/dev
  remotes/origin/dev-aggr
  remotes/origin/dev-mmc
  remotes/origin/master

Now I could start some development locally, but the first time I tried to push I was warned I couldn’t: it is normally not allowed to push stuff when the remote checked out branch is the same that is being pushed. Dah, no problem: went to the office workstation and typed

git checkout master

Now I could… but got a warning message, like this:

warning: You did not specify any refspecs to push, and the current remote
warning: has not configured any push refspecs. The default action in this
warning: case is to push all matching refspecs, that is, all branches
warning: that exist both locally and remotely will be updated.  This may
warning: not necessarily be what you want to happen.
warning: 
warning: You can specify what action you want to take in this case, and
warning: avoid seeing this message again, by configuring 'push.default' to:
warning:   'nothing'  : Do not push anything
warning:   'matching' : Push all matching branches (default)
warning:   'tracking' : Push the current branch to whatever it is tracking
warning:   'current'  : Push the current branch

Again, no problem:

git config push.default current

And that’s basically it. Now I could develop on my laptop and have my stuff synced to the development branch on my office workstation.

Wiping away stale remote branches
branches may be added and removed on a remote repository, but they’re not deleted from your local copy. To get rid of the stale ones you just need a single command: git remote prune [-n | --dry-run] name, where name is the string that follows remote/ when you do a git branch -a. E.g., in my case I had:

puppetrepo:/puppet# git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/something
  remotes/origin/something-more
  remotes/origin/something-else
...

To get rid of the stale ones it was enough to:

git remote prune origin

It’s all there is to it! If, for any reason, you want to visually check what’s going to happen before actually issuing the command, just add a --dry-run after prune

Creating a new repository (with gitolite)
When you use gitolite, he takes it upon itself to create a new repository for you: if you mention a new repository in gitolite.conf and commit/push the change, the new empty repository is automatically created. What puzzled me was that I couldn’t add files into the repository and received some weird errors like:

No refs in common and none specified; doing nothing.

It took some time and research to find out that the solution is a simple

git push origin master

for the first push

Make a local branch remote
If you have a local-only branch and you want to make it remote, you’ll have to run something similar to:

git push <remote-name> <branch-name>

e.g.:

git push origin mylocalbranch

Check out a file from another branch

git checkout <branch> -- <file>

e.g.:

git checkout mybranch -- hosts.cf

Works on remote branches, too:

git checkout remotes/origin/mybranch -- hosts.cf

Revert a commit
This case is similar to something you’ve seen above, but you’re lucky enough that you haven’t pushed anything to a remote repository. Say you have committed some code in the wrong branch and you want to… uhm… un-commit the change so that you can re-commit it in the right branch. If you haven’t pushed the changes to a remote repository, then it’s absolutely painless: in the branch you committed the wrong code do:

git reset HEAD~1

At this point a git status will show that the files are again ready to be uncommitted. It’s enough to checkout the right branch and redo the commit there.
If you have also pushed the code then you are a bit more in trouble, as doing like this and trying to re-push may result in a non-fast-forward change that may be a risky business if you aren’t the only one using the repository. In that case you may try and use git revert, then check-out the right branch and “hand pick” the files you modified by using git checkout branchID -- files...

Obtain the commit ID associated to a branch or tag
git rev-parse branch_or_tag_ID

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s