I prefer coding on my workstation (30” + 24” LCDs, fast, great chair, etc…). But sometimes want to hack on my laptop (an MBA) when I’m out and about.
Here’s my workflow.
I am posting this for two reasons:
- It’s complex enough that regularly forget how to do it. I rely on random Bing searches to remind myself. This way it’s all in one place.
- I’m not sure I’m doing it right, or if there’s an easier way. If people provide comments I’ll update the workflow accordingly.
The first thing is to get the machine setup. These days, because MileLogr is an ASP.NET app, I do most dev within Windows. Once you are past machine setup the rest of the workflow is basically the same. I use the tools outlined in my post on Coping with the OSS Command Line on Windows to make my Windows workflow work.
In short I install & configure Chocolatey (a Windows tool/package manager that makes installing all this stuff super-easy) and then, in turn use that for installing all the stuff below, from a elevated cmd.exe. Someday I’ll write a cmd script that does this, but for now I just type each command, e.g.:
I do this for each of the following:
- git, ruby, node.js, & python – Your standard OSS tools.
- posh-git – A set of PowerShell scripts which provide Git/PowerShell integration.
- Sublime Text 2 – The best text editor ever. As a bonus, it is easy to get out of.
- ConEmu – A really great Windows console. Super flexible. Blows cmd.exe away and is on par with iTerm2 for the Mac.
Of course, I also have Visual Studio and ReSharper installed.
Once all of the above is installed and configured I use a PowerShell prompt within ConEmu; I rarely ever go back to cmd.exe.
A big part of setting up a machine is dealing with SSH keys. I use both github and bitbucket.org so I need multiple SSH keys.
Generate a RSA SSH key
C:\Users\charlie\.ssh> ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (/c/Users/charlie/.ssh/id_rsa): foo Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in foo. Your public key has been saved in foo.pub. The key fingerprint is: ...
Take the resulting foo.pub file and add its contents to your SSH keys on github or bitbucket.
Add each key to ssh
C:\Users\charlie\.ssh> ssh-add .\foo Enter passphrase for .\foo: Identity added: .\foo (.\foo)
To support multiple git hosts (e.g. github, bitbucket, Azure Web Sites, etc…) on a single machine use a .ssh/config file. Here’s mine (with foo added):
C:\Users\charlie\.ssh> type .\config Host tig HostName github.com User git PreferredAuthentications publickey IdentityFile ~/.ssh/tig_rsa Host heroku HostName heroku.com User git PreferredAuthentications publickey IdentityFile ~/.ssh/heroku_rsa Host sourceforge HostName sourceforge.net User git PreferredAuthentications publickey IdentityFile ~/.ssh/sourceforge_rsa Host wpengine HostName git.wpengine.com User git PreferredAuthentications publickey IdentityFile ~/.ssh/wpengine Host bitbucket HostName bitbucket.org User git PreferredAuthentications publickey IdentityFile ~/.ssh/bizlogr Host foo HostName github.com User git PreferredAuthentications publickey IdentityFile ~/.ssh/foo
Cloning a repo
To use git with the foo credentials, replace the hostname in the git clone command with the Host from the .ssh/config file above.
E.g. Assuming I added the foo.pub public key to my github account (tig), to create a clone of tig/jOdometer, github says to use this:
git clone firstname.lastname@example.org:foo/jOdometer.git
Instead, to ensure the right SSH connection is used, I would do this:
git clone git@foo:tig/jOdometer.git
Don’t forget to setup ssh-agent so you don’t have to enter your pass phrases each time you run a command.
Syncing a Branch
The general workflow Stefan and I are using for MileLogr is derived from this post:
This means that whenever I’m doing any work on MileLogr I’m doing it in a feature branch. Our model is feature branches are generally per-developer and, if I weren’t trying to sync between two machines I probably would just keep them local (although making them remote has the benefit of providing backup).
Note we follow this guys advice and have two categories of branches: public & private. Public branches have a history that is “immutable, atomic, and easy to follow.” Private branches are disposable and malleable. Master and develop are public. Feature branches are private.
Here’s the workflow. Desktop is my desktop that I’m currently working on, where I’ve made changes I want available on my laptop.
Create feature branch
Let’s say I’m making some changes for a feature called demo. Below I create the feature branch and made a change to an existing file:
On my Desktop:
E:\code\milelogr [master]> git checkout -b demo Switched to a new branch 'demo' E:\code\milelogr [demo]> subl .\MileLogr\Views\_ViewStart.cshtml E:\code\milelogr [demo]> E:\code\milelogr [demo +0 ~1 -0]>
(Note, see how my prompt shows my git status? That’s posh-git at work. Love it!)
Committing and pushing work in progress
Now, let’s say I’m in the middle of hacking away and I need to run. But I want to be able to pick up where I left off on my laptop. I need to commit these changes and push them to a remote so I can pull them down to my laptop:
E:\code\milelogr [demo +0 ~1 -0]> git add MileLogr/Views/_ViewStart.cshtml E:\code\milelogr [demo +0 ~1 -0]> git commit -m "work in progress" [demo 5d1fcd1] work in progress 1 file changed, 1 insertion(+) E:\code\milelogr [demo]> git push origin demo Counting objects: 9, done. Delta compression using up to 4 threads. Compressing objects: 100% (5/5), done. Writing objects: 100% (5/5), 442 bytes, done. Total 5 (delta 3), reused 0 (delta 0) remote: bb/acl: bogus is allowed. accepted payload. To email@example.com:bogus/milelogr.git * [new branch] demo –> demo
Now, if you look on the remote (in my case on bitbucket.org) you’ll see the repo now has a new branch named demo.
Pulling a private branch
On the laptop, it’s now easy to pull this branch down.
On Laptop (note different path):
C:\Users\Charlie\code\milelogr [master]> git pull remote: Counting objects: 9, done. remote: Compressing objects: 100% (5/5), done. remote: Total 5 (delta 3), reused 0 (delta 0) Unpacking objects: 100% (5/5), done. From bitbucket:bogus/milelogr * [new branch] demo -> origin/demo
Already up-to-date. C:\Users\Charlie\code\milelogr [master]> git checkout demo Branch demo set up to track remote branch demo from origin. Switched to a new branch 'demo' C:\Users\Charlie\code\milelogr [demo]>
I can make changes on the laptop and when I’m done I can commit and push them by specifying the remote origin with the branch:
C:\Users\Charlie\code\milelogr [demo +0 ~1 -0]> git add MileLogr/Views/_ViewStart.cshtml C:\Users\Charlie\code\milelogr [demo +0 ~1 -0]> git commit -m "more work in progress" C:\Users\Charlie\code\milelogr [demo]> git push
I could setup tracking for a branch with
git branch --set-upstream demo origin/demo
and then subsequent git pull operations would just work. Or I can just
E:\code\milelogr [demo]> git pull origin demo
Integrating feature into develop
When it’s time to integrate this feature branch (demo) into our develop branch I’ll do this:
E:\code\milelogr [demo]> git checkout develop Branch develop set up to track remote branch develop from origin. Switched to a new branch 'develop' E:\code\milelogr [develop]> git merge demo … Merge made by the 'recursive' strategy.
Note that I did NOT use git merge –no-ff demo in the above example. Since we treat our public branches (develop and master) as ‘pure’, we do NOT want to fast-forward history when merging. Instead we will always ‘clean up’ a feature branch’s history before merging (see this post for why).
I can then delete the feature branch (locally):
E:\code\milelogr [develop]> git branch -d demo Deleted branch demo (was 303c53f).
If we’re really done with that demo feature branch we need to delete it from the remote too:
E:\code\milelogr [develop]> git push origin :demo remote: bb/acl: bogus is allowed. accepted payload. To firstname.lastname@example.org:bogus/milelogr.git - [deleted] demo
Finally, we push the develop branch to the remote:
E:\code\milelogr [develop]> git push origin develop Counting objects: 1, done. Writing objects: 100% (1/1), 231 bytes, done. Total 1 (delta 0), reused 0 (delta 0) remote: bb/acl: bogus is allowed. accepted payload. To email@example.com:bogus/milelogr.git
46a5f06..298561f develop –> develop
Hope this helps others.
Comments and suggestions on improving this workflow welcome below.
Make sure when you pull you are using –rebase (or config branch.autosetuprebase to true). Going back and forth between machines will result in a lot of merge commits otherwise. Since you cleanup your feature branches anyway, this may not matter, but it’s essential on master/develop. This is one of the biggest mistakes I see new git users make.
You might also consider a –squash when you merge or rebasing the source branch instead of merging it. The first is more of a personal preference (do you really want to keep the history from that other branch?), but I’ve learned to dislike merge commits of any kind.
Thanks. This makes sense. Once I actually do it I’ll update the post.
I’m curious. Why, exactly, is an accurate, verbose, merge-filled commit history a problem?
Personal preference, but it’s usually just noise. When making commits public it’s better to create a changeset for each meaningful chunk of work rather than publishing all the commits involved in getting there. Certainly merge commits when just pulling in the latest from master are irrelevant.
Can you describe how you configure this? I’ve heard stories of data corruption…
I also use Dropbox (as an independent developer) backed by both the Git repo and an upstream (private) GitHub just in case.
I’ve never had corruption over a 2-year mobile app that I actively developed using this method, and it is great, since you’re always in the “same mindset”, branch, etc. without having to commit to switch machines.
No configuration required on my end…
The link to MileLogr is broken
Great article! Thanks for sharing.
I was actually looking only for alternatives to Mintty .. ConEmu looks nice.
I suppose you also are installing msysgit to be able to ssh ? Or is there an alternative included in one of the packages ?