I don't think GitHub's default workflow for forking a repo is ideal for my needs. Most of the time when I fork a repo, my aim is to fix something or add a small new feature which I will eventually submit upstream as a pull request. While I am hacking away or living with my modifications for a while before I submit them, I want to continue to be able to easily pull in upstream changes as well. So whenever I fork a repo, I want the following:
masterbranch will track the pristine upstream master. Whenever I have no changes of my own pending, I can just switch my work tree to use
masterto keep up with the latest changes with a simple
- A volatile
hackbranch where I work on my changes. This is volatile since I'll regularly be rebasing onto the head of
- A volatile
pullmebranch where I will put changes that are ready for a pull request submission. I may want to continue to hack away in
hackwhile I'm waiting for the resolution of my pull request, so this is analogous to a release branch. But this is volatile since, for the sake of simplicity, I'll be resetting it whenever I'm ready to submit a new pull request.
(By volatile, I mean that I can feel free to rewrite the history on these branches without repercussions.)
So here's how I go about forking a GitHub repo:
git clone <upstream_repo> # e.g. http://github.com/mxcl/homebrew.git cd <upstream_repo_name> # e.g. homebrew git remote add fork <forked_repo> # e.g. firstname.lastname@example.org:SethMilliken/homebrew.git git config branch.hack.remote fork git config branch.hack.merge refs/heads/hack git branch --no-track hack origin/master git push fork hack:refs/heads/hack git checkout hack
At this point here's how things look branches-wise:
$ git branch -r * hack master remotes/fork/hack remotes/fork/master remotes/origin/HEAD -> origin/master remotes/origin/master
remotes/fork/master is what the GitHub website uses for it's default branch. I like to clean this up to remove any chance of ambiguity about the name
master. To me,
master always means the authoritative branch from the canonical repository, never a branch in my fork. Here is how I tidy this up:
- Click the
Adminbutton on the forked repo's GitHub page.
- Change the
Default Branchto "hack".
- Delete the forked repo's
git push fork :master.
If there were a way to name the default branch when you first fork, or to subsequently rename it via a commandline git command, that would simplify things a bit.
To rebase my changes onto incoming upstream, which I do periodically as I'm hacking, and again immediately before submitting a pull request:
git pull --rebase origin master
When I'm finally ready to submit a pull request, here is what I do:
git branch -f pullme hack git push fork pullme:refs/heads/pullme
This essentially replaces the
pullme branch with the current state of the
And after my pull request is accepted, I switch back to
git checkout master
git hg tech
I have become accustomed in Git to using
git stash to temporarily set aside in-progress changes in my working directory before pulling from upstream. Whenever I use Mercurial now, I find myself wanting analogous functionality. The Googles don't seem to turn up any decent techniques for this, so I have repeatedly reinvented a series incantations to abuse Mercurial's
mq extension to provide it. Yet I always manage to forget whatever trick I used by the time I want to do it again. I finally decided to fix it for good, giving myself
hg stash and
hg pop, allowing a workflow in a dirty working copy that goes something like:
hg stash hg fetch hg pop
Here are the relevant
[alias] stash = !hg qinit &>2 /dev/null; hg qqueue --create stash-temp && hg qnew stash && hg qfinish tip && hg strip tip && hg qqueue patches && hg qqueue --purge stash-temp pop = !hg -R .hg/strip-backup/`ls -rt .hg/strip-backup/ | tail -1` diff -r tip | patch -p 1 -R
Breaking it down:
# hg stash hg qinit &>2 /dev/null # initialize mqueue in this directory # ignore error output since this has likely already been done # (this could be potentially be problematic if there is some other problem) hg qqueue --create stash-temp # create a new patch queue so we don't mess with any existing ones hg qnew stash # create the 'stash' patch with the outstanding modifications hg qfinish tip # apply the patch hg strip tip # now strip it (for the side-effect of the backup bundle) hg qqueue patches # switch back to original patch queue hg qqueue --purge stash-temp # delete the temporary patch queue
# hg pop hg -R .hg/strip-backup/`ls -rt .hg/strip-backup/ | tail -1` # use the strip bundle as the repository # finding it with some shell tricks # (likely a better, more portable way to do this) diff -r tip # output it as a diff patch -p 1 -R # apply that output as a patch # and reverse patch since we spun things around # with the bundle
Your working copy's
.hg/strip-backup/ directory could start to fill up if you use this a lot, so you may want to clean it out occasionally. I decided to not have
hg pop automatically cleanup the bundle just in case something goes wrong.
And why not just
hg diff > stash.diff && hg revert --all to stash and
patch -p 1 < stash.diff to pop? That doesn't really handle binaries, and somehow it feels a little too dangerous to me. But, yeah, for the majority of cases that would probably be sufficient. So I'll leave that here, too:
[alias] stash = !hg diff > stash.diff && hg revert --all pop = !patch -p 1
I have been using Firefox as my primary browser for quite a while now. What held me back from switching initially were two things: 1) unacceptably poor non-native widgets, which have since improved dramatically and 2) no support for the Mac OS X Keychain, which is now available via the Keychain Services Integration extension. But eventually my desire for vim keybindings everywhere pushed me to switch despite these shortcomings. The
vimperator pentadactyl Firefox extension allows for entirely keyboard-based browsing, using bindings similar to vim. And even better, it facilitates form field editing using vim via external editor support. So when I want to edit a form field, I type either "gi" (to enter the field) or "f#" (where # is the QuickHint mode number associated with the field; useful if there are multiple fields on the page) then hit Control-i. This creates a new tab with a buffer for that field in an existing instance of MacVim which is always running in the same Space as my browser, so it comes up almost instantaneously. I edit the contents of this buffer using all the goodness of native vim and then type ZZ. Thanks to a couple of autocommands in my .vimrc, this saves the buffer, saves a copy of its contents in a timestamped archive file, closes the buffer, and then hides MacVim, leaving Firefox active with the text I just wrote inserted in the form field, ready to submit.
Here are the requisite pieces of the puzzle:
This is the pentadactyl configuration file. You need to add a line to set your external editor to the script that opens MacVim.
Make a symbolic link to your mvim script so that the script can detect the context it should run in.
ln -s ~/bin/mvim ~/bin/firefox
This is the mvim script that comes bundled with MacVim, moved to ~/bin and modified. Add this bit to the 'case "$name" in' statement:
*firefox) opts="$opts --remote-tab-wait-silent" gui=true ;;
Next, a couple of additions to the vim configuration file to make ZZ save the contents to an archive, close the buffer, and return Firefox to the foreground.
augroup VimperatorYPentadactyl au! BufRead vimperator-*,pentadactyl-* nnoremap <buffer> ZZ :call FormFieldArchive() \| :silent write \| :bd \| :macaction hide:<CR> au BufRead vimperator-*,pentadactyl-* imap <buffer> ZZ <Esc>ZZ augroup END
Finally, here is the function that saves a copy of the form field contents to an archive file, complete with timestamp and url:
function! FormFieldArchive() let l:contents = getbufline("%", 1, "$") let l:filepath = expand("%") let l:filename = expand("%:t:r") let l:formfielddir = $HOME . "/webforms/" let l:currentdate = TimestampText('date') let l:entry = l:formfielddir . l:currentdate . ".txt" let l:entryexists = filereadable(l:entry) exec "split " . l:entry if l:entryexists normal Go normal o exec "call setline(\".\", \"" . TimestampText('time') . "\")" else exec "call setline(\".\", \"" . TimestampText('journal') . "\")" exec "silent !svn add " . l:entry endif normal o exec "call setline(\".\", \"" . l:filename . "\")" normal o exec "call setline(\".\", " . string(l:contents) . ")" write bd endfunction function! TimestampText(style) let l:iswindows = has("win16") || has("win32") || has("win64") if l:iswindows if a:style == "long" let l:dateformat = strftime("%#x %H:%M:%S ") elseif a:style == "short" let l:dateformat = strftime("%Y-%m-%d %H:%M:%S ") endif let l:dateformat .= substitute(strftime("%#z"), '[a-z]\+\($\| \)', '', 'g') else if a:style == "long" let l:dateformat = strftime("%Y %b %d %a %X %Z") elseif a:style == "journal" let l:dateformat = strftime("%A, %B %d, %Y %H:%M:%S %Z") elseif a:style == "short" let l:dateformat = strftime("%Y-%m-%d %H:%M:%S %Z") elseif a:style == "time" let l:dateformat = strftime("%H:%M:%S %Z") endif endif if a:style == "date" let l:dateformat = strftime("%Y-%m-%d") endif return l:dateformat endfunction
This will create a new file with the current date, e.g.
"2010-03-15.txt", if it doesn't already exist. If it does exist, it will add the entry to the end of the file separated by a timestamp.
TimestampText() is just a convenience function I wrote that returns a timestamp in various
TimestampText()function for completeness. A couple of other minor edits and links added.]
During a dream last night I devised a technique to cut and paste between dreams and reality. I cleverly pasted the description of this technique on the shared dream/reality clipboard. I was thwarted by the fact that alarm clock snooze bars clear the clipboard.
NetNewsWire now supports Moveable Type. This is a test of that functionality. If it works well, maybe I'll post more. On a completely unrelated note, I've been experimenting with leaving Sticky Keys turned on to see if I can improve the state of my wrists which have both taken a turn for the worse due to various combinations of foosball, swing dancing, playing guitar, and typing.
ssh tunneling is a great lightweight vpn stand-in. You do need three things: an account on a externally accessible machine that is on the same internal network as the machine you're tunneling to, the IP address of the machine you're tunneling, and knowledge of what port(s) you need to forward. The syntax is: ssh <your account>@<externally accessible machine> -L <local port>:<internal target host>:<port to forward>