Git magic – Cherry Pick
Yesterday I submitted my patch for a Popcorn.js ticket touched 20+ files in the tree. The ticket wasn’t particularly complex, but there was a lot of different files touched and therefore a lot of places for stuff to go wrong. The ticket was about fixing issues with Popcorn.js’ unit tests and that they may be testing at intervals that are just too small (a half a second in some cases) and in other cases the intervals were way too long (upwards of 20 seconds). My fix was to go in and try and make all of the unit tests run in roughly the same amount of time. I figured around 5 seconds per each plugin was a reasonable time and thats what I rolled with. After about an hour or so of going through all of these different files and making small adjustments all over the place, I added my changes just like anyone would in git:
git status in order to see all of the files ready to be staged, which in this case were a lot. Normally I would do
git add filename-goes-here for each file that I touched, but just like every programmer, I’m lazy. I figured I’d be clever today and do it all in one go using the following command
git add *.* . Now while this command did the trick, it is also very dangerous. I would NOT recommend doing this unless you are completely sure that all of the files ready to be staged are the files that you want. Smiling and happy that I did this all in one go I quickly commited
git commit -m "Message explaining ticket" and then pushed to my remote repo
git push repo-location branch-name .
Also, a bit of a side track, but while im explaining this, a nifty trick I picked up a while back was adding remote repo locations and assigning them a name for easy and convienient access any time. To do so, you can use
git remote add name branch-location which in practice would probably look something like
git remote add dave git://github.com/dseif/popcorn-js.git . The awesome part about this is that you can easily access that repo when pushing by simply referencing the name you entered for that repo instead of the full path name each time. This becomes invaluable when you are pushing and pulling from multiple different repos frequently. To view the remote’s that you currently have simply type
git remote .
Anyways, back to my original issue. About an hour after I did my initial push I realized I pushed a whole file of bogus code that I left in from something I was testing the previous day. Before the ticket got reviewed I quickly added another commit remedying my crappy commit and changing the file. I figured this would be ok, but it was not. When people are reviewing your code they do not like jumping between numerous different commits, as it is hard to follow what is going on when pieces of code are being added and removed between commits. The reviewer kindly asked if I could fix this and add all of the proper data into one commit and re-commit. This didn’t seem like a big problem to me and I began trying all sorts of crap off the top of my head to try and get what I wanted, things like attempting to merge the two commits
git merge commit1 commit2 , using git reset to a past commit
git reset commit# , as well as other things. I was going in circles trying to solve a problem without actually trying anything intelligent. My next idea was to go speak with our resident git master jbuck about what to do. I remembered him having me do something called a cherry pick on a previous problem I had similar to this in the past. The problem here was that I couldn’t find jbuck, he was MIA, probably on lunch or something. I decided to take matters into my own hands, and actually start reading the git docs.
After a bit of reading I was much better informed on what cherry pick does. In simplest terms, it allows you to apply the changes from some commit to your current branch. This is pretty awesome as you can grab a commit from a completely different branch that you worked on a week ago and attempt to apply it to your current branch ( keep in mind, merge conflicts can happen ). So I ran the following command to cherry pick the commit that I needed
git cherry-pick commit# . As soon as I ran it I saw all of the commit data be applied to the current branch that I was working on. Awesome! The next problem I was faced with was that this commit still contained the bogus file from before and I needed to get rid of that. This required a fair bit more searching as I needed to un-commit the commit that I just applied and then only choose the relevant files this time for committing. This was done through a fancy command that I found through a stack-overflow thread (awesome site by the way). The command was
git reset HEAD^ which essentially resets to one commit before HEAD, but leaves the changes in. Another option is also
git reset --soft HEAD^ which does basically the same, but does not unstage the changes (leaving them ready to be committed). This was exactly what I needed and simply consisted of me removing the bogus file and re-committing. Success!
Doing stuff likes this is why git is so amazing, flexible, and powerful. Although I still dont know 1/100th of what there is to know in git, the more I learn each day, the more I understand how awesome it is.