My Opera is closing 3rd of March

Cronache di Sarvegia

...because every new challenge hides an opportunity

git: some tricks

,

I used CVS and SVN in the past, so I am not new to version control. GIT is a bit different from the other two, by the way, and is new to me, so I need to write down things before I learn them by earth. I am publishing it 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 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

One more round of leapocalypseIt just needs an OFFSET