Working with CodeKit and Git
Table of Contents
This is a guest post by Bryan Jones. He's the creator of the popular CodeKit Mac app.
Who I Am
My name is Bryan. I write a Mac app called CodeKit. It's an app for web developers that automates a bunch of tasks and makes building websites much faster. It's won several awards including: "2014 App I've Never Heard Of" from John Gruber and "Why Does This UI Look So Familiar?" from Panic.
The folks at Tower asked me to write about my experience with Git and how you can use CodeKit and Git together. They will regret this.
What Git Means to Me
"This is Unix. I KNOW THIS!" some of you are saying. You've forgotten more about computers than I'll ever know. You're just reading this because you heard I write funny release notes...
Git is your personal slave, willing and eager to bend to your every whim. You poor soul; that's exactly where she wants you. One day you're just writing some code and then...GODDAMN RAPTORS FALLING OUT OF THE CEILING WHAT DID YOU DO TO THE REPO WE'RE SUPPOSED TO SHIP TOMORROW!
And this isn't just my opinion. Ever seen the source code for Git? Here's the top of the
This is why I use Tower. It's still Git, but it's like Git on Xanex: 50% less likely to sever my aorta.
Using CodeKit With Git
At its core, CodeKit is an app that watches files for changes and takes action when those changes happen.
- You save a Sass file, CodeKit compiles it to CSS.
- You save a JS file, CodeKit minifies it.
- You save a Gif, CodeKit asks you to pronounce "G-I-F" and deletes your hard disk if you get it wrong. (I don't care if you created the damn format. Pronouce it correctly.)
You know what's really good at changing files? Git. This sometimes causes people trouble. Here's how:
Say we're building a website. In our project folder there's a file named "main.js". In CodeKit, we've set some custom options for this file. Then, we decide to switch branches. All of a sudden, we discover that CodeKit now has completely different settings for "main.js"! After 20 seconds of cursing, we open up Mail.app and send Bryan a nasty email: "HOW CAN YOU SUCK AT PROGRAMMING THIS MUCH!? DID YOU ALSO WRITE discoveryd? YOU HAVE RUINED MY LIFE. I'M SWITCHING TO GRUNT."
Here's What Happened:
1. We switch branches in Git.
2. Git notices that the file "main.js" has some differences between branches.
3. Git deletes the existing "main.js" file from our project folder.
4. CodeKit sees that "main.js" has been deleted. It says, "Cool. We can forget the settings we had for that file."
5. Git now re-creates a file named "main.js" with the content of our new branch.
6. CodeKit sees that a new file named "main.js" has been created.
7. Since there are no saved settings for this file, CodeKit applies the default options we have specified for the project. Boom: our custom file settings have warped back to the defaults for JS files in this project!
Here's How To Avoid It:
2. Un-pause CodeKit's file watching.
3. Send Bryan an apology email.
It's super easy to pause CodeKit: just hit command + option + control + / (It's the three keys next to the spacebar, plus a slash — like you're typing a comment!) This shortcut is global; it works even if CodeKit is in the background. Or choose File > Pause File Watching from CodeKit's menu.
Anytime you're going to perform a Git action that will change many files at once (a pull, branch-switch, etc.) you need to pause CodeKit's file-watching first. That's all there is to it!
Why Can't CodeKit Just Auto-Ignore Git Changes?
RIGHT? CodeKit gets file-change information directly from the OS X kernel. The trouble is that the kernel doesn't report what caused a file-change event; it just reports that the event happened. So, to CodeKit, a file-delete event from Git looks exactly like the event that happens when you drag a file to the trash. The app can't tell the difference.
I've asked Apple Engineering to improve OS X by including the process ID of whatever app caused a file-change event. Their response so far has been: "HOW DARE YOU SPEAK TO US, PEASANT." Then they send Bob Mansfield after me. You'd think Bob can't run that fast. You would be wrong.
What About CodeKit's Config File?
CodeKit stores project settings in a special file named "config.codekit". Let Git track this file and commit it along with the rest of your project. This is how you sync CodeKit settings across teams. You change a setting, then commit your config.codekit file. Your teammate pulls the repo and gets your new config file. When she un-pauses CodeKit, the app will immediately apply the settings from the new config file and boom—you're both on the same settings.
The config.codekit file is plain-text JSON, so conflicts are easily resolved. Just compare the contents of the conflicting versions and choose the one that has the settings you want.
It helps a lot if you start a team project by getting everyone on the the same initial settings. Have one person tweak settings as needed, commit their config.codekit file and then have everyone else pull that down and start working from there.
And finally, don't forget that settings may change if you switch branches! You might have a "debug" branch where you set CodeKit to create source maps, and so on. Then, you might have a "release" branch where source maps are off and everything is minified. Each branch has a different config.codekit file that contains different settings. When you switch and unpause CodeKit, the app warps to your new settings from that branch.
How I Use Git
No, Seriously, How Do You Use Git?
For me, Git is like Time Machine: a way to back up my work and roll back to a previous version when I realize
I have my intern has seriously screwed up.
I don't use too many branches. If I branch, it's for major things. Like if I were theoretically working on CodeKit 3, I'd have a separate branch so that I could switch back to my 2.0 branch and continue releasing updates for the existing app.
I might create a branch for a major new feature if I expect to be working on it for a long time. Otherwise, I tend to use a single "main" branch and just commit as I go. Many of my commits are with the repo in a "working" state, meaning it's not ready for release. (Did you feel that? All of Silicon Valley just shuddered in disgust.) I do, however, place a commit each time I release an update to the public. That way, I can always go back to a certain release and branch off of it if a super critical bug pops up that I need to fix immediately.
If I could give you only a single life tip, it would be: "commit more often."
This situation happens to me a lot: I haven't pushed any commits for a week. I make a bunch of changes to some file with 7,500 lines of code. The changes I just made today are stupid. The changes I made to this file four days ago would make grown men with 350,000 reputation on Stack Overflow weep with joy. "Should...have sent...a poet." So now I've got this file with changes I DO want and changes I don't. And so begins the process of "Lohan-ization" wherein I attempt to sort out a gigantic freaking mess under court supervision and, usually, alcohol.
I've also learned to never update dependencies across branches. ("NEVER cross the streams!") For example: CodeKit, just like 9,592 other apps, uses Sparkle for update-checking. A few months ago, I updated Sparkle on a separate branch and then merged those changes to my "main" branch. I shipped an update. Turns out, Git incorrectly merged some files that make up Sparkle and broke the whole damn thing. This was a huge problem because it left my users stranded on an old version of the app—without a working update-checker, they would never see any future releases! I never got any conflicts or errors; Sparkle just silently broke.
I should point out that this style of Gitting (is that a word outside of Alabama? Screw it, I'm making it a word.) works for me because I'm not in a team environment. If you're working with other developers, you should probably read a better tutorial on Git workflows.
So Long And Thanks For All The Fish
So there you have it. My take on Git. Plus at least 41 pop culture references. If you need me, I'm @bdkjones on Twitter and, if you'll excuse me, I'm now going to get back to improving CodeKit. Because my doors still go like this: <-- --> and not like this: ^-- --^. 42.