unethical blogger - Slide
http://unethicalblogger.com/taxonomy/term/14/0
Over-arching category for most things pertaining to Slide, Inc.enBeing a Libor, Addendum
http://unethicalblogger.com/posts/2010/05/being_libor_addendum
<p>A couple of weeks ago I wrote a post on how to "<a href="http://unethicalblogger.com/posts/2010/04/be_libor">Be a Libor</a>", trying to codify a few points I feel like I learned about building a successful engineering team at Slide. Shortly after the post went live, I discovered that Libor had been promoted to <a href="http://www.slide.com/corp/about-us.html">CTO at Slide</a>.</p>
<p>Over coffee today Libor offered up some finer points on the post in our discussion about building teams. It is important, according to Libor, to maintain a "mental framework" within which the stack fits; guiding decisions with a consistent world-view or ethos about building on top of the foundation laid. This is not to say that you should solve all problems with the same hammer, but rather if the standard operating procedure is to build small single-purpose utilities, you should not attack a new problem with a giant monolithic uber-application that does thirty different things (hyperbole alert!).</p>
<p>Libor also had a fantastic quote from the conversation with regards to approaching new problems:</p>
<blockquote>
<p>Just because there are multiple right answers, doesn't mean there's no wrong answers</p>
</blockquote>
<p>Depending on the complexity of the problems you're facing there are likely a number of solutions but you still can get it wrong, particularly if you don't remain consistent with your underlying mental framework for the project/organization.</p>
<p>As usual my discussions with Libor are interesting and enjoyable, he's one of the most capable, thoughtful engineers I know, so I'm interested to see the how Slide Engineering progresses under his careful hand as the new CTO. I hope you join me in wishing him the best of luck in his role, moving from wrangling coroutines, to herding cats.</p>
<p><a href="http://icanhascheezburger.com/2007/05/13/god-speed-moon-cat/">God speed mooncat</a></p>
http://unethicalblogger.com/posts/2010/05/being_libor_addendum#commentsAptureOpinionPythonSlideSoftware DevelopmentTue, 18 May 2010 16:00:00 +0000R. Tyler Croy286 at http://unethicalblogger.comBe a Libor
http://unethicalblogger.com/posts/2010/04/be_libor
<p>I reflect occasionally on how I've gotten to where I am right now, specifically to how I made the jump from "just some kid at a Piggly Wiggly in Texas" as <a id="aptureLink_7fpgpX6rLb" href="http://twitter.com/stuffonfire">Dave</a> once said, to the guy who knows <em>stuff</em> about <strong>things</strong>. I often think about what pieces of the <a id="aptureLink_CJpdUZmrfu" href="http://twitter.com/slideinc">Slide</a> engineering environment were influential to my personal growth and how I can carry those forward to build as solid an engineering organization at <a id="aptureLink_jd3j6BSrUf" href="http://www.apture.com">Apture</a>.</p>
<p>The two pillars of engineering at Slide, at least in my naive world-view, were Dave and <a id="aptureLink_xrzzjPhkPZ" href="http://www.facebook.com/libor.michalek">Libor</a>. I joined Dave's team when I joined Slide, and I left Libor's team when I left Slide. Dave ran the client team, and did exceptionally well at filling a void that existed at Slide bridging engineering prowess with product management. Libor often furrowed his brow and built some of the large distributed systems that gave Slide an edge when dealing with incredible growth. In my first couple years I did my best to emulate Dave, engineers would always vie for Dave's time, asking questions and working through problems until they could return to their desk with the confidence that they understood the forces involved and solve the task at hand. Now that I'm at Apture, I'm trying to emulate Libor.</p>
<p>(<em>Note</em>: I do not intend to idolize either of them, but cite important characteristics)</p>
<p>To understand the Libor role, the phrase "the buck stops here" is useful. A Libor is the end of the line for engineering questions, unlike some organizations the "question-chain-of-command" is not the same as the org-chart. If a problem or question progressed up the stack to a Libor, and between an engineer and a Libor the pair cannot solve the problem, <em>you're screwed</em>.</p>
<p>What does it take to be a Libor you may be thinking:
<!--break-->
* <strong>No Guessing:</strong> When acting as a Libor, <em>knowing</em> is crucial. That is not to say you must understand everything about all the nooks and crannies of the code-base, but when you give an answer it is crucial you actually know what the hell you are talking about. The consequences of being wrong are far worst than the consequences of not knowing, if a fellow engineer builds on your guess, when that code ships live in a few days/weeks there is a serious risk of everything falling over.</p>
<ul>
<li><p><strong>Grok the stack:</strong> A Libor is expected to hold a wealth of information internally, much like a clock maker, a Libor should understand where every single gear and spring fit together in a large complex system. It is not necessary to understand how each component individually works but instead, understand how all the pieces operate in concert. Some amount of acting as a Libor requires direct discussions with the operations team as well as the rest of engineering, when all that JavaScript and Python rolls out to 10, 20, 100, or 1,000 machines, somebody should have at least considered the ramifications of adding 3 more database calls to every request, that's the Libor.</p></li>
<li><p><strong>Maintenance and accountability:</strong> Typically working at the lower ends of the stack, a Libor has to relive and tolerate last month's and last year's short-sighted decisions over and over. A Libor should not let himself nor colleagues "fire and forget" code, poor judgement will haunt a Libor for much longer than most people's New Year's resolutions. Because of this mistake-longevity, a Libor should be quite concerned with how well thought-out and tested new changes, particularly drastic ones, are.</p></li>
<li><p><strong>Focus on Engineering:</strong> Code quality and extendability are Libor's primary focus, that is not to say that a Libor's role is to impede product development, but rather ensure that it is properly framed. While a product manager's primary concern may be to get a feature deployed as soon as possible, the primary concern of a Libor is to ensure that once that feature is shipped it doesn't break or otherwise degrade the quality of service of the rest of the site. When interfacing with other engineers a Libor should be asking questions about code, intentions and implementation. Code review is as important as communication with the team, flatly rejecting code is unacceptable, but discussing with engineers the potential pitfalls of certain approaches ensures that the group moves forward.</p></li>
</ul>
<p>Playing the Libor character at Apture has been interesting to say the least, I've done a lot of work getting a number of systems in place to help educate my decisions, particularly in our production environment. Focusing on the entire stack as a complex system has allowed us to make some adjustments here and there that have literally started to pay dividends the day after they ship.</p>
<p>Non-engineering also benefits from having a Libor character in the organization, at Apture the product development narrative has changed, I find myself emphasizing:</p>
<blockquote>
<p>Tell me what you want, we'll find a way to do it</p>
</blockquote>
<p><em>That's</em> <a href="http://twitter.com/tristanharris/status/8355935929">a breakthrough</a>.</p>
http://unethicalblogger.com/posts/2010/04/be_libor#commentsAptureOpinionPythonSlideSoftware DevelopmentFri, 30 Apr 2010 14:45:00 +0000R. Tyler Croy281 at http://unethicalblogger.comDaniel felt left out.
http://unethicalblogger.com/posts/2009/10/daniel_felt_left_out
<p>Dear Slide, I luv u guys, srsly. kthxbai.<center><br />
<img src="http://agentdero.cachefly.net/unethicalblogger.com/images/iheartdaniel.jpg"/><br />
<small><em>Photo credit: <a id="aptureLink_EzaWOtAnTZ" href="http://twitter.com/ItsTheCroz">Meghan</a></em></small><br />
</center></p>
http://unethicalblogger.com/posts/2009/10/daniel_felt_left_out#commentsSlideSat, 24 Oct 2009 00:59:10 +0000R. Tyler Croy236 at http://unethicalblogger.comEnd of a journey
http://unethicalblogger.com/posts/2009/10/end_journey
<p><em>See parts <a href="http://unethicalblogger.com/posts/2009/10/my_journey_slide_part_1">1</a>, <a href="http://unethicalblogger.com/posts/2009/10/my_journey_slide_part_2">2</a> and <a href="http://unethicalblogger.com/posts/2009/10/my_journey_slide_part_2">3</a></em></p>
<p>My journey at <a id="aptureLink_AFX5uK9A2m" href="http://twitter.com/slideinc">Slide</a> comes to an end today, when I leave this evening
I will once again return to being a free agent (if only for two days).
Some of my coworkers have casually referred to my writings over the
past few days of my "memoirs", which isn't too far off to be honest.
When I leave Slide this evening, my employment will have accounted
for roughly 10% of my entire life and 50% of my adult life. Writing
my side of the story down, to some extent, has been more about telling
a story to myself and less about telling it to anybody else (apologies).
So much of my time spent at Slide has been in a state of controlled
choas that at times it's hard for me to remember when things were done,
who was doing them and the order in which they happened.</p>
<p>The two questions I've invariably gotten since I gave my notice and
subsequently started writing this series of posts have been:
why are you leaving and where are you going? My reasons for leaving
are irrelevant, I will say that if I could take the people I've been working
with at Slide with me, I would. I've learned such an incredible amount
from those that I've worked with, both technical and non-technical, while
at Slide. Whenever I would pitch friends on the idea of joining Slide, my
take-home point was always "when you join Slide, you will not be the
smartest person there." I feel lucky that I was given the opportunity to
"come of age" as a young engineer in a company of so many tremendously talented
individials, given the chance for a do-over, I would still play my cards the
way I've played them. I joined Slide a punk kid from Texas, I'm leaving
Slide a slightly-more-learned punk kid from Texas.</p>
<p>As to where I am going, after an extended vacation of Saturday and Sunday,
I will be joining my second startup ever (Slide's a pretty good
first time out) on Monday. When I started looking at other companies I had a couple
of criteria, I wanted to join a smaller team (Slide's upwards of a hundred people
or so) and I had to really like who I would be working with. <a id="aptureLink_mYybewEzku" href="http://twitter.com/tristanharris">Tristan</a>, <a id="aptureLink_Hoou8TMc1I" href="http://www.linkedin.com/pub/2/87B/3B5">Jesse</a>, <a id="aptureLink_ihO2we1IkW" href="http://twitter.com/cansar">Can</a> (John)
and the team from <a id="aptureLink_msqObwpdC5" href="http://twitter.com/apture">Apture</a> fit both criteria. I'm not going to go into detail
about what I'm really going to be doing there or where Apture is going as a
company. I will say that after two and a half years of working and studying
at Slide, I'm looking forward to employing what I've learned and continuing
my education at Apture.</p>
<p>See you on the beaches of the world.
<!--break--></p>
http://unethicalblogger.com/posts/2009/10/end_journey#commentsMiscellaneousOpinionSlideFri, 23 Oct 2009 15:29:19 +0000R. Tyler Croy235 at http://unethicalblogger.comMy journey at Slide (part 3)
http://unethicalblogger.com/posts/2009/10/my_journey_slide_part_3
<p><em>Continuing on from <a href="http://unethicalblogger.com/posts/2009/10/my_journey_slide_part_1">part 1</a> and <a href="http://unethicalblogger.com/posts/2009/10/my_journey_slide_part_2">part 2</a></em></p>
<p>Prior to joining <a id="aptureLink_6j6T0qZh7l" href="http://en.wikipedia.org/wiki/Slide">Slide</a>, a friend of mine <a id="aptureLink_V4T5uiewvy" href="http://twitter.com/whurley">"whurley"</a> had nicknamed
me the "Angry Young Man" which I promptly put on my first set of
business cards (my current business cards list my title as "Meta-Chief
Platform Architect, Enterprise Edition", I received them after mentioning a failed
poaching attempt by LinkedIn to Max); when Top Friends went dark
on Facebook, I was a little more than an "angry young man."</p>
<p>Given my close involvement with the product, the amount of sleepless
nights working on it, the actions against Top Friends felt personal to
me, regardless of the posturing between Slide and Facebook's
executives. As hours turned into days offline, it became clear to me
that the suspension of the application was far less about our privacy hole
and far more about Facebook making an example out of Top Friends to
the rest of the platform development community. The message was heard loud
and clear by the majority of the developers that I knew, this is not your
platform, these are not your users and you will play by our rules or we
will wipe you from the face of the site. Building on the platform
was not only no more fun, it was also a risky business decision.</p>
<p>At the time of the suspension, Keith and I had already started discussing
what a "TopFriends.com" might look like, as the signals of platform
instability for applications were already being sent. When Top Friends went
offline, I prepared a few page outline for Max and Keith detailing "my vision"
for what Top Friends would become, I was convinced by that time that its
future lie as a social network unto itself, rather than a network contained
bu another network (yo dawg..). Not content to simply be "vanity and personal
expression" inside of Facebook, I wanted Top Friends to become a separate
entity by itself, your VIP club on the internet, at one point there was
even executive support for the drawing of users away into a destination site
for Top Friends. When the seven days of suspension were over and Top Friends
came back online, Slide's strategy shifted drastically. Our new mission for TF on
Facebook was to "get as close to Facebook as you can," we were to integrate
into a user's experience as much as conceivably possible. Previously we
had wanted to run as far away from Facebook as we could, taking our users
with us, but the fear that was enstilled by the application suspension
caused us to rethink that stance and push Top Friends to be a squeeky clean
platform citizen, while we contemplated a possible exodus for FunSpace and SuperPoke!.</p>
<p>Around this time in Slide's history I became quite jaded and cynical with
regards to the platform, Top Friends had been neutered by Facebook, and my
notion of what Top Friends should have been was neutered by Max. Regardless,
we still had plenty of work that needed to be done to try to succeed with our
new strategy. Months prior, Tony Hsieh (not the Zappos guy) the original
Top Friends PM had failed to win the visa lottery and moved back to China,
leaving TF without a product manager for some time. While we continued to
look for senior PM to take on the role, I had to play both product and engineering
manager (with help in both places every now and again). Quite the twist of
fate for me, I had often poked fun at PMs at Slide, once creating a powerpoint (one should
speak the language) titled "PM Flowchart". The presentation consisted of one slide,
with a fairly simple state diagram on it, one block labeled "Write Spec" had an
arrow pointing to another block labeled "Bitch." which pointed back at "Write Spec".
Suffice to say, product managers and I usually had a tenuous relationship.</p>
<p>Passionate about the product to begin with, I started meeting more and more often
with Max and Keith to discuss product strategy for TF, in between doing my "real job" of
Engineering Manager. Some meetings Keith and Max would square off and I would sit back
and watch, other times Keith and I squared off against Max, I rarely took Max's side against
Keith's though. Not that I always disagreed with Max, but he was at a slight disadvantage
in these discussions, Keith and I generally shared a lot of fundamental ideas of what TF should be,
stemming from months of discussing the product by his desk before he ever "officially" worked
with the project. The transition over a year and a half from quivering in fear as the director
of engineering cursed at me on Dave's house phone, to arguing with the CEO about
the product <strong>he</strong> pitched me on, was surreal to say the least. How I didn't get fired
is either a testament to my charm or Max's patience.</p>
<p>In fall of 2008, when <a id="aptureLink_soRM7vKl9e" href="http://www.linkedin.com/pub/seema-kumar/1/42/92a">Seema</a> finally joined as the Top Friends product manager, not only was
I more than ready to relinquish the post, Top Friends was in the midst of
an identity crisis. Our "facebook zerg rush" strategy of getting closer and closer
to the platform played out as you might of expected (hindsight and all), Facebook
redesigned the profile, changed viral communications channels and did a lot
of things that were likely good for Facebook, but terrible for applications.
TF had a lot of momentum on the "old profile" thanks to users dragging the TF
profile box <em>all the way up</em> on their profiles. When Facebook rolled out their new profile
which put applications not in the backseat, but in the way-back seat, the strategy
of "be lovey dovey with Facebook" started to break down, they weren't being
lovey dovey back.</p>
<p>Times were also changing outside of Top Friends at Slide, the <a id="aptureLink_S9NqCRPAas" href="http://twitter.com/sppets">SuperPoke! Pets</a> product was
starting to take off and actually <strong>make</strong> money directly from users. This was
important! Users, giving us money, for pixels! Brilliant! Being a much more reliable
revenue stream than the advertising oriented model that FunSpace, SuperPoke! and Top Friends
had been built around, Pets quickly became the "top" product at Slide.
With ad revenue drying up for Top Friends, we were tasked
with experimenting with virtual currency (like Pets) and ultimately "premium items"
(like Pets) within Top Friends. It seemed almost as if Top Friends was changing
visions, strategies and directions on a bi-weekly basis. One week we were building
virtual currency experiments with "Top Dollars", the next, virtual economy
experiments with an "Own your friends' profiles" feature, the next, premium virtual
goods with "Top Gifts". As the "Top Friends guy" and the manager of the engineering team,
I was so confused and disoriented about what we <strong>actually did</strong> and where we were
actually heading, I didn't stand a chance at convincing Paul, Geoff and Jason of it.</p>
<p>2008 winding down, the writing was on the wall, Top Friends was not going to live long,
at least the Top Friends Team wasn't. We had gained a reputation of being very
self-sufficient and competent, but with that autonomy came uncertainty from outsiders.
I regularly had to remind coworkers that I was a Slide engineer, not a Top Friends
engineer, regardless of the TF team's internal view of itself as a "microstartup."
When we failed to meet goals set out for us, it was decided that the staff behind
Top Friends were too valuable to spend time on a failing product.</p>
<p>Jason, Paul and Seema went to start a new project,
while Geoff and I, together since the desktop client days, joined the Server/Infrastructure team.
My personal "love" for Top Friends had all but dissolved by this point, I was sick of
Top Friends, I was sick of Facebook, I was sick of policy, I didn't care all that
much about the product anymore. The breaking up of the team though, was crushing.
As far war metaphors go, the TF team was a small rag-tag group of guerrillas, capable
of taking large projects and finishing them in record time. We often talked about
what we did as "playing jazz music" because our work had an improvisational style,
but the trust and understanding of where we all fit into the act, allowed us
to tackle large tasks in stride; that was all over though. The dream team was broken up.</p>
<p>My time on the server team at Slide is unfortunately a boring story of working
with stellar engineers capable of writing solid code and deploying it without
incident. As exciting as wood filler "this worked out just fine, the end."
After years of frenzy with Top Friends and the Facebook platform,
my first project for the server team took three weeks to build, was pushed
without a hitch and has only required two minor updates since. With my nose
to the grindstone building services and scalable architecture, I went
months without particularly concerning myself with "product direction", company
strategy and their ilk. The closest I would come to application development would be
jumping up into application code to fix bugs, all the while cursing app developers'
laziness while conveniently forgetting how often I was guilty of the same offense
in my tenure with Top Friends.</p>
<p>When I finally stuck my head back up, near the end of the summer, I started to
realize that I was working at a different company than I remember joining. Slide
had grown tremendously and changed direction once again. Since stepping back from
the front-lines, I had changed and Slide had changed too.</p>
<p>It was about time Slide and I started seeing other people.</p>
<p><strong>Continue on to <a href="http://unethicalblogger.com/posts/2009/10/end_journey">the end</a></strong></p>
http://unethicalblogger.com/posts/2009/10/my_journey_slide_part_3#commentsMiscellaneousOpinionSlideThu, 22 Oct 2009 09:08:05 +0000R. Tyler Croy234 at http://unethicalblogger.comMy journey at Slide (part 2)
http://unethicalblogger.com/posts/2009/10/my_journey_slide_part_2
<p>When I finished up writing <a href="http://unethicalblogger.com/posts/2009/10/my_journey_slide_part_1">part 1</a> of my journey at <a id="aptureLink_fx47bCG79W" href="http://www.crunchbase.com/company/slide">Slide</a> yesterday, I had
just recounted becoming "the Top Friends guy", savvy readers might have noted
that I had <em>not</em> moved off of Dave's couch at the time. I am uncertain whether
it is a record to be proud of, but I held the position of "the guy on Dave's
couch" for two months. With the leadup to the "F8" conference I didn't have a
whole lot of time to find an apartment, Dave being an all around nice guy and
amazing cook, wasn't helping my motivation to leave either. That said, I'm a
delightful house guest, honest.</p>
<p>Shortly after the initial successes of the Top Eight product, and the launch of
"FunWall" (renamed "FunSpace" later), Slide quickly converted the desktop client
team to the "Facebook Team" with 4-5 engineers hacking on Facebook applications
to capitalize as quickly as possible on the wild-west nature of the platform at
the time. We subsequently launched another couple apps, such as "My Questions" an application
that allowed you to poll your friends (likely our most "useful" application). I
ended up writing another application alongside Top Eight called "Fortune Cookie",
contrasted to My Questions, it was probably our most useless application. The application
was absolutely brilliant (Mike and Max get credit here again), the profile box for the application was a picture of a fortune
cookie with a fortune overlaid. <strong>Brilliant</strong>. If/when the user clicked through to the
application's canvas page, they were met with a simple grid of checkboxes and friends'
faces, checkboxes checked with a giant blue button that said "Invite your Friends!".</p>
<p>Never underestimate the power of "Select All", Fortune Cookie <strong>exploded</strong>, alongside
our "Magic 8 Ball" application (guess what that was), it spread through the Facebook
ecosystem like an epidemic. By mid June Top Eight was renamed Top Friends after
we bumped the number of "top friends" you could list from 8 to 24 (<strong>innovation!</strong>);
with the power of an intrisicly simple value-proposition to users, 24 friend tiles
and "Select All", Top Friends held the rank of <a href="http://www.insidefacebook.com/2007/06/13/10-facebook-apps-with-1-million-users/" target="_blank">#1 application on Facebook</a>. Following
Top Friends was iLike, a major initial success, with Fortune Cookie pulling in third
place. Further down the list were a couple of familiar applications: Free Gifts, created
by <a id="aptureLink_I6DjuMXqUs" href="http://twitter.com/zachallia">Zach Allia</a>, a Northwestern student at the time and a regular on the #facebook channel
on Freenode; <a id="aptureLink_bD6TEXokD4" href="http://www.rockyou.com/">Rock You!</a>'s "X Me" application was likely one of the first acquisitions on the Facebook platform, after
being created by a student who joined the #facebook channel frantically
asking for help as his server was crumbling under the load of pure virality, and SuperPoke!
an application created by a then part-time Microsoft employee and two friends.</p>
<p><img src="http://agentdero.cachefly.net/unethicalblogger.com/images/superpoke.gif" align="right" alt="SuperPoke!'s Original Logo"/>
The first couple weeks of the Facebook platform were sheer insanity, determined to one-up our
competitors Rock You!, Slide acquired SuperPoke! and the three engineers that wrote it,
<a id="aptureLink_sd7BZ1tbfb" href="http://www.linkedin.com/pub/nik-gandhy/3/a10/48a">Nik</a>, <a id="aptureLink_Fyf7r6i5iF" href="http://twitter.com/wliu">Will</a> and <a id="aptureLink_SoRf26L7bj" href="http://twitter.com/jonathanhsu">Jonathan</a>. Slide was determined to own the market of "virtually do virtual things to your virtual friends
on Facebook". In short order the SuperPoke team moved down from Seattle to join the "Facebook Team"
in Slide's office at 2nd and Howard, Jon went to the metrics team (being a PhD and all) while
Nik and Will shared a desk and started learning Python to port SuperPoke! over to Slide's stack
to allow it to scale faster and better than could have been possible on the PHP/MySQL stack it
used at the time. Prior to joining Slide, the SuperPoke! application icon was some picture of a goat
Nik had plucked from the internets, by joining Slide they had access to <strong>real</strong>
designers, not goats from Google Image Search. Slide's most senior designer, <a id="aptureLink_iinYqBREKu" href="http://www.linkedin.com/in/johnniemanzari">Johnnie</a>, can
be credited with helping define the brand that would ultimately be synonymous with the Facebook
platform and Slide: the SuperPoke sheep. While SuperPoke! and X Me battled it out for 4th and 5th place
in the application rankings, journalists started writing articles discussing the Facebook platform,
in both positive and negative light, without fail mentiong the absurdity of "throwing sheep" at
your friends. I always got the impression that <a id="aptureLink_uXBP518EqL" href="http://www.youtube.com/watch?v=_cEySyEnxvU">Mark Zuckerberg</a> would have considered the
Facebook platform successful when IBM ported Lotus Notes to it, being a "utility fetishist", I
can only imagine how "<em>delighted</em>" he must have been with the top applications on the platform
being the likes of Top Friends, Fortune Cookie, Horoscopes, Graffiti, X Me and SuperPoke!.</p>
<p>After the SP guys had joined Slide, Facebook hosted a mid-morning event at their Palo Alto office
to help kickstart some developer relations and have top application developers do some
lightning-round style presentations. The meeting starting at 9, it was only logical that
Nik, Will, Max and I meet at the Slide offices at 8:45; we piled into Max's BMW M3 (a gorgeous car,
I highly recommend it) and sped southwards from San Francisco on the 101. Despite driving between 90-100mph
through rush-hour traffic towards Palo Alto, we arrived fashionably late; walking in
during a presentation, <a id="aptureLink_4kk0ZJGGZc" href="http://www.crunchbase.com/person/dave-mcclure">Dave McClure</a> announced to the whole room "Slide has arrived."</p>
<p>Roll around in an M3 enough, have people announce your arrival enough and you too will feel like
a Web 2.0 rockstar. Being the "Top Friends guy", I certainly had a bit of an ego going, I still kind
of do, but I'm far more modest now about being a complete badass.</p>
<p>The summer of 2007 was mostly a blur, the majority of my "workdays" ended up being 14-16 hours usually
ending with <a id="aptureLink_QuPobFrnT3" href="http://www.linkedin.com/in/geoffgoss">Geoff</a>, <a id="aptureLink_4S7OQ9EQQy" href="http://www.flickr.com/photos/aarongustafson/6715261/">Sergio</a>, <a id="aptureLink_qcZXJQlIZn" href="http://twitter.com/kaseyhatefuture">Kasey</a> and I drinking into the wee hours of the morning, pushing code and
smoking on the fire escape (building management didn't really care for that part). The night before
the iPhone launched, a bunch of Sliders had arranged to wait in line in shifts at Apple's Market St store
(we were third in line). Given my schedule at the time, I worked most of the night and then manned
the 4-7 a.m. shift in line. I didn't even want an iPhone but Tony, the product manager we hired
for Top Friends, and I hung out on the sidewalk, smoked fancy cigars and watched the streets get
cleaned. My (now) fianceé was still in Texas finishing up with school, so I had nothing to
do but hang out, drink, smoke, write code, push the site and sleep every now and again. My
apartment, right in the middle of the colorful Tenderloin district, only served as a place to
shower and crash. For the duration of my lease, I didn't own any dishes and rarely had
anything in the fridge other than left-over pizza and Cokes.</p>
<p>By the latter part of 2007 we hired <a id="aptureLink_bmroAQB3XS" href="http://www.crunchbase.com/person/keith-rabois">Keith Rabois</a> to be the VP of Business Development, presumably to help us ink
deals with big important companies about big important things (with big important sacks of money).
Initially, I hadn't a clue what the hell Keith did, other than walk around in his shiney silk
shirts talking on his fancy iPhone, loud enough to hear across the office. The layout of Slide's
office was such that on one end was the open floorplan engineering "pit" and on the other end,
separated by a ping pong table and a copy machine were the "non-engineers". The ping pong
table was usually as far as I went. At some point, I don't remember exactly when, I started
consulting with Keith on product related matters. He had this chair by his desk, so I would
stroll over, plop down and gab for longer than he probably had time for about subjects ranging
from the latest Facebook gossip to long-term strategy; Keith's involvement with
Top Friends would only increase from then moving forward.</p>
<p>By the beginning of 2008, the Facebook platform wasn't fun anymore. Too many emails contained
the words "policy" and "violation" and often dastardly combinations of the two. At the same time,
Slide had upped its commitment to Top Friends hiring <a id="aptureLink_O25FBRJ00X" href="http://jasonrubenstein.blogspot.com/">Jason</a>, who I had known for some time
from the #facebook IRC channel, his compatriot <a id="aptureLink_W8EZvJEfz0" href="http://twitter.com/pjthiel">Paul</a>, and assigning Geoff, a senior QA engineer
who had put up with my shit on the client team since I joined the company months earlier.
I was promoted to Engineering Lead and shortly thereafter to Engineering Manager. My role had changed dramatically, no longer
simply just a monkey coding like there was no tomorrow, I now had people I had to be accountable
to, all the miserable hacks I had thrown into Top Friends in the previous 8 months I had to
sheepishly explain to Jason and Paul, mentioning from time to time how I could
do it better given the time.</p>
<p>Jason and Paul being hired and assigned to my team was likely the luckiest thing that ever happened to me at Slide,
overnight I went from a hard-working "army of one" to part of a team of four hard-working
bone crushers with an incredible drive to succeed. In a few short months we had shipped an "Awards"
feature, built out a "Top Friends Profile" and started pushing our way back to the top.</p>
<p>In June, a reporter for CNet <a href="http://news.cnet.com/8301-10784_3-9977762-7.html" target="_blank">reported on a hole in the Top Friends Profile</a> that allowed
a user to view information about other users they could not have otherwise seen. The
reporter used this an instrumental piece of a larger article bashing Facebook
on their privacy record and the openness of the Faceobok platform.
When Keith texted me that night, I rushed home and pushed a fix for the hole within the
hour, went to dinner by myself and had the worst Pad Thai I've ever eaten, watching
the exchange of emails between Slide's and Facebook's executive team on my Blackberry.</p>
<p>Top Friends had tens of millions of users and with the flick of a switch, <a href="http://www.techcrunch.com/2008/06/26/did-facebook-shut-down-slides-top-friends-how-very-myspace-of-them/" target="_blank">Facebook took Top Friends offline</a>.</p>
<p><strong>Continue on to <a href="http://unethicalblogger.com/posts/2009/10/my_journey_slide_part_3">part 3</a> and <a href="http://unethicalblogger.com/posts/2009/10/end_journey">the end</a></strong></p>
http://unethicalblogger.com/posts/2009/10/my_journey_slide_part_2#commentsMiscellaneousOpinionSlideWed, 21 Oct 2009 05:47:48 +0000R. Tyler Croy233 at http://unethicalblogger.comMy journey at Slide (part 1)
http://unethicalblogger.com/posts/2009/10/my_journey_slide_part_1
<p>As some of you may, or may not know, this friday October 23rd will be my final
day as a <a id="aptureLink_0RXt2swE8I" href="http://en.wikipedia.org/wiki/Slide">Slide</a> employee. With my journey at Slide nearing its completion, I wanted
to document some of how I've gotten here and where I've started, if for nobody other
than myself.</p>
<p>Officially I started at Slide April 2nd, 2007, though my journey to Slide started
far earlier. At the end of my fourth semester at Texas A&M my then girlfriend, now fianceé
and I decided we were through with <a id="aptureLink_aHZznBg58s" href="http://maps.google.com/maps?om=0&iwloc=addr&f=q&ll=30.627977%2C-96.3344068&hl=en&z=13&ie=UTF8">College Station</a> and to move to <a id="aptureLink_Gd4ZlvpCcA" href="http://maps.google.com/maps?om=0&iwloc=addr&f=q&ll=29.4241219%2C-98.4936282&hl=en&z=13&ie=UTF8">San Antonio</a>; most Texans would consider this a lateral move at best.
I had every intention of resuming my studies at UTSA following a brief stint at San Antonio College clearing up pre-requisites with a slightly lower price tag. By
the end of fall semester it had become clear that I wasn't cut out for college, I
stopped attending and focused full time on software. At the time most of my experience
and contacts were through the Mac development community, primarily via IRC on the <a id="aptureLink_GzxVgqeuw0" href="http://freenode.net/">Freenode</a>
network and developer mailing lists for various open source projects. Through
my involvement in the <a id="aptureLink_53d9mUyyvO" href="http://en.wikipedia.org/wiki/Bonjour%20%28software%29">Bonjour</a> mailing lists and work with the API, I had at one
point impressed Bonjour's original inventor <a id="aptureLink_cCGllfHxRe" href="http://en.wikipedia.org/wiki/Stuart%20Cheshire">Stuart Cheshire</a> enough to land
an interview at Apple for the Core OS group, working on Bonjour.</p>
<p>Sitting on the Continental flight out to San Jose, I practiced writing network services
using BSD sockets and pouring over as much C as I could possibly manage, all told I
likely wrote around 3 multicast service/client pairs on that flight. What I wasn't prepared for
was the "computer science" nature of the interview; I bombed it with my rudimentary algorithms
knowledge and lack of experience working with C on a day to day basis. Fortunately, my last
interviewer of the day was <a id="aptureLink_mJ9ZfrKUGS" href="http://www.linkedin.com/in/drernie">Ernie Prabhakar</a> then a product or marketing manager for the Core OS
group; Ernie indulged me in a very interesting conversation about Apple's position in the open
source universe, product direction, etc. Despite bombing the technical portions of the interview,
I suppose Ernie saw enough enthusiasm in me to refer me to <a id="aptureLink_4vNXJclTkH" href="http://www.linkedin.com/in/davemorin">Dave Morin</a> at Facebook (the two worked
together at some point).</p>
<p>Those that know Dave Morin understand that the man wields a Jobsian reality distortion field,
even via email 1500 miles away in Texas I felt the power of the field and was drawn to Facebook.
While I was ultimately disappointed to not have landed my then-dream-job at Apple, I was incredibly excited to be flying back to Silicon Valley to interview at Facebook. When I mentioned to daver (<a id="aptureLink_OG4mmMoyfm" href="http://twitter.com/stuffonfire">Dave Young</a>) on IRC that I would be flying back out to
see the nice folks at Facebook in Palo Alto, he also arranged an interview at Slide the day after.</p>
<p>A number of factors likely lead to my failure to excite my interviewers at Facebook, not having a
Facebook account for one didn't help, I also think I uttered "fuck" under my breath once or twice while
sketching out problems on a whiteboard. Considering my interview was done mostly in "the game
room" due to a scheduling error, I didn't think I was being too unprofessional. As one could assume, my interview with Slide went substantially better, I accepted an offer to join Slide as
a junior software engineer working on their now defunct desktop application(s).</p>
<p>My start date was set for April 2nd, within three weeks I had terminated the lease on my apartment
in San Antonio, tossed, sold or otherwise gave away the majority of my belongings and furniture and packed my VW Jetta
to the brim and drove west. I didn't particularly have a plan other than "show up, get to work"
(I was 21, how young and foolish), so I crashed on daver's couch while I settled in and started
searching for an apartment.</p>
<p>My early days at Slide were all about getting up to speed on <a id="aptureLink_oG0xGN1j1F" href="http://en.wikipedia.org/wiki/Python%20%28programming%20language%29">Python</a> (Slide's language of choice) and
ActionScript 2 (Slide's only option for Flash at the time); I started helping with the Windows
client, mostly in the spagetti-driven Flash-based screensaver product. Towards the beginning of May,
<a id="aptureLink_8fiXI4SDgw" href="http://twitter.com/jerobi">Jeremiah</a> (then Director of Engineering, now CTO) and Bobby (another engineer) were working with
some preview APIs from what would ultimately become the Facebook platform. For whatever reason I
started working on trying to incorporate some data from Facebook into our desktop client (optimal
synergy, etc) and became the third engineer working at Slide on the Facebook platform in its
infancy. As May came to completion, we (Slide) were invited to "F8" to unveil some of the
applications for their new platform we had built.</p>
<p>Donning my trusty brown cordoruy sport jacket, dark blue Slide t-shirt, I helped man Slide's booth
presenting some of our apps: SlideShows and YouTube Skins (both products turned out to be utter
failures). As the business/presentation portion part of F8 wound down, I grabbed a 19" monitor
and told Max and Jeremiah that I wanted to stay for the hackathon but didn't have any idea what to
hack on (being a desktop developer and all). Max leaned in and muttered "Top Friends" on his way
out, leaving me to set up shop with the only dual screen setup in the hackathon, at a lonely table by myself (I hadn't
figured out how to socialize at that point). Coming from a desktop background, I hadn't a clue
what I was doing, I could barely figure out how to get pages working on Slide's infrastructure, let
alone all this FBML, FQL and JavaScript malarkey.</p>
<p>Fortunately in the days following the hackathon, I was able to enlist the help of <a id="aptureLink_hhvhv9i8ar" href="http://www.flickr.com/photos/aarongustafson/6715261/">Sergio</a>, the best web front-end engineer
Slide had to offer to help me create a grid of drag and droppable images along with
some other pieces of front-end to make the application palatable. All said and done, if I remember correctly, Top Eight launched less than a
week after the platform did, my first "big" project at Slide. Originally I couldn't get database
resources for the app, so I stashed the friends list inside of the profile FBML and then would
subsequently retrieve <strong>back</strong> from Facebook when I needed it, using regular expressions (had help
with that too) to pull the list of Facebook user IDs out; that hacked up solution lasted for all
of maybe 30 minutes on live as soon as everyone saw how god-awful slow it was.</p>
<p>Day three of Top Eight, I learned what "viral meant". My parents had neglected to pay their phone
bill, taking my "family plan" number out with it, meaning I couldn't receive the frantic calls
from Jeremiah as I slept-in that morning. Turns out by giving the Top Eight a callback URL with
Facebook that hit "www.slide.com" was proving impossible to load balance, resulting in a couple
hours of site issues for the rest of Slide, as Top Eight skyrocketed hundreds of thousands of users in a single day.
I awoke that morning to pounding on Dave's door (I was still on their couch), opening it I
saw Carey (another desktop developer at Slide) who said "your phone's off." Not my preferred
way to wake up, but it sufficed. I sheepishly called Jeremiah on Dave's house phone.</p>
<p>Jeremiah was <strong>pissed</strong>. Not "who ate the rest of my hummus"
pissed, righteously pissed, at me. Here I was, living on a friend's (who I met on the internets)
couch, without a proper mailing address trying to figure out how this startup thing worked, and
Jeremiah was furious with me. If there were such a thing as an "ideal time" for an earthquake, I
would have gladly accepted that as an alternative.</p>
<p>Once the smoke cleared and tempers cooled, we looked at some of the installation and growth numbers of Top Eight
during the previous 6 hours; I had found myself a new job at Slide. From that day forth, I was
"the Top Friends guy."</p>
<p><strong>Continue on to <a href="http://unethicalblogger.com/posts/2009/10/my_journey_slide_part_2">part 2</a>, <a href="http://unethicalblogger.com/posts/2009/10/my_journey_slide_part_3">part 3</a> and <a href="http://unethicalblogger.com/posts/2009/10/end_journey">the end</a></strong></p>
http://unethicalblogger.com/posts/2009/10/my_journey_slide_part_1#commentsMiscellaneousOpinionSlideTue, 20 Oct 2009 05:40:53 +0000R. Tyler Croy232 at http://unethicalblogger.comIronWatin; mind the gap
http://unethicalblogger.com/posts/2009/10/ironwatin_mind_gap
<p>Last week <a id="aptureLink_UJbJnwQvgk" href="http://twitter.com/admc">@admc,</a> despite being a big proponent of <a id="aptureLink_mV2RK9dLaN" href="http://twitter.com/windmillproject">Windmill</a>, needed to use WatiN for a change. <a id="aptureLink_sf9oXnu3uF" href="http://watin.sourceforge.net/">WatiN</a> has the distinct capability of being able to work with Internet Explorer's HTTPS support as well as frames, a requirement for the task at hand. As adorable as it was to watch <a id="aptureLink_zccUSsrvlx" href="http://twitter.com/admc">@admc,</a> a child of the dynamic language revolution, struggle with writing in C# with Visual Studio and the daunting "Windows development stack," the prospect of a language shift at Slide towards C# on Windows is almost laughable. Since <a id="aptureLink_oR2hGjfmlx" href="http://www.crunchbase.com/company/slide">Slide</a> is a Python shop, IronPython became the obvious choice.</p>
<p>Out of an hour or so of "extreme programming" which mostly entailed Adam watching as I wrote IronPython in his Windows VM, <a href="http://github.com/rtyler/IronWatin"><strong>IronWatin</strong></a> was born. IronWatin itself is a <strong>very</strong> simple test runner that hooks into Python's <a id="aptureLink_SpWkHjDZgq" href="http://en.wikipedia.org/wiki/PyUnit">"unittest"</a> for creating integration tests with WatiN in a familiar environment.</p>
<p>I intended IronWatin to be as easy as possible for "native Python" developers, by abstracting out updates to <code>sys.path</code> to include the Python standard lib (adds the standard locations for Python 2.5/2.6 on Windows) as well as adding <code>WatiN.Core.dll</code> via <code>clr.AddReference()</code> so developers can simply <code>import IronWatin; import WatiN.Core</code> and they're ready to start writing integration tests. When using IronWatin, you create test classes that subclass from <code>IronWatin.BrowserTest</code> which takes care of setting up a browser (WatiN.Core.IE/WatiN.Core.FireFox) instance to a specified URL, this leaves your <code>runTest()</code> method to actually execute the core of your test case.</p>
<p>Another "feature"/design choice with IronWatin, was to implement a <code>main()</code> method specifically for running the tests on a per-file basis (similar to <code>unittest.main()</code>). This main method allows for passing in an <code>optparse.OptionParser</code> instance to add arguments to the script such as "--server" which are passed into your test classes themselves and exposed as "self.server" (for example). Which leaves you with a fairly straight-forward framework with which to start writing tests for the browser itself:</p>
<div class="geshifilter"><pre class="geshifilter-python"><ol><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;">#!/usr/bin/env ipy</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;"># The import of IronWatin will add a reference to WatiN.Core.dll</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;"># and update `sys.path` to include C:\Python25\Lib and C:\Python26\Lib</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;"># so you can import from the Python standard library</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">import</span> IronWatin</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">import</span> WatiN.<span style="color: black;">Core</span> as Watin</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">import</span> <span style="color: #dc143c;">optparse</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">class</span> OptionTest<span style="color: black;">(</span>IronWatin.<span style="color: black;">BrowserTest</span><span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> url = <span style="color: #483d8b;">'http://www.github.com'</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">def</span> runTest<span style="color: black;">(</span><span style="color: #008000;">self</span><span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #808080; font-style: italic;"># Run some Watin commands</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">assert</span> <span style="color: #008000;">self</span>.<span style="color: black;">testval</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">if</span> __name__ == <span style="color: #483d8b;">'__main__'</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> opts = <span style="color: #dc143c;">optparse</span>.<span style="color: black;">OptionParser</span><span style="color: black;">(</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> opts.<span style="color: black;">add_option</span><span style="color: black;">(</span><span style="color: #483d8b;">'--testval'</span>, dest=<span style="color: #483d8b;">'testval'</span>, <span style="color: #008000;">help</span>=<span style="color: #483d8b;">'Specify a value'</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> IronWatin.<span style="color: black;">main</span><span style="color: black;">(</span>options=opts<span style="color: black;">)</span></div></li></ol></pre></div>
<p>Thanks to IronPython, we can make use of our developers' and QA engineers' Python knowledge to get the up and running with writing integration tests using WatiN rapidly instead of trying to overcome the hump of teaching/training with a new language.</p>
<p><strong>Deployment Notes:</strong> We're using IronPython 2.6rc1 and building WatiN from trunk in order to take advantage of some recent advances in their Firefox/frame support. We've not tested IronWatin, or WatiN at all for that matter, anywhere other than Windows XP.</p>
http://unethicalblogger.com/posts/2009/10/ironwatin_mind_gap#commentsHudsonMonoSlideSoftware DevelopmentTue, 13 Oct 2009 21:57:49 +0000R. Tyler Croy231 at http://unethicalblogger.comInvestment Strategy for Developers
http://unethicalblogger.com/posts/2009/08/investment_strategy_developers
<p>It seems every time <a href="http://twitter.com/jasonrubenstein">@jasonrubenstein</a>,
<a href="http://twitter.com/ggoss3">@ggoss3</a>, <a href="http://twitter.com/cablelounger">@cablelounger</a>
and I sit down to have lunch together, we invariably sway back and forth between
generic venting about "<a href="http://www.slide.com">work stuff</a>" and best practices for
doing aforementioned "work stuff" better. The topic of "reusable code" came up
over Mac 'n Cheese and beers this afternoon, and I felt it warranted "wider
distribution" so to speak (yet-another-lame-Slide-inside-joke).</p>
<p>We, <a href="http://www.slide.com">Slide</a>, are approaching our fourth year in existence as a
startup which means all sorts of interesting things from an investor standpoint,
employees options are starting to become fully-vested and other mundane and boring
financial terms. Being an engineer, I don't care too much about the stocks and such,
but rather about development; four years is a <strong>lot</strong> from a code-investment
standpoint (my bias towards code instead of financial planning will surely bite me
eventually). Projects can experience bitrot, bloating (read: Vista'ing) and a myriad
other illnesses endemic to software that's starting to grow long in the tooth.</p>
<p>At Slide, we have a number of projects on slightly different trajectories and timelines,
meaning we have an intriguing cross-section of development histories representing
themselves. We are no doubt experiencing a similar phenomenon to Facebook, MySpace, Yelp and a number of other "startups" who match this same age group of 4-7
years. Just like our bretheren in the startup community, we have portions of code
that fit all the major possible categories:</p>
<ul>
<li>That which was written extremely fast, without an afterthought to what would happen when it serve tens of millions of users</li>
<li>That which was written slowly, trying to cater to every possible variation, ultimately to go over-budget and over-schedule.</li>
<li>That which has been rewritten. And rewritten. And rewritten.</li>
<li>Then the <strong>exceptionally</strong> rare, that which has been written in such a fashion that it has been elegantly extended to support more than it was originally conceived to support.</li>
</ul>
<p>In all four cases, "we" (whereas "<em>we</em>" refers to an engineering department) have
invested differently in our code portfolio depending on a number of factors and
information given at the time. For example, it's been a year since Component X was
written. Component X is currently used by every single product The Company owns, but
over the past year it's been refactored and partially rewritten each time a new
product starts to "use" Component X. In its current state, Component X's code reads
more like an embarrasing submission to <a href="http://thedailywtf.com">The Daily WTF</a> with
its hodge-podge of code, passed from team to team, developer to developer, like some
expensive game of "<a href="http://en.wikipedia.org/wiki/Chinese_whispers">Telephone</a>" for
software engineers. After the fact, it's difficult and not altogether helpful to
try to lay blame with the mighty sword of hindsight, but it is feasible to identify the
<em>reasons</em> for the N number of developer hours lost fiddling, extending, and refactoring
Component X.</p>
<ul>
<li>Was the developer responsible for implementing Component X originally aware of the potentially far reaching scope of their work?</li>
<li>Was the developer given an adequate time frame to implement a proper solution, or "this should have shipped yesterday!"</li>
<li>Did somebody pass the project off to an intern or somebody who was on their way out the door?</li>
<li>Were other developers in similar realms of responsibility asked questions or for their opinions?</li>
<li>Is/was the culture proliferated by Engineering Leads and Managers encouraging of best practices that lead to extensible code?</li>
</ul>
<p>I've found, watching Slide Engineering culture evolve, that the majority of libraries
or components that go through multiple time/resource-expensive iterations tend to have
experienced shortcomings in one of the five sections above. More often than not,
a developer was given the task to implement Some Thing. Simple enough, Some Thing
is developed with the specific use-case in mind, and the developer moves on with their
life. Three months later however, somebody else asks another developer, to add Some Thing
to <strong>another</strong> product.</p>
<blockquote>
<p>"Product X has Some Thing, and it works great for them, let's incorporate Some Thing into Product Y by the end of the week."</p>
</blockquote>
<p>Invariably this leads to heavy developer drinking. And then perhaps some copy-paste,
with a dash of re-jiggering, and quite possibly multiple forks of the same code. That
is, if Some Thing was not properly planned and designed in the first place.</p>
<p>Working as a developer on products that move at a fast pace, but will be around for
longer than three months is an exercise in <em>investment strategy</em> (i.e. managing
<a href="http://blogs.construx.com/blogs/stevemcc/archive/2007/11/01/technical-debt-2.aspx">technical debt</a>).
What makes great Engineering Managers great is their ability to determine when and where to invest
the time to do things right, and where to write some Perl-style write-only code (<em>zing!</em>).
What makes a startup environment a more difficult one to work on your "code portfolio"
is that you don't usually know what may or may not be a success, and in a lot of cases
getting your product out there <strong>now</strong> is of paramount importance. Unfortunately
there isn't any simple guideline or silver bullet, and there is <strong>no bailout</strong>, if
you invest your time poorly up front, there will be nobody to save you further down
the line when you're staring an resource-devouring refactor in its ugly face.</p>
<p>Where do you invest the time in any given project? What will happen if you shave a
few days by deciding not to write any tests, or documentation. Will it cost you
a week further down the road if you take shortcuts now?</p>
<p>I wish I knew.
<!--break--></p>
http://unethicalblogger.com/posts/2009/08/investment_strategy_developers#commentsOpinionSlideSoftware DevelopmentTue, 11 Aug 2009 06:34:19 +0000R. Tyler Croy222 at http://unethicalblogger.comWriting for Stability (or: I hate writing tests)
http://unethicalblogger.com/posts/2009/07/writing_stability_or_i_hate_writing_tests
<p>Since moving to the infrastructure team at <a href="http://slideinc.github.com">Slide</a>
I've found the rate at which my software gets deployed has plummeted, while the
quantity of the code that I am deploying to the live site has sky-rocketed. When
on an applications-team within Slide, code is typically pushed in small incrememnts a few
days a week, if not daily. This allows for really exciting compact milestones that
make more fine-grained analysis achievable, post-push for product management and metrics
purposes. On the <em>infrastructure</em> team however, the requirements are <em>wholly</em> different,
the "fail-fast, ship-now" mentality that prevails when doing user-facing web application
development just <em>does not</em> work in infrastructure. The most important aspects of building
out infrastructure components are stability and usability, our "customers" are the rest of
engineering, and that has a definite effect on your workflow.</p>
<h2>Code Review</h2>
<p>One of the things that <a href="http://twitter.com/jasonrubenstein">@jasonrubenstein</a> and I always
did when we worked together, was occasional code review. In the majority of cases, our "code review" sessions
were more or less <a href="http://en.wikipedia.org/wiki/Rubber_duck_debugging">rubber duck debugging</a>, but
occasionally it would escalate into more complex discussions about the "right way" to do something.
When you're writing infrastructure software for services that are handling <a href="http://www.slide.com/static/about_press">tens of millions of users</a>
the notion of "code review" goes from being <em>optional</em> to being absolutely <em>required</em>. Discussions
are had on the correctness or performance characteristics of database indexes, the necessity of some
objects instantiating default values of attributes or having them lazily load, or debating garbage
collection of objects while meticulously watching <a href="https://twitter.com/agentdero/status/2442677113">memory consumption</a>.</p>
<p>For one of my most recent projects, I was working on <a href="http://github.com/slideinc/PyECC/tree/master">something</a> in C,
a rarity at Slide since we work with managed code in <a href="http://python.org">Python</a> the majority of the
time. As the project neared completion, I counted roughly two or three <em>hours</em> of code review time
dedicated by our Chief Architect. The attention to detail paid to this code was extremely high,
as the service was going to be handling millions of requests from other levels of
the Slide infrastructure, before getting cycled or restarted.</p>
<p>A particularly frustrating aspect of code review by your peers is that a second set of eyes not only
will find problems with your code, but will likely mean refactoring or bug fixes, more work.
In my case, whenever a bug or stability issue was discovered, a test needed to be
written for it to make sure the bug did not present itself again, the workload would be larger than
if I had just fixed the bug and moved on with my life.</p>
<h2>Testing, oh the testing</h2>
<p>If you expect to write an API, have it stablize, and <em>then</em> be used, you <em>must</em> write test cases for
it. I'm not a <a href="http://en.wikipedia.org/wiki/Test-driven_development">TDD</a> "nut", I actually <em>hate</em>
writing test cases, I absolutely abhor it. Writing test cases is <em>responsible</em> and the <em>adult</em>
thing to do. In my experience, it can also be <em>tedious</em> and usually comes as a result of
finding flaws in my own software. The majority of tests that I find myself writing are admissions of
defeat, admitting that I don't crap roses and by george, my code isn't perfect either.</p>
<p>On the flipside however, <em>I hate debugging</em> even more. Stepping through a call stack is on par
with waterboarding in my book, torture. Which means I'm more than willing to tolerate writing tests
so long as it means I can be certain I will be cutting down on the time spent being tortured with
either <a href="http://docs.python.org/library/pdb.html">pdb</a> or <a href="http://www.gnu.org/software/gdb/">gdb</a>.
In almost every situation where I've written tests properly, like the <em>responsible</em> developer that
I am, I find them saving me at some point. It might be getting late, or I'm just feeling a little
cavalier, but tests failing almost always indicates that I've screwed something up I shouldn't have.</p>
<p>Additionally, now that the majority of my projects are infrastructure-level projects, the tests I write serve
a second "undocumented" purpose, they provide ready-made examples for other developers on <em>how to
use my code</em>. Bonus!</p>
<p>The more and more code I write, the more amazed I am at the pushback against testing in general,
there exists decent libraries for <em>every</em> language imaginable (well, perhaps BrainfuckUnit doesn't
exist), and its <em>sole</em> purpose (in my opinion) is to save develpoment time, particularly when coupled
with a <a href="http://www.hudson-ci.org">good continuous integration server</a>. Further to that effect, if you're
building services for <em>other developers</em> to use, and you're not writing tests for it, you're not only
wasting your time and your employer's money, but the time of your users as well (read: stop being a jerk).</p>
<p>Sure there are a lot of articles/books/etc about writing stable code, but in my opinion, solid code
review and testing will stablize your code far more than any design pattern ever will.
<!--break--></p>
http://unethicalblogger.com/posts/2009/07/writing_stability_or_i_hate_writing_tests#commentsOpinionSlideSoftware DevelopmentWed, 29 Jul 2009 07:30:08 +0000R. Tyler Croy221 at http://unethicalblogger.comSlide Open Source
http://unethicalblogger.com/posts/2009/04/slide_open_source
<p>It's not been a secret that I'm a big fan of open source software, I would likely not be where I am today had I not started with projects like <a href="http://www.openbsd.org">OpenBSD</a> or <a href="http://www.freebsd.org">FreeBSD</a> way back when. While my passion for open source software and the "bazaar" method of developing software might not be shared by everybody at <a href="http://slide.com">Slide, Inc</a>, everybody can certainly get on board with the value of incorporating open source into our stack, which is almost entirely comprised of <a href="http://www.python.org">Python</a> (and an assortment of other web technologies).</p>
<p>Along those lines, there's been some amount of discussion about what we can or should open source from what we've developed at <a href="http://www.slide.com">Slide</a>, but we've not really <em>pushed</em> anything out into the ether as of yet. Today however, I think we finally put our foot in the door as far as contributing back to the open source community as a whole, <strong>we're now on <a href="http://www.github.com">GitHub</a> as "<a href="http://github.com/slideinc/">slideinc</a>"</strong>, yay! (coincidentally we have a <a href="http://twitter.com/slideinc">slideinc</a> twitter account too)</p>
<p>Currently the only project that's come directly out of Slide, and shared via the <strong>slideinc</strong> GitHub account is <a href="http://github.com/slideinc/pyve/tree">PyVE</a>, a Python Virtual Earth client that I hacked together recently to tinker with some Geocoding (released under a 3-clause BSD license). In the (hopefully) near future we'll continue to open source some other components we've either created or extended internally.</p>
<p>If you're not a GitHub user, you should definitely check GitHub out, it's a pretty impressive site. If you are a GitHub user, or a Python developer, you should "follow" the <a href="http://github.com/slideinc/">slideinc</a> user on GitHub to catch the cool stuff that we may or may not ever actually release ;)</p>
http://unethicalblogger.com/posts/2009/04/slide_open_source#commentsSlideSoftware DevelopmentWed, 08 Apr 2009 06:56:20 +0000R. Tyler Croy216 at http://unethicalblogger.comDo not fear continuous deployment
http://unethicalblogger.com/posts/2009/03/do_not_fear_continuous_deployment
<p>One of the nice things about living in Silicon Valley is that you have relatively easy access to a number of the developers you may work with through open source projects, mailing lists, IRC, etc. Today <a href="http://weblogs.java.net/blog/kohsuke/" target="_blank">Kohsuke Kawaguchi</a> of Sun Microsystems, the founder of the <a href="http://hudson.dev.java.net" target="_blank">Hudson project</a>, stopped by the <a href="http://www.slide.com" target="_blank">Slide</a> offices to discuss Hudson and the "cloud", continuous deployment and our workflow with Hudson here at Slide. Continuous deployment being the most interesting topic for me, and the most relevant in terms of the importance of Hudson in our current infrastructure.</p>
<p>Since reading Timothy Fitz's <a href='http://timothyfitz.wordpress.com/2009/02/10/continuous-deployment-at-imvu-doing-the-impossible-fifty-times-a-day/' target="_blank">post on the setup for "continuous deployment"</a> at <a href="http://www.imvu.com/" target="_blank">IMVU</a>, I've become obsessed to a certain degree with pushing Slide in that direction as an engineering organization. Currently we push a number of times a day as necessary, and it's almost as if we have manual-continuous-deployment as it is right now, there's just a lot of room for optimizations and automation to cut down on the tedium and allow for more beer drinking. </p>
<blockquote><p>@agentdero continuous deployment = when build is green, autoship? sounds terrifying...</p></blockquote>
<p> (<a href="http://twitter.com/tlipcon" target="_blank">@tlipcon</a>)</p>
<p>As a concept, continuous deployment can be quite scary "wait, some robot is going to deploy code to my production site, wha!" It's important to remember that the concept of continuous deployment doesn't necessarily mean that no QA is involved in the release process, it is however ideal to have enough good test cases such that you can do a fully automated unit/integration/system test run. The biggest difficultly with the entire concept of "continuous deployment" however is not writing tests or actually implementing a system to deploy, it forces you to <strong>understand your releases and production environment</strong>; it's about eliminating the guess work from your process and reducing the amount of human error (or potential for human error) involved in deployments. </p>
<p>In my opinion, continuous deployment isn't about making a hard switch, firing your QA and writing boat-loads of tests to ensure that you can push the production site straight from "trunk" as much as humanly possible. Continuous deployment is far more about solidifying your understanding of your entire stack, evolving your code base to where it is both more testable and better covered by your tests, then putting your money where your mouth is and relying on those tests. If your codebase moves rapidly, unit/integration/system tests are only going to be up to date and valuable if you actually rely on them. If breaking a single unit test pre-deployment becomes a Big Deal™, then the developer responsible for the code being deployed will make sure that: (a) the test is valid and up to date and (b) the code that the test is covering does not contain any actual regressions.</p>
<p>Take the typical repository layout for most companies which is, as far as I've seen, made up of a volatile trunk, stable release branch and then a number of project branches. In an engineering department QA would be responsible for ensuring that projects are properly vetted before merging from project branches (also called "topic branches" in the Git community) into the more volatile trunk branch. Once the CI server (i.e. Hudson) picks up on changes in trunk, the testing process would begin at that particular revision. Provided the test suites passed with flying colors Hudson would start to kick up the process to do a slow/sampled deploy as <a href='http://timothyfitz.wordpress.com/' target="_blank">Timothy</a> describes in <a href='http://timothyfitz.wordpress.com/2009/02/10/continuous-deployment-at-imvu-doing-the-impossible-fifty-times-a-day/' target="_blank">his post</a>. If the tests failed however, alarms would start beeping, sirens would wail and there would be much gnashing of teeth, somebody has now broken trunk and is blocking any other deployments coming down the pipe. In this "disaster scenario" the QA involved in the process would be thoroughly shamed (obviously) but then given the choice to block future pushes while the developer(s) create a fix or revert their changes out of trunk and take them back to a project branch to correct the deficiencies. This attention to detail will have an larger benefit in that developers won't become <a href="http://www.squarefree.com/2009/02/19/continuous-integration-at-mozilla/" target="_blank">numb to test failures</a> to where they're no longer important.</p>
<p>What good is writing tests if there aren't <em>have real-consequences for them failing?</em> Releases shouldn't be a scary time of the day/week/month, you should certainly be nervous (keeps you sharp), but if you <strong>fear</strong> releases then it is probably an error in your release process that allows for too much uncertainty: inadequate test coverage, insufficient blackbox testing, poor release practices, etc. Continuous deployment might not be the magic solution to your woe of shipping software but the practice of moving <em>towards</em> continuous deployment will greatly improve your release process whether or not you ever actually make the switch over to a fairly automated deployment process as the engineers at IMVU have.</p>
<p>How confident are you in your test coverage?</p>
http://unethicalblogger.com/posts/2009/03/do_not_fear_continuous_deployment#commentsHudsonSlideSoftware DevelopmentFri, 13 Mar 2009 08:13:29 +0000R. Tyler Croy214 at http://unethicalblogger.comGit Protip: Split it in half, understanding the anatomy of a bug (git bisect)
http://unethicalblogger.com/posts/2009/03/git_protip_split_it_half_understanding_anatomy_bug_git_bisect
<p>I've been sending "Protip" emails about Git to the rest of engineering here at <a href="http://slide.com">Slide</a> for a while now, using the "Protips" as a means of introducing more interesting and complex features Git offers.</p>
<hr/>
There are those among us who can look at a reproduction case for a bug and <strong><em>just know</em></strong> what the bug is. For the rest of us mere mortals, finding out what change or set of changes actually introduced a bug is extremely useful for figuring out why a particular bug exists. This is even more true for the more elusive bugs or the cases where code "looks" correct and you're stumped as to why the bug exists now, when it didn't yesterday/last week/last month. The options in most classical version control systems you have available to you are to sift through diffs or wade through log message after log message trying to spot the particular change that introduced the regression you're now tasked with resolving. <br/><br />
Fortunately (of course) Git offers a handy feature to assist you in tracking down regressions as they're introduced, <strong>git bisect</strong>. Take the following scenario:<br />
<blockquote>Roger has been working on some lower level changes in a project branch lately. When he left work last night, he ran his unit tests (everything passed), committed his code and went home for the day. When he came in the next morning, per his typical routine, he synchronized his project branch with the master branch to ensure his code wasn't stomping on released changes. For some reason however, after synchronizing his branch, his unit tests started to fail indicating that a bug was introduced in one of the changes that was integrated into Roger's project branch. </p></blockquote>
<p>Before switching to Git, Roger might have spent an hour looking over changes trying to pinpoint what went wrong, but now Roger can use <strong>git bisect</strong> to figure out exactly where the issue is. Taking the commit hash from his last good commit, Roger can walk through changes and pinpoint the issue as follows:</p>
<pre>
## Format for use is: git bisect start [<bad> [<good>...]] [--] [<paths>...]
xdev4% git bisect start HEAD 324d2f2235c93769dd97680d80173388dc5c8253
Bisecting: 10 revisions left to test after this
[064443d3164112554600f6da39a36ffb639787d7] Changed the name of an a/b test.
xdev4%
</pre><p>This will start the bisect process, which is interactive, and start you halfway between the two revisions specified above (see the image below). Following the scenario above, Roger would then run his unit tests. Upon their success, he'd execute "git bisect good" which would move the tree halfway between that "good" revision and the "bad" revision. Roger will continue doing this until he lands on the commit that is responsible for the regression. Knowing this, Roger can either revert that change, or make a subsequent revision that corrects the regression introduced.<br />
<center><img src="http://agentdero.cachefly.net/unethicalblogger.com/images/git_bisect.png"/></center><br />
A sample of what this sort of transcript might look like is below:</p>
<pre>
xdev4% git bisect good
Bisecting: -1 revisions left to test after this
[bcf020a6c4ac7cc5df064c66b182b2500470000a] Merge branch 'cjssp' into master
xdev4% git bisect bad
bcf020a6c4ac7cc5df064c66b182b2500470000a is first bad commit
xdev4% git show bcf020a6c4ac7cc5df064c66b182b2500470000a
commit bcf020a6c4ac7cc5df064c66b182b2500470000a
Merge: 62153e2... 064443d...
Author: Chris <chris@foo>
Date: Tue Jan 27 12:57:45 2009 -0800
Merge branch 'cjssp' into master
xdev4% git bisect log
# bad: [7a5d4f3c90b022cb66fd8ea1635c5de6768882d7] Merge branch 'foo' into master
# good: [d1014fd52bebd3c56db37362548e588165b7f299] Merge branch 'bar'
git bisect start 'HEAD' 'd1014fd52bebd3c56db37362548e588165b7f299' '--' 'apps'
# good: [064443d3164112554600f6da39a36ffb639787d7] Changed the name of an a/b test. PLEASE PICK ME UP WITH NEXT PUSH. thx
git bisect good 064443d3164112554600f6da39a36ffb639787d7
# bad: [bcf020a6c4ac7cc5df064c66b182b2500470000a] Merge branch 'cjssp' into master
git bisect bad bcf020a6c4ac7cc5df064c66b182b2500470000a
xdev4% git bisect reset
xdev4%
</pre><p>Instead of spending an hour looking at changes, Roger was able to quickly walk a few revisions and run the unit tests he has to figure out which commit was the one causing trouble, and then get back to work squashing those bugs.</p>
<p>Roger is, like most developers, inherently lazy, and running through a series of revisions running unit tests sounds like "work" that doesn't need to be done. Fortunately for Roger, git-bisect(1) supports the subcommand "<strong>run</strong>" which goes hand in hand with unit tests or other tests. In the example above, let's pretend that Roger had a test case exhibiting the bug he was noticing. What he could actually do is let <strong>git bisect run</strong> automatically run a test script to run his unit tests to find the offending revision i.e.:</p>
<pre>
xdev4% git bisect start HEAD 324d2f2235c93769dd97680d80173388dc5c8253
Bisecting: 10 revisions left to test after this
[064443d3164112554600f6da39a36ffb639787d7] Changed the name of an a/b test.
xdev4% git bisect run ./mytest.sh
</pre><p>After executing the <strong>run</strong> command, git-bisect(1) will binary search the revisions between GOOD and BAD testing whether or not "mytest.sh" returns a zero (success) or non-zero (failure) return code until it finds the commit that causes the test to fail. The end result should be the exact commit the regression was introduced into the tree, after finding this Roger can either grab his rubber chicken and go slap his fellow developer around or fix the issue and get back to playing Nethack.<br/><br />
All in all git-bisect(1) is extraordinarily useful for pinning down bugs and diagnosing issues as they're introduced into the code base.<br/></p>
<p>For more specific usage of `git bisect` refer to it's man page here: <a href="http://www.kernel.org/pub/software/scm/git/docs/git-bisect.html" target="_top">git-bisect(1) man page</a></p>
<hr/>
<em>Did you know!</em> <a href="http://www.slide.com/static/jobs">Slide is hiring</a>! Looking for talented engineers to write some good Python and/or JavaScript, feel free to contact me at tyler[at]<a href="http://slide.com">slide</a></p>
http://unethicalblogger.com/posts/2009/03/git_protip_split_it_half_understanding_anatomy_bug_git_bisect#commentsGitSlideSoftware DevelopmentSat, 07 Mar 2009 06:07:13 +0000R. Tyler Croy212 at http://unethicalblogger.comHead in the clouds
http://unethicalblogger.com/posts/2009/03/head_clouds
<p>I've spent the entire day thinking about "cloud computing", which is quite a twist for me. Seeing <a href="http://itcloudconference.com/">"impressive" conferences</a> centered around "cloud computing" I've ridiculed the concept mercilessly, it has a phenomenally high buzzword/usefulness ratio, which makes it difficult to take seriously. It tends to have an air of idiocy attached to it of the same style that the re-invention of thin-clients did a few years back. That said, I think the concept is sound, and useful for a number of companies and uses (once distilled of the buzz).</p>
<p>Take <a href="http://slide.com">Slide</a> for example, we have a solid amount of hardware, hundreds of powerful machines constantly churning away on a number of tasks: serving web pages, providing back-end services, processing database requests, recording metrics, etc. If I start the work-week needing a new pool of machines either set up or allocated for a particular task, I can usually have hardware provisioned and live by the end of the week (depending on my Scotch offering to the Operations team, I can get it as early as the next day). If I can have the <em>real thing</em> I clearly have no need for cloud computing or virtualization.</p>
<p>That's what I thought, at least, until I started to think more about what would be required to get Slide closer to the lofty goal of <a href="http://timothyfitz.wordpress.com/2009/02/10/continuous-deployment-at-imvu-doing-the-impossible-fifty-times-a-day/">continuous deployment</a>. As I was involved in pushing for and setting up our <a href="http://hudson.dev.java.net">Hudson CI server</a>, I constantly check on the performance of the system and help make sure jobs are chugging along as they should be, I've become the defacto Hudson janitor.</p>
<p>Our current continuous integration setup involves one four-core machine running three separate instances of our server software as different users, processing jobs throughout the day. One "job" typically consists of a full restart of the server software (Python) and running literally <strong>every</strong> test case in the suite (we walk the entire tree aggregating tests). On average the completion of one job takes close to 15 minutes, and executes around 400+ test cases (and growing). Fortunately, and unfortunately, our Hudson machine is no longer able to service this capacity during development peak in the middle of the day, this is where the "cloud" comes in.</p>
<p>We have a few options at this point:
<ul>
<li>Setup another one or more machines</li>
<li>Rethink how we provision hardware for continuous integration</li>
</ul>
<p>The fundamental problem with provisioning resources for continuous integration, at least at Slide, is that the requirements are bursty at best. We typically queue a job for a particular branch when a developer executes a <span class="geshifilter"><code class="python geshifilter-python">git push</code></span> (via the Hudson API and a post-receive hook). From around 9 p.m. until 9 a.m. we don't need but maybe two actual "executors" inside Hudson to handle the workload the night-owl developers tend to place on Hudson, from 12 p.m. until 7 p.m. however our needs fluctuate rapidly between needing 4 executors, and 10 executors. To exacerbate things further, due to "natural traffic patterns" in how we work, mid-afternoon on Wednesday and Thursday require even more resources as teams are preparing releases and finishing up milestones.</p>
<p>The only two possible solutions to solve the problem are to: build a continuous integration farm with full knowledge capacity will remain unused for large amounts of time, <strong>or</strong> look into "cloud computing" with service provides like Amazon EC2 which will allow for Hudson slaves to be provisioned <strong>on demand</strong>. The maintainer of Hudson, <a href="http://weblogs.java.net/blog/kohsuke/">Kohsuke Kawaguchi</a> has already started work on "cloud support" for Hudson via the <a href="http://fisheye4.atlassian.com/browse/hudson/trunk/hudson/plugins/ec2">EC2 plugin</a> which makes this a real possibility. (Note: using EC2 for this at Slide was <a href="http://stuffonfire.com/">Dave's</a> idea, not mine :))</p>
<p>Using Amazon EC2 isn't the only way to solve this "bursty" problem however, we could just as easily solve the problem in house with provisioning of <a href="http://www.xen.org/">Xen</a> guests across a few machines. The downside of doing it yourself is amount of time between when you know you need more capacity and when you can actually add that capacity to your own little "cloud". Considering Amazon has an <a href="http://docs.amazonwebservices.com/AWSEC2/2006-10-01/DeveloperGuide/">API</a> for not only running instances but terminating them, it certainly provides a compelling reason to "outsource" the problem to Amazon's cloud.</p>
<p>I recommend following Kohsuke's development of the EC2 plugin for Hudson closely, as continuous integration and "the cloud" seem like a match made in heaven (alright, that pun was unnecessary, it sort of slipped out). At the end of the day the decision comes down to a very fundamental business decision: which is more cost effective, building my own farm of machines, or using somebody else's?</p>
<p>(<small><em>footnote:</em> I'll post a summary of how and what we eventually do to solve this problem</small>)</p>
http://unethicalblogger.com/posts/2009/03/head_clouds#commentsHudsonSlideSoftware DevelopmentFri, 06 Mar 2009 07:42:03 +0000R. Tyler Croy211 at http://unethicalblogger.comGit Protip: A picture is worth a thousand words (git tag)
http://unethicalblogger.com/posts/2009/01/git_protip_a_picture_worth_a_thousand_words_git_tag
<p>I've been sending weekly "Protip" emails about Git to the rest of engineering here at <a href="http://slide.com">Slide</a> for a while now, using the "Protips" as a means of introducing more interesting and complex features Git offers. Below is the fourth Protip written to date.</p>
<hr/>
<br/><br />
While the concept of "tagging" or "labeling" code is not a new, or original idea that was introduced with Git, our use of tags in a regular workflow does not predate the migration to Git however. At it's most basic level, a "tag" in any version control system is to take a "picture" of how the tree looks at a certain point in time such that it can be re-created later. This can be extremely helpful for both local and team development, take the following scenario for local development using tags:</p>
<div style="margin: 10px; padding: 7px; border: 1px solid #cecece;">
<p>Tim is extremely busy, most of his days working at an exciting, fast-paced start-up seem to fly by. With one particular project Tim is working on, a lot of code is changing at a very fast pace and the branch he's currently working in is stable one minute and destabilized the next. Tim has two basic options for leaving himself "bread-crumbs" to step back in time to a stable or an unstable state. The first, complicated option, is to mark his commit messages with something like "STABLE", etc so he can <span class="geshifilter"><code class="python geshifilter-python">git diff</code></span> or <span class="geshifilter"><code class="python geshifilter-python">git reset --hard</code></span> from the current HEAD to the last stable point of the branch. </p>
<p>
The second option is to make use of tags. Whenever Tim reaches a stable point in his turmultuous development, he can simply run:<br />
<span class="geshifilter"><code class="python geshifilter-python">git tag wip-protips_<span style="color: #66cc66;">`</span>date <span style="color: #483d8b;">"+%s"</span></code></span><br />
(or something similar, `date` added to ensure the tag is unique). If Tim finds himself too far down the wrong path, he can rollback his branch to the latest tag (<span class="geshifilter"><code class="python geshifilter-python">git reset --hard protiptag</code></span>), create a new stable branch based on that tag (<span class="geshifilter"><code class="python geshifilter-python">git checkout -b wip-protip<span style="color: #ff4500;">-2</span> protiptag</code></span>), or diff his current HEAD to the tag to see what all he's changed since his branch was stable (<span class="geshifilter"><code class="python geshifilter-python">git diff protiptag...<span style="color: black;">HEAD</span></code></span>)</p>
</div>
<p>This local development scenario can become a team development scenario involving tags, if for example, Tim needed QA to start testing portions of his branch (his changes are just that important). Since the current HEAD of Tim's branch is incredibly unstable, he can push his tag to the central repository so QA can push a stage using the tag to the last stable point in the branch's history with the command: <span class="geshifilter"><code class="python geshifilter-python">git push origin tag protiptag</code></span></p>
<p>Tags are similar to most other "refs" in Git insofar that they are distributable, if I execute <span class="geshifilter"><code class="python geshifilter-python">git fetch your-repo --tags</code></span>, I can pull the tags you've set in "your-repo" and apply them locally aid development. The distributed nature is primarily how tags differ in Git from Subversion, nearly the rest of the concept is the exact same.</p>
<p>Currently at Slide, tag usage is dominated by the <span class="geshifilter"><code class="python geshifilter-python">post-receive</code></span> hook in the central repository, where <strong>every</strong> push into the central repository ("origin") in the branch release branch is tagged. This allows us to quickly "revert" bad live pushes temporarily, by simply pushing the last "good" tagged release, to ensure minimal site destabilization (while we correct live issues outside of the release branch). </p>
<p>For more specific usage of `git tag` refer to the <a href="http://www.kernel.org/pub/software/scm/git/docs/git-tag.html" target="_top">git-tag(1) man page</a></p>
<hr/>
<em>Did you know!</em> <a href="http://www.slide.com/static/jobs">Slide is hiring</a>! Looking for talented engineers to write some good Python and/or JavaScript, feel free to contact me at tyler[at]<a href="http://slide.com">slide</a><br />
<!--break--></p>
http://unethicalblogger.com/posts/2009/01/git_protip_a_picture_worth_a_thousand_words_git_tag#commentsGitSlideSoftware DevelopmentFri, 16 Jan 2009 06:14:10 +0000R. Tyler Croy207 at http://unethicalblogger.comBut Who Will Write The Tests?
http://unethicalblogger.com/posts/2009/01/but_who_will_write_the_tests
<p>In addition to frothing at the mouth about <a href="http://unethicalblogger.com/blog_categories/git">Git</a>, I've been really getting into the concept of automated unit tests lately (thus my interest in <a href="http://unethicalblogger.com/blog_categories/hudson">Hudson</a>). Just like code comments however, tests are good, no tests is bad, wrong tests is worse. That means once you give in to the almighty power of unit testing, you are saddled with the curse of knowing that you will have to update them, forever.</p>
<p>Taking up <a href="http://en.wikipedia.org/wiki/Test-driven_development">Test-driven Development</a> is like having a child, if you are at a point in your life where you're ready to accept that kind of responsibility, it can be wonderful, a lot of work, but ultimately you will feel satisfied with your new role as a Responsible Developer (tm). If you're not prepared to take on the burden that TDD will present you with, you will likely regret it or neglect your tests (Deadbeat Developer, I like this metaphor). </p>
<p>In the Top Friends Team at <a href="http://slide.com">Slide</a>, we practice the more "loose" definition of TDD; tests are not written before functionality is written, but rather functionality is written, and then as part of the QA and release process, the appropriate and accompanying tests are written. Our basic workflow is usually as follows:
<ul>
<li>Tickets are written and assigned to milestones and developers in Trac</li>
<li>Branch is created in central Git repository</li>
<li>General plan-of-action is discussed between developers</li>
<li>Hack-hack-hack</li>
<li>Code complete is reached, QA starts to test milestone</li>
<li>Developers write tests if needed for functionality</li>
<li>Once QA signs off, and tests look solid, code is shipped live</li>
</ul>
</p><p>There are two primary flaws with this workflow, the first and most obvious one is that it is far to easy to "forget to write the tests." That is, the project scheduled to start development tends to "flow forward" into the allotted test-writing time. As important as test coverage is, at the end of the day Slide did not raise funding on having solid test coverage, and our priorities lie in shipping software, first and foremost. Solving the flow-forward of scheduled projects into any available space is something that can be worked on, but never solved, it really comes down to discipline between those in charge of setting up any given project's particular roadmap. </p>
<p>The second, more subtle flaw in this workflow, and I think all Test-driven Devleopment workflows, revolves around the writer of the tests. The fundamental nature of almost all bugs in software is human error, our natural tendency to make mistakes means that nothing we do will ever be perfect, including our tests. If Developer A is writing a couple new methods to handle data validation prior to that data going into the database. Chances are that Developer A's life is going to be made far easier by writing some test cases to run through some predefined user-input, and pass his validation code over it. Therein lies the problem, if the developer doesn't think of a particular edge case when he's writing the code to handle the data validation, the chances he'll remember and account for that particular edge case while he's working on the unit tests is nil.</p>
<p>How do you really ensure that tests are of high enough quality to actually catch errors and regressions?</p>
<p>I think a certain extent of intra-team test writing and code review, depending on the level of communication between developers, can really help. In this case less developer communication is better. If Developer A <strong>tells</strong> Developer B how his code works, Developer B is now going to have an unnecessary expectation when he starts to write tests for Developer A's code. If Developer B reviews the code for what it actually is, instead of what Developer A thinks it is, the tests that will ultimately be written will be more thorough than if Developer A had written the whole suite himself.</p>
<p>This <em>still</em> isn't sufficiently fool-proof to where I feel all that confident in test coverage, the tests being written are subject to the availability, thoroughness and understanding that Developer B brings to the table. Inside a small team like this one, one of those is almost always in short supply (usually availability). </p>
<p>One approach I'm anxious to try is the more active involvement of QA engineers in the test writing process, both in the pre-fail and post-fail scenarios. The pre-fail scenario being one like that which I detailed above, where new code is being written. In this case a QA engineer's experience can help guide the developer on what sets of user-input have typically caused issues in the past. The second case, post-fail, is actually already occuring at Slide; a live issue, data validity bug, or regression is caught by QA engineers who detail the reproduction case in Trac and as a result a regression test can be written for that specific issue.</p>
<p>This still is subject to the three things I cited above: availability, thoroughness and understanding of those involved. I still have a lot of unanswered questions about the ideal QA and Dev workflow however, how does this scale to a team of tens or hudnreds? Who writes the tests for large teams? What about a team of 1 Dev and a 1 QA, what about the lone-hacker? How do you write quality code, without getting bogged down in the mush of writing thousands of tests for everything you can imagine could go wrong?</p>
<p>Who writes the tests?</p>
http://unethicalblogger.com/posts/2009/01/but_who_will_write_the_tests#commentsHudsonSlideSoftware DevelopmentMon, 12 Jan 2009 16:43:02 +0000R. Tyler Croy206 at http://unethicalblogger.comFind me on github (rtyler)
http://unethicalblogger.com/posts/2009/01/find_me_github_rtyler
<p>Rod reminded me with <a href="http://unethicalblogger.com/posts/2009/01/im_using_git_because_it_makes_me_feel_cool#comment-715" target="_blank">his comment</a> in one of <a href="http://unethicalblogger.com/posts/2009/01/im_using_git_because_it_makes_me_feel_cool" target="_blank">my other posts</a> that I've not yet mentioned <a href="http://github.com" target="_blank">github</a>.</p>
<p>I've got a bunch of my nonsense thrown up on <a href="https://github.com/rtyler" target="_blank">github.com/rtyler</a>, it's awesome (no really, github rocks my socks, those guys are good people).</p>
http://unethicalblogger.com/posts/2009/01/find_me_github_rtyler#commentsGitMiscellaneousSlideSoftware DevelopmentMon, 05 Jan 2009 10:04:26 +0000R. Tyler Croy204 at http://unethicalblogger.comGit Protip: By commiting that revision, you fucked us (git revert)
http://unethicalblogger.com/posts/2008/12/git_protip_by_commiting_revision_you_fucked_us_git_revert
<p>I've been sending weekly "Protip" emails about Git to the rest of engineering here at <a href="http://slide.com">Slide</a> for a while now, using the "Protips" as a means of introducing more interesting and complex features Git offers. Below is the third Protip written to date.</p>
<hr/>
<br/></p>
<p>The concept of "revert" in Git versus Subversion is an interesting, albeit subtle, change, mostly in part due to the subtle differences between Git and Subversion's means of tracking commits. Just as with Subversion, you can only revert a committed change, unlike Subversion there is a 1-to-1 mapping of a "commit" to a "revert". The basic syntax of revert is quite easy: <span class="geshifilter"><code class="python geshifilter-python">git revert 0xdeadbeef</code></span>, and just like a regular commit, you will need to push your changes after you revert if you want others to receive the revert as well. <br />
In the following example of a revert of a commit, I also use the "-s" argument on the command line to denote that I'm signing-off on this revert (i.e. I've properly reviewed it).</p>
<pre>
xdev3% git revert -s c20054ea390046bd3a54693f2927192b2a7097c2
----------------[Vim]----------------
Revert "merge-to-release unhide 10000 coin habitat"
This reverts commit c20054ea390046bd3a54693f2927192b2a7097c2.
Signed-off-by: R. Tyler Ballance <tyler@slide.com>
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch wip-protips
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: bt/apps/pet/data.py
</pre><pre>
+ python bt/qa/git/post-commit.py -m svn@slide.com
Sending a commit mail to svn@slide.com
Created commit a6e93b8: Revert "merge-to-release unhide 10000 coin habitat"
1 files changed, 4 insertions(+), 3 deletions(-)
</pre><p><br/><br />
<!--break--></p>
<h2><a name="Reverting_multiple_commits"></a> Reverting multiple commits </h2>
<p>Since <span class="geshifilter"><code class="python geshifilter-python">git revert</code></span> will generate a new commit for you every time you revert a previous commit, reverting multiple commits is not as obvious (side note: I'm aware of the ability to squash commits, or --no-commit for git-revert(1), I dislike compressing revision history when I don't believe there shouldn't be compression). If you want to revert a specific merge from one branch into the other, you can revert the merge commit (provided one was generated when the changes were merged). Take the following example:</p>
<pre>
commit 81a94bb976dfaaaae42ae2600b7e9e88645ebd81
Merge: 8134d17... d227dd8...
Author: R. Tyler Ballance <tyler@slide.com>
Date: Thu Nov 20 10:15:16 2008 -0800
Merge branch 'master' into wip-protips
</pre><p><br/><br />
I want to revert this merge since it refreshed my <strong>wip-protips</strong> branch from master, and brought in a lot changes tat have destablized my branch. In the case of reverting a merge commit, you need to specify <strong>-m</strong> and a number to denote where the mainline branch for Git to pivot off of is, <strong>-m 1</strong> usually suffices. So the revert of the commit above will look something like this:</p>
<pre>
git revert 81a94bb976dfaaaae42ae2600b7e9e88645ebd81 -m 1
</pre><p><br/><br />
Then my revert commit will be committed after I review the change in Vim:</p>
<pre>
commit 8cae4924c4c05dadaaeccb3851cfc9ec1b8efd0f
Author: R. Tyler Ballance <tyler@slide.com>
Date: Thu Nov 20 10:20:44 2008 -0800
Revert "Merge branch 'master' into wip-protips"
This reverts commit 81a94bb976dfaaaae42ae2600b7e9e88645ebd81.
</pre><p><br/><br />
Let's take the extreme case where I don't have a merge commit to pivot off of, or I have a particular <strong>set</strong> of bare revisions that I need to revert in one pass, you can start to tie Git subcommands together like <strong>git-rev-list(1)</strong> to accomplish this. This hypothetical situation might occur if some swath of changes have been applied to a team-master that need to be backed out. Without a merge commit to key off of, you have to revert the commits one by one, but that doesn't mean you have to revert each one by hand:<br />
<span class="geshifilter"><code class="bash geshifilter-bash"><span style="color: #000000; font-weight: bold;">for</span> r <span style="color: #000000; font-weight: bold;">in</span> `git rev-list master...master-fubar --<span style="color: #007800;">since=</span><span style="color: #ff0000;">"8:00"</span> --<span style="color: #007800;">before=</span><span style="color: #ff0000;">"12:00"</span> --no-merges`; <span style="color: #000000; font-weight: bold;">do</span> git revert --no-edit -s <span style="color: #007800;">$r</span>; <span style="color: #000000; font-weight: bold;">done</span></code></span><br />
In the above example, I can use <strong>git-rev-list(1)</strong> to give me a list of revisions that have occurred on "master-fubar" that have not occurred on "master" between the times of 8 a.m. and 12 p.m., excluding merge commits. Since <strong>git-rev-list(1)</strong> will return a list of commit hashes by default, I can loop through those commit hashes in chronological order and revert each one. The inner part of the loop signs-off on the revert (-s) and then tells <strong>git-revert(1)</strong> to auto-commit it without opening the commit message in Vim (--no-edit). What this then outputs is the following:</p>
<pre>
xdev% for r in `git rev-list master...master-fubar --since="8:00" --before="12:00" --no-merges`; do git revert --no-edit -s $r; done
Finished one revert.
Created commit b6810d7: Revert "a test, for you"
1 files changed, 1 insertions(+), 1 deletions(-)
Finished one revert.
Created commit 83156bd: Revert "These are not the droids you are looking for
1 files changed, 2 insertions(+), 0 deletions(-)
Finished one revert.
Created commit 782f328: Revert "commented out stuff"
1 files changed, 0 insertions(+), 3 deletions(-)
Finished one revert.
Created commit 2b8d664: Revert "back on again"
1 files changed, 1 insertions(+), 1 deletions(-)
xdev%
</pre><p><br/><br />
For specific usage of "git-revert" or "git-rev-list" refer to the <a href="http://www.kernel.org/pub/software/scm/git/docs/git-revert.html" target="_top">git-revert(1) man page</a> or the <a href="http://www.kernel.org/pub/software/scm/git/docs/git-rev-list.html" target="_top">git-rev-list(1) man page</a></p>
<hr/>
<em>Did you know!</em> <a href="http://www.slide.com/static/jobs">Slide is hiring</a>! Looking for talented engineers to write some good Python and/or JavaScript, feel free to contact me at tyler[at]<a href="http://slide.com">slide</a></p>
http://unethicalblogger.com/posts/2008/12/git_protip_by_commiting_revision_you_fucked_us_git_revert#commentsGitSlideSoftware DevelopmentWed, 10 Dec 2008 10:35:59 +0000R. Tyler Croy201 at http://unethicalblogger.comGit Protip: Learning from your history (git log)
http://unethicalblogger.com/posts/2008/12/git_protip_learning_your_history_git_log
<p>I've been sending weekly "Protip" emails about Git to the rest of engineering here at <a href="http://slide.com">Slide</a> for a while now, using the "Protips" as a means of introducing more interesting and complex features Git offers. Below is the second Protip written to date.</p>
<hr/>
<br/></p>
<p>
One of the major benefits to using Git is the entirety of the repository being entirely local and easily searched/queried. For this, Git has a very useful command called <span class="geshifilter"><code class="python geshifilter-python">git log</code></span> which allows you to inspect revision histories in numerous different ways between file paths, branches, etc. There are a couple basic scenarios where <span class="geshifilter"><code class="python geshifilter-python">git log</code></span> has become invaluable, for me at least, in order to properly review code but also to track changes effectively from point A to point B.</p>
<ul>
<li> <strong>What's Dave been working on lately?</strong> (<strong><em>with diffs</em></strong>)
<ul>
<li><span class="geshifilter"><code class="python geshifilter-python"> git log -p --no-merges --author=dave</code></span></li>
</ul>
<p>The <strong>--no-merges</strong> option will prevent <strong>git log</strong> from displaying merge commits which are automatically generated whenever you pull from one Git branch to another</p>
</li>
<li> <strong>Before I merge this branch down to my team master, I want to know what files have been changed and what revisions</strong>
<ul>
<li><span class="geshifilter"><code class="python geshifilter-python">git log --name-status master-topfriends...<span style="color: black;">proj</span>-topfriends-thing</code></span></li>
</ul>
<p>
Git supports the ability with <strong>git log</strong> and with <strong>git diff</strong> to provide unidirectional branch lookups or bidirectional branch lookups. For example, say the left branch has commits "A, B" and the right branch has commits "A, C". The <strong>...</strong> syntax will output "C", whereas <strong>..</strong> will output "B, C"
</p>
</li>
<li> <strong>I just got back from a vacation, I wonder what's changed?</strong>
<ul>
<li><span class="geshifilter"><code class="python geshifilter-python">git log --since=<span style="color: #483d8b;">"2 weeks ago"</span> --name-status -- templates</code></span></li>
</ul>
<p>At the tail end of a <strong>git log</strong> command you can specify particular paths to look up the histories for with the <strong>--</strong> operator, in this case, I will be looking at the changes that have occured in the templates directory over the past two weeks</p>
</li>
<li> <strong>Most recent X number of commits?</strong> (<strong><em>with diffs</em></strong>)
<ul>
<li> <span class="geshifilter"><code class="python geshifilter-python">git log -n <span style="color: #ff4500;">10</span> --no-merges -p</code></span>
</li>
</ul>
</li>
</ul>
<p>All <span class="geshifilter"><code class="python geshifilter-python">git log</code></span> commands automatically filter into <span class="geshifilter"><code class="python geshifilter-python">less<span style="color: black;">(</span><span style="color: #ff4500;">1</span><span style="color: black;">)</span></code></span> so you can page through the output like you would normally if you executed a <span class="geshifilter"><code class="python geshifilter-python">svn log | less</code></span>. Because <span class="geshifilter"><code class="python geshifilter-python">git log</code></span> is simply reading from the locally stored revision history you can quickly grep the history by any number of different search criteria to gain a better understanding of how the code base is changing and where.</p>
<p>For more specific usage of `git log` refer to the <a href="http://www.kernel.org/pub/software/scm/git/docs/git-log.html" target="_blank">git log man page</a></p>
<hr/>
<em>Did you know!</em> <a href="http://www.slide.com/static/jobs">Slide is hiring</a>! Looking for talented engineers to write some good Python and/or JavaScript, feel free to contact me at tyler[at]<a href="http://slide.com">slide</a></p>
http://unethicalblogger.com/posts/2008/12/git_protip_learning_your_history_git_log#commentsGitSlideSoftware DevelopmentTue, 02 Dec 2008 22:03:40 +0000R. Tyler Croy200 at http://unethicalblogger.comGit Protip: Stash the goods yo (git stash)
http://unethicalblogger.com/posts/2008/11/git_protip_stash_goods_yo_git_stash
<p>For about a month now I've been sending weekly "Protip" emails about Git to the rest of engineering here at <a href="http://slide.com">Slide</a>. I've been using them to slowly and casually introduce some of the more "interesting" features Git has to offer as we move away from Subversion entirely. Below is the first Protip I sent around, I'll be sure to send the rest in good time.</p>
<hr/>
<br/></p>
<p>
Given the nature of how Git is structured, in that your "working copy" is also your "repository" you might find yourself switching branches relatively often. While you can actually switch branches with uncommited changes outstanding in your Git directory, it's not advised (as you might forget you're commiting changes in the wrong branch, etc). You have two options if you are halfway through some work, one is to commit a checkpoint revision, but the other is to make use of the <span class="geshifilter"><code class="python geshifilter-python">git stash</code></span> command.
</p>
<p>A scenario where this becomes especially useful would be: Bill is working in his local branch "wip-funtime" on replacing large swaths of unnecessary useless code and Ted accidentally pushes some of Bill's other changes from another branch live and things <em>break</em>. Bill <em>could</em> commit his code and write a fairly uninformative log message like "checkpoint" which cheapens the value of the revision history of his changes <strong>or</strong> Bill can use <span class="geshifilter"><code class="python geshifilter-python">git stash</code></span> to snapshot his currently working state and context switch. In this case Bill would execute the following commands:
<ul>
<li> <span class="geshifilter"><code class="python geshifilter-python">git stash</code></span>
</li>
<li> <span class="geshifilter"><code class="python geshifilter-python">git checkout master-media</code></span>
</li>
<li> <em>perform hotfixes</em>
</li>
<li> <span class="geshifilter"><code class="python geshifilter-python">git checkout wip-funtime</code></span>
</li>
<li> <span class="geshifilter"><code class="python geshifilter-python">git stash pop</code></span>
</li>
</ul>
<p>After performing the <span class="geshifilter"><code class="python geshifilter-python">git stash pop</code></span> command, Bill's Git repository will be in the exact same state, all his uncommitted changes in tact, as it was when he originally stashed and context-switched.</p>
<p />
For specific usage of `git stash` refer to the <a href="http://www.kernel.org/pub/software/scm/git/docs/git-stash.html" target="_top">git stash man page</a></p>
<p />
<p />
<h3><a name="Example_usage_of_git_stash"></a><a name="Example_usage_of_git_stash_"></a> Example usage of `git stash` </h3>
<p><strong>Stashing changes away</strong><br />
<div class="geshifilter"><pre class="geshifilter-bash"><ol><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">tyler<span style="color: #000000; font-weight: bold;">@</span>starfruit:~<span style="color: #000000; font-weight: bold;">/</span><span style="color: #7a0874; font-weight: bold;">source</span><span style="color: #000000; font-weight: bold;">/</span>git<span style="color: #000000; font-weight: bold;">/</span>main<span style="color: #000000; font-weight: bold;">/</span>bt<span style="color: #000000; font-weight: bold;">></span> git stash</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">Saved working directory and index state <span style="color: #ff0000;">"WIP on master-topfriends: 7b1ce9e... TOS copy fix"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">(</span>To restore them <span style="color: #7a0874; font-weight: bold;">type</span> <span style="color: #ff0000;">"git stash apply"</span><span style="color: #7a0874; font-weight: bold;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">HEAD is now at 7b1ce9e TOS copy fix</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">tyler<span style="color: #000000; font-weight: bold;">@</span>starfruit:~<span style="color: #000000; font-weight: bold;">/</span><span style="color: #7a0874; font-weight: bold;">source</span><span style="color: #000000; font-weight: bold;">/</span>git<span style="color: #000000; font-weight: bold;">/</span>main<span style="color: #000000; font-weight: bold;">/</span>bt<span style="color: #000000; font-weight: bold;">></span> </div></li></ol></pre></div><strong>Looking at the stash</strong><br />
<div class="geshifilter"><pre class="geshifilter-bash"><ol><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">tyler<span style="color: #000000; font-weight: bold;">@</span>starfruit:~<span style="color: #000000; font-weight: bold;">/</span><span style="color: #7a0874; font-weight: bold;">source</span><span style="color: #000000; font-weight: bold;">/</span>git<span style="color: #000000; font-weight: bold;">/</span>main<span style="color: #000000; font-weight: bold;">/</span>bt<span style="color: #000000; font-weight: bold;">></span> git stash list</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">stash<span style="color: #000000; font-weight: bold;">@</span><span style="color: #7a0874; font-weight: bold;">{</span><span style="color: #000000;">0</span><span style="color: #7a0874; font-weight: bold;">}</span>: WIP on master-topfriends: 7b1ce9e... TOS copy fix</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">stash<span style="color: #000000; font-weight: bold;">@</span><span style="color: #7a0874; font-weight: bold;">{</span><span style="color: #000000;">1</span><span style="color: #7a0874; font-weight: bold;">}</span>: On master-topfriends: starfruit <span style="color: #7a0874; font-weight: bold;">complete</span> patchset</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">stash<span style="color: #000000; font-weight: bold;">@</span><span style="color: #7a0874; font-weight: bold;">{</span><span style="color: #000000;">2</span><span style="color: #7a0874; font-weight: bold;">}</span>: On wip-classmethod: starfruit patches</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">tyler<span style="color: #000000; font-weight: bold;">@</span>starfruit:~<span style="color: #000000; font-weight: bold;">/</span><span style="color: #7a0874; font-weight: bold;">source</span><span style="color: #000000; font-weight: bold;">/</span>git<span style="color: #000000; font-weight: bold;">/</span>main<span style="color: #000000; font-weight: bold;">/</span>bt<span style="color: #000000; font-weight: bold;">></span> </div></li></ol></pre></div><strong>Grabbing the latest from the stash</strong><br />
<div class="geshifilter"><pre class="geshifilter-bash"><ol><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">tyler<span style="color: #000000; font-weight: bold;">@</span>starfruit:~<span style="color: #000000; font-weight: bold;">/</span><span style="color: #7a0874; font-weight: bold;">source</span><span style="color: #000000; font-weight: bold;">/</span>git<span style="color: #000000; font-weight: bold;">/</span>main<span style="color: #000000; font-weight: bold;">/</span>bt<span style="color: #000000; font-weight: bold;">></span> git stash pop</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">Dropped refs<span style="color: #000000; font-weight: bold;">/</span>stash<span style="color: #000000; font-weight: bold;">@</span><span style="color: #7a0874; font-weight: bold;">{</span><span style="color: #000000;">0</span><span style="color: #7a0874; font-weight: bold;">}</span> <span style="color: #7a0874; font-weight: bold;">(</span>94b9722b5a999c32c4361d795ee8f368d8412f9a<span style="color: #7a0874; font-weight: bold;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">tyler<span style="color: #000000; font-weight: bold;">@</span>starfruit:~<span style="color: #000000; font-weight: bold;">/</span><span style="color: #7a0874; font-weight: bold;">source</span><span style="color: #000000; font-weight: bold;">/</span>git<span style="color: #000000; font-weight: bold;">/</span>main<span style="color: #000000; font-weight: bold;">/</span>bt<span style="color: #000000; font-weight: bold;">></span> </div></li></ol></pre></div><strong>Grabbing a specific stash</strong><br />
<div class="geshifilter"><pre class="geshifilter-bash"><ol><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">tyler<span style="color: #000000; font-weight: bold;">@</span>starfruit:~<span style="color: #000000; font-weight: bold;">/</span><span style="color: #7a0874; font-weight: bold;">source</span><span style="color: #000000; font-weight: bold;">/</span>git<span style="color: #000000; font-weight: bold;">/</span>main<span style="color: #000000; font-weight: bold;">/</span>bt<span style="color: #000000; font-weight: bold;">></span> git stash list</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">stash<span style="color: #000000; font-weight: bold;">@</span><span style="color: #7a0874; font-weight: bold;">{</span><span style="color: #000000;">0</span><span style="color: #7a0874; font-weight: bold;">}</span>: WIP on master-topfriends: 7b1ce9e... TOS copy fix</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">stash<span style="color: #000000; font-weight: bold;">@</span><span style="color: #7a0874; font-weight: bold;">{</span><span style="color: #000000;">1</span><span style="color: #7a0874; font-weight: bold;">}</span>: On master-topfriends: starfruit <span style="color: #7a0874; font-weight: bold;">complete</span> patchset</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">stash<span style="color: #000000; font-weight: bold;">@</span><span style="color: #7a0874; font-weight: bold;">{</span><span style="color: #000000;">2</span><span style="color: #7a0874; font-weight: bold;">}</span>: On wip-classmethod: starfruit patches</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">tyler<span style="color: #000000; font-weight: bold;">@</span>starfruit:~<span style="color: #000000; font-weight: bold;">/</span><span style="color: #7a0874; font-weight: bold;">source</span><span style="color: #000000; font-weight: bold;">/</span>git<span style="color: #000000; font-weight: bold;">/</span>main<span style="color: #000000; font-weight: bold;">/</span>bt<span style="color: #000000; font-weight: bold;">></span> git stash apply <span style="color: #000000;">2</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;"># On branch master-topfriends</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;"># Changed but not updated:</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;"># (use "git add <file>..." to update what will be committed)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;">#</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;"># modified: db/dbroot.py</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;"># modified: gogreen/coro.py</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;"># modified: py/bin/_makepyrelease.py</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;"># modified: py/initpkg.py</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;"># modified: py/misc/_dist.py</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;"># modified: py/misc/testing/test_initpkg.py</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;"># modified: py/path/local/local.py</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;"># modified: py/test/terminal/terminal.py</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">tyler<span style="color: #000000; font-weight: bold;">@</span>starfruit:~<span style="color: #000000; font-weight: bold;">/</span><span style="color: #7a0874; font-weight: bold;">source</span><span style="color: #000000; font-weight: bold;">/</span>git<span style="color: #000000; font-weight: bold;">/</span>main<span style="color: #000000; font-weight: bold;">/</span>bt<span style="color: #000000; font-weight: bold;">></span></div></li></ol></pre></div></p>
<hr/>
<em>Did you know!</em> <a href="http://www.slide.com/static/jobs">Slide is hiring</a>! Looking for talented engineers to write some good Python and/or JavaScript, feel free to contact me at tyler[at]<a href="http://slide.com">slide</a></p>
http://unethicalblogger.com/posts/2008/11/git_protip_stash_goods_yo_git_stash#commentsGitSlideTue, 25 Nov 2008 07:50:06 +0000R. Tyler Croy199 at http://unethicalblogger.comWhy we chose Git, a rebuttal.
http://unethicalblogger.com/posts/2008/11/why_we_chose_git_a_rebuttal
<p>One thing I learned early on in the internet, when is was more of a cobbling instead of a series, of tubes, was not to feed trolls. <img src="http://agentdero.cachefly.net/unethicalblogger.com/images/xkcd_duty_calls.png" height="250" align="right"/>That said, I found that my post "<a href="http://www.unethicalblogger.com/posts/2008/11/delightfully_wrong_about_git" target="_blank">Delightfully Wrong About Git</a>" had found it's way into such silly news aggregation machines as <a href="http://www.dzone.com/links/delightfully_wrong_about_git.html" target="_blank">DZone</a>, <a href="http://www.reddit.com/r/git/comments/7fdbd/delightfully_wrong_about_git/" target="_blank">Reddit</a> and <a href="http://news.ycombinator.com/item?id=374970" target="_blank">Hacker News</a>. Some of the points raised in the comments were valid and warrant a response, while the majority of them were the standard responses to <strong>any</strong> discussion about version control "psh, dumb. should have used [Bazaar/Mercurial/Darcs/Subversion/Team Foundation System]"</p>
<p><strong><big>Why not another (D)VCS?</big></strong><br />
One of the most resounding criticisms/questions was this one, why not Bazaar? Why not Mercurial! My favorite, albeit childish, retort is "why?" But I can say that I have tried a variety of other version control systems, Git, Bazaar, CVS, Subversion, Perforce and some other proprietary VCSes at previous employers. While both Darcs and Mercurial seem to be very solid DVCSes, they suffer from a problem of momentum, Darcs in particular. They both appear to be victims of Git's success, while there is inherently nothing wrong with either of them, they are competing with Linus' love-child, Git. When chosing to move to a new VCS in a company that is well over 50+ employees, the staying power of the technology you chose is important. I feel confident that Git will not only be supported, but actively developed and improved for years to come.</p>
<p>More importantly than that though, <strong>I like Git</strong>. Is that not enough right there? <a href="http://slide.com" target="_blank">Slide</a> makes excessive use of branches, tags and other "complex" VCS concepts that centralized systems like CVS and Subversion have trouble with.<img src="http://agentdero.cachefly.net/unethicalblogger.com/images/branch_madness.jpeg" width="300" align="left" hspace="4" alt="Git Branch Madness!" title="Git Branch Madness!"/> With Subversion creating branches in the volume in which we create branches spiralled out of control with branches becoming "stale" quickly, meaning that if we didn't refresh the branch regularly with updates from trunk it would be nearly impossible to cleanly merge back down into trunk. With my current Git clone of our primary repository, I have 23 branches (roughly 6 personal local branches, 5 old branches, and 12 active branches). Our primary Git repository has been online for about 6 months and currently has 68 branches in it, rougly 55 are active.<br />
<blockquote>Why all the love for Git, but nobody every talks about Bazaar, Mercurial, Darcs, etc? Sure Git is faster, but unless you've got a enormous code base (like the linux kernel), it seems like Bazaar or Mercurial would be a better choice than Git.</p></blockquote>
<p>One of the better known selling points of Git is that <strong>it's fast</strong>. My cloned repository of the primary Slide Git repository weighs in at a hefty <strong>7.1GB</strong>. The latest revision number in our Subversion repository is in the 103,000 range, tacked onto that our tree is just over 2GB in size, and you've got a lot of history to keep track of. Git handles this without a sweat. despite hitting the disk extremely hard when switching to a very out of date branch. With <a href="http://kerneltrap.org/mailarchive/git/2008/10/29/3859494" target="_blank">this fix</a> from Nico, the last of the mmap(2) allocation issues we were experiencing vanished as well.</p>
<p><strong><big>Stop re-inventing the wheel!</big></strong><br />
One of the more interesting sentiments I noticed perusing the various comments made regarding my previous post were that we are "re-inventing the wheel" by writing scripts, hooks and other wrappers to use a product like Git. The notion that having scripts and hooks for something you use in daily development is re-inventing the wheel, or gratuituous strikes me as laughable at best. We're developers. We write scripts. Why didn't I ever write a myriad of scripts when I was an avid Subversion user? <a href="http://www.unethicalblogger.com/posts/r_tyler_ballance/subversion_branching_with_less_pain" target="_blank"><strong>I did.</strong></a>. There's an enormous different between writing scripts to compensate for a poorly performing product, and writing scripts to further enhance you or your colleagues' workflows, <a href="http://www.kernel.org/pub/software/scm/git/docs/githooks.html" target="_blank">Git's hook support</a> falls into the latter category.</p>
<p>The "religion" aspect of the whole version control debate was never considered in our transition to Git, nor was the buzz. I'm far more interested in what makes other VCSes better or worse than Git, so that Git can be improved instead of a justification to ditch Git for yet-another-dvcs. I like to think of the various tools like version control that we developers use as something more relatable: work pants. A good pair of work pants should be flexible enough to allow you to get your work done, modest enough to stay out of the way and most importantly, a good pair of work pants should keep your junk safe ;)</p>
<p>I'm still happy to answer more specific questions about Git and how/why it works for us as well as it has, but I think most of the questions I've seen thus far have been answered above.<br />
<br/></p>
<hr/>
<em>Did you know!</em> <a href="http://www.slide.com/static/jobs">Slide is hiring</a>! Looking for talented engineers to write some good Python and/or JavaScript, feel free to contact me at tyler[at]<a href="http://slide.com">slide</a></p>
http://unethicalblogger.com/posts/2008/11/why_we_chose_git_a_rebuttal#commentsGitOpinionSlideSoftware DevelopmentTue, 25 Nov 2008 04:24:38 +0000R. Tyler Croy198 at http://unethicalblogger.comGit integration with Hudson and Trac.
http://unethicalblogger.com/posts/2008/11/git_integration_with_hudson_and_trac
<p>As I mentioned in my <a href="http://www.unethicalblogger.com/posts/2008/11/delightfully_wrong_about_git" target="_blank">previous post about Git</a> at <a href="http://slide.com">Slide</a>, I wanted to answer some questions that we had to answer to migrate to Git for our development workflow. One of the major questions that had to be answered, especially for our QA department to sign off on the idea was:<br />
<blockquote>How will Git integrate with Hudson, Trac and our other pieces of development infrastructure?</p></blockquote>
<p>For us to use any version control system, centralized or decentralized, there had to be a "central" point for changes to integrate into in order for us to properly test releases and then ship them to the live site. With this requirement, we oriented our use of Git around a centralized repository which developers pull from, and push to on a regular basis.</p>
<p>In order for Git to integrate into Trac and Hudson, we opted for baking the functionality we needed into the post-receive hook on the centralized repository instead of relying on <a href="http://trac-hacks.org/wiki/GitPlugin" target="_blank">GitTrac</a>, or the <a href="http://hudson.gotdns.com/wiki/display/HUDSON/Git+Plugin" target="_blank">Hudson Git plugin</a> to do what we needed it do to.</p>
<p>You can find the script below, or <a href="https://github.com/rtyler/slide-git-scripts/tree" target="_blank">in this GitHub repository</a>. The script requires the <a href="http://trac-hacks.org/wiki/XmlRpcPlugin" target="_blank">Trac XML-RPC</a> plugin to be installed in order to properly annotate tickets when changes are pushed into the central repository. The notation syntaxes that the post-receive.py script supports in commit messages are:<br />
<blockquote>re #12345<br />
qa #12345<br />
attn bbum,fspeirs</p></blockquote>
<p><!--break--><br />
As one might expect, the first notation: "<strong>re #12345</strong>" will simply annotate a ticket with the commit message and the branch in which the commit was pushed into. The "<strong>qa #12345</strong>" notation part of an internal notation of marking tickets in Trac as "Ready for QA", which let's our QA engineers know when tickets are ready to be verified; a "qa" note in a commit message will reference the commit and change the status of the ticket in question. The final notation that the script supports: "<strong>attn bbum,fspeirs</strong>" is purely for calling attention to a code change, or to ask for a code review. When a commit is pushed to the central repository with "attn" in the commit message, an email with the commit message and diff will be emailed to the specified recipients.</p>
<p>In addition to updating Trac tickets, pushes into any branch that have a <a href="https://hudson.dev.java.net/" target="_blank">Hudson</a> job affiliated will use the Hudson External API to queue a build for that branch. In effect, it you "git push origin master", the post-receive.py script will ping Hudson and ask it to queue a build of the "master" job.</p>
<p>I have included the script inline below for those weary of clicking links like <a href="https://github.com/rtyler/slide-git-scripts/tree" target="_blank">this one to the GitHub repository containing the script</a>. Enjoy :)</p>
<div style="height: 500px; overflow: scroll;"><div class="geshifilter"><pre class="geshifilter-python"><ol><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">''</span><span style="color: #483d8b;">'</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">Copyright (c) 2008 Slide, Inc</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">Permission is hereby granted, free of charge, to any person obtaining a copy</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">of this software and associated documentation files (the "Software"), to deal</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">in the Software without restriction, including without limitation the rights</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">to use, copy, modify, merge, publish, distribute, sublicense, and/or sell</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">copies of the Software, and to permit persons to whom the Software is</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">furnished to do so, subject to the following conditions:</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">The above copyright notice and this permission notice shall be included in</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">all copies or substantial portions of the Software.</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">THE SOFTWARE.</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">'</span><span style="color: #483d8b;">''</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">''</span><span style="color: #483d8b;">'</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;"> For questions, patches, etc contact R. Tyler Ballance <[email protected]></span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">'</span><span style="color: #483d8b;">''</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">import</span> <span style="color: #dc143c;">getpass</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">import</span> <span style="color: #dc143c;">os</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">import</span> <span style="color: #dc143c;">re</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">import</span> <span style="color: #dc143c;">socket</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">import</span> <span style="color: #dc143c;">smtplib</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">import</span> <span style="color: #dc143c;">sys</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">import</span> <span style="color: #dc143c;">time</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">import</span> <span style="color: #dc143c;">xmlrpclib</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">from</span> <span style="color: #dc143c;">optparse</span> <span style="color: #ff7700;font-weight:bold;">import</span> OptionParser</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">MAIL_SERVER = <span style="color: #483d8b;">'your_mail_server.com'</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">MAIL_SUFFIX = <span style="color: #483d8b;">'@mycompany.com'</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">BUILD_HUDSON = <span style="color: #008000;">True</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">HUDSON_URL = <span style="color: #483d8b;">'http://hudson'</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">TRAC_XMLRPC_URL = <span style="color: #483d8b;">'URL_TO_TRAC/projects/MYPROJECT/login/xmlrpc'</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">def</span> rpcProxy<span style="color: black;">(</span><span style="color: #dc143c;">user</span>=<span style="color: #483d8b;">'qatracbot'</span>, password=<span style="color: #008000;">None</span><span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> password = password <span style="color: #ff7700;font-weight:bold;">or</span> <span style="color: #dc143c;">os</span>.<span style="color: black;">getenv</span><span style="color: black;">(</span><span style="color: #483d8b;">'TRAC_PASS'</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #dc143c;">xmlrpclib</span>.<span style="color: black;">ServerProxy</span><span style="color: black;">(</span><span style="color: #483d8b;">'https://%s:%s@%s'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span><span style="color: #dc143c;">user</span>, password, TRAC_XMLRPC_URL<span style="color: black;">)</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">def</span> _send_commit_mail<span style="color: black;">(</span><span style="color: #dc143c;">user</span>, address, subject, branch, commits, files, diff<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">'Sending a GITRECEIVE mail to %s'</span> <span style="color: #66cc66;">%</span> address</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> message = <span style="color: #483d8b;">'Commits pushed to %s:<span style="color: #000099; font-weight: bold;">\n</span>--------------------------------------<span style="color: #000099; font-weight: bold;">\n</span><span style="color: #000099; font-weight: bold;">\n</span>%s<span style="color: #000099; font-weight: bold;">\n</span>--------------------------------------<span style="color: #000099; font-weight: bold;">\n</span>%s<span style="color: #000099; font-weight: bold;">\n</span>--------------------------------------<span style="color: #000099; font-weight: bold;">\n</span>%s'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span>branch, commits, files, diff<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> _send_mail<span style="color: black;">(</span><span style="color: #dc143c;">user</span>, address, subject, message<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">def</span> _send_attn_mail<span style="color: black;">(</span><span style="color: #dc143c;">user</span>, destuser, diff<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">'Sending a "please review" mail to %s'</span> <span style="color: #66cc66;">%</span> destuser</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> message = <span style="color: #483d8b;">''</span><span style="color: #483d8b;">'Good day my most generous colleague! I would hold you in the highest esteem and toast you over my finest wines if you would kindly review this for me<span style="color: #000099; font-weight: bold;">\n</span><span style="color: #000099; font-weight: bold;">\n</span><span style="color: #000099; font-weight: bold;">\t</span> - %(user)s<span style="color: #000099; font-weight: bold;">\n</span><span style="color: #000099; font-weight: bold;">\n</span>Diff:<span style="color: #000099; font-weight: bold;">\n</span>------------------------------------------------<span style="color: #000099; font-weight: bold;">\n</span>%(diff)s'</span><span style="color: #483d8b;">''</span> <span style="color: #66cc66;">%</span> <span style="color: black;">{</span><span style="color: #483d8b;">'diff'</span> : diff, <span style="color: #483d8b;">'user'</span> : <span style="color: #dc143c;">user</span><span style="color: black;">}</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> addresses = <span style="color: black;">[</span><span style="color: black;">]</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">for</span> d <span style="color: #ff7700;font-weight:bold;">in</span> destuser.<span style="color: black;">split</span><span style="color: black;">(</span><span style="color: #483d8b;">','</span><span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> addresses.<span style="color: black;">append</span><span style="color: black;">(</span><span style="color: #483d8b;">'%s%s'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span>d, EMAIL_SUFFIX<span style="color: black;">)</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> _send_mail<span style="color: black;">(</span><span style="color: #dc143c;">user</span>, addresses, <span style="color: #483d8b;">'Please review this change'</span>, message<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">def</span> _send_mail<span style="color: black;">(</span><span style="color: #dc143c;">user</span>, address, subject, contents<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">try</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">if</span> <span style="color: #ff7700;font-weight:bold;">not</span> <span style="color: #008000;">isinstance</span><span style="color: black;">(</span>address, <span style="color: #008000;">list</span><span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> address = <span style="color: black;">[</span>address<span style="color: black;">]</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> s = <span style="color: #dc143c;">smtplib</span>.<span style="color: black;">SMTP</span><span style="color: black;">(</span>MAIL_SERVER<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> message = <span style="color: #483d8b;">'From: %s%s<span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>To: %s<span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>Subject: %s<span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span><span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>%s<span style="color: #000099; font-weight: bold;">\n</span>'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span><span style="color: #dc143c;">user</span>, MAIL_SUFFIX, <span style="color: #483d8b;">', '</span>.<span style="color: black;">join</span><span style="color: black;">(</span>address<span style="color: black;">)</span>, subject, contents<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> s.<span style="color: black;">sendmail</span><span style="color: black;">(</span><span style="color: #483d8b;">'%s%s'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span><span style="color: #dc143c;">user</span>, MAIL_SUFFIX<span style="color: black;">)</span>, address, message<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> s.<span style="color: black;">quit</span><span style="color: black;">(</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">except</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">'Failed to send the email :('</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">def</span> _update_ticket<span style="color: black;">(</span>ticket, message, options=<span style="color: black;">{</span><span style="color: black;">}</span><span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> rpc = rpcProxy<span style="color: black;">(</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> rpc.<span style="color: black;">ticket</span>.<span style="color: black;">update</span><span style="color: black;">(</span>ticket, message, options<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">return</span> rpc</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">def</span> find_re<span style="color: black;">(</span>commit<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #008000;">map</span><span style="color: black;">(</span><span style="color: #008000;">int</span>, <span style="color: #dc143c;">re</span>.<span style="color: black;">findall</span><span style="color: black;">(</span>r<span style="color: #483d8b;">'(?i)<span style="color: #000099; font-weight: bold;">\s</span>+re<span style="color: #000099; font-weight: bold;">\s</span>*#([0-9]+)'</span>, commit<span style="color: black;">)</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">def</span> handle_re<span style="color: black;">(</span>branch, commit, ticket<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">'Annotating ticket #%s'</span> <span style="color: #66cc66;">%</span> ticket</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> message = <span style="color: #483d8b;">''</span><span style="color: #483d8b;">'The following was committed in "%(branch)s":</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;"> {{{ </span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">%(commit)s }}}</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;"> '</span><span style="color: #483d8b;">''</span> <span style="color: #66cc66;">%</span> <span style="color: black;">{</span><span style="color: #483d8b;">'branch'</span> : branch, <span style="color: #483d8b;">'commit'</span> : commit<span style="color: black;">}</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> _update_ticket<span style="color: black;">(</span>ticket, message<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">def</span> find_qa<span style="color: black;">(</span>commit<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #008000;">map</span><span style="color: black;">(</span><span style="color: #008000;">int</span>, <span style="color: #dc143c;">re</span>.<span style="color: black;">findall</span><span style="color: black;">(</span>r<span style="color: #483d8b;">'(?i)<span style="color: #000099; font-weight: bold;">\s</span>+qa<span style="color: #000099; font-weight: bold;">\s</span>*#([0-9]+)'</span>, commit<span style="color: black;">)</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">def</span> handle_qa<span style="color: black;">(</span>branch, commit, ticket<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">'Marking ticket #%s as "ready for QA"'</span> <span style="color: #66cc66;">%</span> ticket</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> message = <span style="color: #483d8b;">''</span><span style="color: #483d8b;">'The following was committed in "%(branch)s":</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;"> {{{ </span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;">%(commit)s }}}</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #483d8b;"> '</span><span style="color: #483d8b;">''</span> <span style="color: #66cc66;">%</span> <span style="color: black;">{</span><span style="color: #483d8b;">'branch'</span> : branch, <span style="color: #483d8b;">'commit'</span> : commit<span style="color: black;">}</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> rpc = _update_ticket<span style="color: black;">(</span>ticket, message, options=<span style="color: black;">{</span><span style="color: #483d8b;">'status'</span> : <span style="color: #483d8b;">'qa'</span><span style="color: black;">}</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">def</span> find_attn<span style="color: black;">(</span>commit<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #dc143c;">re</span>.<span style="color: black;">findall</span><span style="color: black;">(</span>r<span style="color: #483d8b;">'(?i)<span style="color: #000099; font-weight: bold;">\s</span>+attn<span style="color: #000099; font-weight: bold;">\s</span>*([A-Za-z,]+)'</span>, commit<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">def</span> handle_attn<span style="color: black;">(</span>branch, commit, attn<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #808080; font-style: italic;"># Unpack commit from this: "commit 5f4c31f3c31347c62d68ecb5f2c9afa3333f4ad0\nAuthor: R. Tyler Ballance <[email protected]>\nDate: Wed Nov 12 16:57:32 2008 -0800 \n\n Merge commit 'git-svn' \n\n \n \n"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">try</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> commit_hash = commit.<span style="color: black;">split</span><span style="color: black;">(</span><span style="color: #483d8b;">'<span style="color: #000099; font-weight: bold;">\n</span>'</span><span style="color: black;">)</span><span style="color: black;">[</span><span style="color: #ff4500;">0</span><span style="color: black;">]</span>.<span style="color: black;">split</span><span style="color: black;">(</span><span style="color: #483d8b;">' '</span><span style="color: black;">)</span><span style="color: black;">[</span><span style="color: #ff4500;">1</span><span style="color: black;">]</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">except</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #808080; font-style: italic;"># fuk it</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> diff = <span style="color: #dc143c;">os</span>.<span style="color: black;">popen</span><span style="color: black;">(</span><span style="color: #483d8b;">'git show --no-color %s --pretty=format:"Author: %%cn <%%ce>%%n%%s%%n%%n%%b%%n%%n%%H"'</span> <span style="color: #66cc66;">%</span> commit_hash<span style="color: black;">)</span>.<span style="color: black;">read</span><span style="color: black;">(</span><span style="color: black;">)</span>.<span style="color: black;">rstrip</span><span style="color: black;">(</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> _send_attn_mail<span style="color: black;">(</span><span style="color: #dc143c;">getpass</span>.<span style="color: black;">getuser</span><span style="color: black;">(</span><span style="color: black;">)</span>, attn, diff<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">def</span> mail_push<span style="color: black;">(</span>address, oldrev, newrev, refname<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #dc143c;">user</span> = <span style="color: #dc143c;">getpass</span>.<span style="color: black;">getuser</span><span style="color: black;">(</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> machine = <span style="color: #dc143c;">socket</span>.<span style="color: black;">gethostname</span><span style="color: black;">(</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> base_git_diff = <span style="color: #483d8b;">'git diff %s %s'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span>oldrev, newrev<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> files_diffed = <span style="color: #dc143c;">os</span>.<span style="color: black;">popen</span><span style="color: black;">(</span><span style="color: #483d8b;">'%s --name-status'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span>base_git_diff<span style="color: black;">)</span><span style="color: black;">)</span>.<span style="color: black;">read</span><span style="color: black;">(</span><span style="color: black;">)</span>.<span style="color: black;">rstrip</span><span style="color: black;">(</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> full_diff = <span style="color: #dc143c;">os</span>.<span style="color: black;">popen</span><span style="color: black;">(</span><span style="color: #483d8b;">'%s -p --no-color'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span>base_git_diff<span style="color: black;">)</span><span style="color: black;">)</span>.<span style="color: black;">read</span><span style="color: black;">(</span><span style="color: black;">)</span>.<span style="color: black;">rstrip</span><span style="color: black;">(</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #483d8b;">''</span><span style="color: #483d8b;">' git rev-parse --not --branches | grep -v "$new" | git rev-list "$old".."$new" --stdin '</span><span style="color: #483d8b;">''</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> commits = <span style="color: #dc143c;">os</span>.<span style="color: black;">popen</span><span style="color: black;">(</span><span style="color: #483d8b;">'git rev-parse --not --branches | grep -v "%s" | git rev-list %s..%s --stdin --pretty=format:"Author: %%cn <%%ce>%%nDate: %%cd %%n%%n %%s %%n%%n %%b %%n %%n-------[post-receive marker]------%%n" --first-parent '</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span>newrev, oldrev, newrev<span style="color: black;">)</span><span style="color: black;">)</span>.<span style="color: black;">read</span><span style="color: black;">(</span><span style="color: black;">)</span>.<span style="color: black;">rstrip</span><span style="color: black;">(</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> branch = refname.<span style="color: black;">split</span><span style="color: black;">(</span><span style="color: #483d8b;">'/'</span><span style="color: black;">)</span><span style="color: black;">[</span><span style="color: #ff4500;">-1</span><span style="color: black;">]</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> mail_subject = <span style="color: #483d8b;">'GITRECEIVE [%s/%s] %s files changed'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span>machine, branch, <span style="color: #008000;">len</span><span style="color: black;">(</span>files_diffed.<span style="color: black;">split</span><span style="color: black;">(</span><span style="color: #483d8b;">'<span style="color: #000099; font-weight: bold;">\n</span>'</span><span style="color: black;">)</span><span style="color: black;">)</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">if</span> branch == <span style="color: #483d8b;">'master-release'</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">'Tagging release branch'</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> tagname = <span style="color: #483d8b;">'livepush_%s'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span><span style="color: #dc143c;">time</span>.<span style="color: black;">strftime</span><span style="color: black;">(</span><span style="color: #483d8b;">'%Y%m%d%H%M%S'</span>, <span style="color: #dc143c;">time</span>.<span style="color: black;">localtime</span><span style="color: black;">(</span><span style="color: black;">)</span><span style="color: black;">)</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #dc143c;">sys</span>.<span style="color: black;">stderr</span>.<span style="color: black;">write</span><span style="color: black;">(</span><span style="color: #483d8b;">'Creating a tag named: %s<span style="color: #000099; font-weight: bold;">\n</span><span style="color: #000099; font-weight: bold;">\n</span>'</span> <span style="color: #66cc66;">%</span> tagname<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #dc143c;">os</span>.<span style="color: black;">system</span><span style="color: black;">(</span><span style="color: #483d8b;">'git tag %s'</span> <span style="color: #66cc66;">%</span> tagname<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> mail_subject = <span style="color: #483d8b;">'%s (tagged: %s)'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span>mail_subject, tagname<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">if</span> BUILD_HUDSON_JOB:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">'Queuing the Hudson job for "%s"'</span> <span style="color: #66cc66;">%</span> branch</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #dc143c;">os</span>.<span style="color: black;">system</span><span style="color: black;">(</span><span style="color: #483d8b;">'/usr/bin/env wget -q -O /dev/null http://%s/job/%s/build'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span>HUDSON_URL, branch<span style="color: black;">)</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> _send_commit_mail<span style="color: black;">(</span><span style="color: #dc143c;">user</span>, address, mail_subject, branch, commits, files_diffed, full_diff<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">if</span> branch == <span style="color: #483d8b;">'master'</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #808080; font-style: italic;"># we don't want to update tickets and such for master/merges</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> commits = <span style="color: #008000;">filter</span><span style="color: black;">(</span><span style="color: #ff7700;font-weight:bold;">lambda</span> c: <span style="color: #008000;">len</span><span style="color: black;">(</span>c<span style="color: black;">)</span>, commits.<span style="color: black;">split</span><span style="color: black;">(</span><span style="color: #483d8b;">'-------[post-receive marker]------'</span><span style="color: black;">)</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> commits.<span style="color: black;">reverse</span><span style="color: black;">(</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">for</span> c <span style="color: #ff7700;font-weight:bold;">in</span> commits:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">if</span> c.<span style="color: black;">find</span><span style="color: black;">(</span><span style="color: #483d8b;">'Squashed commit'</span><span style="color: black;">)</span> <span style="color: #66cc66;">></span>= <span style="color: #ff4500;">0</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">continue</span> <span style="color: #808080; font-style: italic;"># Skip bullshit squashed commit</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">for</span> attn <span style="color: #ff7700;font-weight:bold;">in</span> find_attn<span style="color: black;">(</span>c<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> handle_attn<span style="color: black;">(</span>branch, c, attn<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">for</span> ticket <span style="color: #ff7700;font-weight:bold;">in</span> find_re<span style="color: black;">(</span>c<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> handle_re<span style="color: black;">(</span>branch, c, ticket<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">for</span> ticket <span style="color: #ff7700;font-weight:bold;">in</span> find_qa<span style="color: black;">(</span>c<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> handle_qa<span style="color: black;">(</span>branch, c, ticket<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">if</span> __name__ == <span style="color: #483d8b;">'__main__'</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> op = OptionParser<span style="color: black;">(</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> op.<span style="color: black;">add_option</span><span style="color: black;">(</span><span style="color: #483d8b;">'-m'</span>, <span style="color: #483d8b;">'--mail'</span>, dest=<span style="color: #483d8b;">'address'</span>, <span style="color: #008000;">help</span>=<span style="color: #483d8b;">'Email address to mail git push messages to'</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> op.<span style="color: black;">add_option</span><span style="color: black;">(</span><span style="color: #483d8b;">'-o'</span>, <span style="color: #483d8b;">'--oldrev'</span>, dest=<span style="color: #483d8b;">'oldrev'</span>, <span style="color: #008000;">help</span>=<span style="color: #483d8b;">'Old revision we<span style="color: #000099; font-weight: bold;">\'</span>re pushing from'</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> op.<span style="color: black;">add_option</span><span style="color: black;">(</span><span style="color: #483d8b;">'-n'</span>, <span style="color: #483d8b;">'--newrev'</span>, dest=<span style="color: #483d8b;">'newrev'</span>, <span style="color: #008000;">help</span>=<span style="color: #483d8b;">'New revision we<span style="color: #000099; font-weight: bold;">\'</span>re pushing to'</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> op.<span style="color: black;">add_option</span><span style="color: black;">(</span><span style="color: #483d8b;">'-r'</span>,<span style="color: #483d8b;">'--ref'</span>, dest=<span style="color: #483d8b;">'ref'</span>, <span style="color: #008000;">help</span>=<span style="color: #483d8b;">'Refname that we<span style="color: #000099; font-weight: bold;">\'</span>re pushing'</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> opts, args = op.<span style="color: black;">parse_args</span><span style="color: black;">(</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">if</span> <span style="color: #ff7700;font-weight:bold;">not</span> opts.<span style="color: black;">address</span> <span style="color: #ff7700;font-weight:bold;">or</span> <span style="color: #ff7700;font-weight:bold;">not</span> opts.<span style="color: black;">oldrev</span> <span style="color: #ff7700;font-weight:bold;">or</span> <span style="color: #ff7700;font-weight:bold;">not</span> opts.<span style="color: black;">newrev</span> <span style="color: #ff7700;font-weight:bold;">or</span> <span style="color: #ff7700;font-weight:bold;">not</span> opts.<span style="color: black;">ref</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">'*** You left out some needed parameters! ***'</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> exit</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> mail_push<span style="color: black;">(</span>opts.<span style="color: black;">address</span>, opts.<span style="color: black;">oldrev</span>, opts.<span style="color: black;">newrev</span>, opts.<span style="color: black;">ref</span><span style="color: black;">)</span></div></li></ol></pre></div></div>
<p><br/></p>
<hr/>
<em>Did you know!</em> <a href="http://www.slide.com/static/jobs">Slide is hiring</a>! Looking for talented engineers to write some good Python and/or JavaScript, feel free to contact me at tyler[at]<a href="http://slide.com">slide</a></p>
http://unethicalblogger.com/posts/2008/11/git_integration_with_hudson_and_trac#commentsGitSlideSoftware DevelopmentMon, 24 Nov 2008 05:51:04 +0000R. Tyler Croy197 at http://unethicalblogger.comDelightfully Wrong About Git
http://unethicalblogger.com/posts/2008/11/delightfully_wrong_about_git
<p>A very long time ago I mentioned on <a href="http://twitter.com">Twitter</a> that I was looking at Git as a replacement for <a href="http://subversion.tigris.org/" target="_blank">Subversion</a> and <a href="http://perforce.com" target="_blank">Perforce</a> with my personal projects, but lamented moving to <a href="http://git.or.cz/" target="_blank">Git</a> at Slide would not be feasible<center><img src="http://agentdero.cachefly.net/unethicalblogger.com/images/git_tweet.png"/></center>Like most disagreements I've had with people on technology in the past, immediately after I said it, I actively tried to prove myself wrong. Back in April when I made the statement above, <a href="http://subversion.tigris.org/" target="_blank">Subversion</a> 1.4 was "good enough" (just barely) for what we wanted to do as far as source control, but I became more and more curious about whether or not we <em>could</em> move to Git. <center><img src="http://agentdero.cachefly.net/unethicalblogger.com/images/git_twitter_2.jpeg"/></center></p>
<p>Back in April, after spending a week with projects like <a href="http://wiki.darcs.net/DarcsWiki/Tailor" target="_blank">Tailor</a> and <a href="http://www.kernel.org/pub/software/scm/git/docs/git-svn.html" target="_blank">git-svn(1)</a> I started to look at the potential of moving just my team over to Git for evaluation purposes. By the end of May I had requested Git to be installed on the machines that we use for development on a day-to-day basis and we moved the team over to Git by the second week of June. </p>
<p>What followed were six months of sloshing uphill, some of the most notable milestones that we had to figure out in this time frame were:
<ul>
<li>Whereas in the Subversion architecture with a central repository there is a very clear development focal point for sharing code between developers, what is this in the <a href="http://git.or.cz/" target="_blank">Git</a> workflow?</li>
<li>How do you ensure developers don't forget code was committed "in that one branch, in that one repository" and keep track of code</li>
<li>How will Git integrate with <a href="https://hudson.dev.java.net/" target="_blank">Hudson</a>, <a href="http://trac.edgewall.org/" target="_blank">Trac</a> and our other pieces of development infrastructure? (<strong><em><a href="http://www.unethicalblogger.com/posts/2008/11/git_integration_with_hudson_and_trac" target="_blank">answered here</a></em></strong>)</li>
</ul>
<p> I'll be answering these questions and share some of the scripts, hooks, and documentation we've written internally to make moving to Git throughout the company a reality. I wish I could say I was responsible for it all, but there were a number of <a href="http://randomoblog.blogspot.com/" target="_blank">other</a> <a href="http://stuffonfire.com/" target="_blank">engineers</a> that were extremely important in defining best practices, and what this shiny new world without Subversion would look like.</p>
<p>At the end of the day, I'm pleased as punch with the transition. I don't hate <a href="http://subversion.tigris.org/" target="_blank">Subversion</a>, I just love <a href="http://git.or.cz/" target="_blank">Git</a>; call me "spoiled" but I think we deserve something more than a system that strives to be "a better <a href="http://i256.photobucket.com/albums/hh165/reddcloudd/failcat.jpg" target="_blank">CVS</a>".</p>
<p><strong>Update:</strong> I've posted an addendum: <a href="http://www.unethicalblogger.com/posts/2008/11/why_we_chose_git_a_rebuttal">Why we chose Git, a rebuttal</a></p>
<hr/>
<em>Did you know!</em> <a href="http://www.slide.com/static/jobs">Slide is hiring</a>! Looking for talented engineers to write some good Python and/or JavaScript, feel free to contact me at tyler[at]<a href="http://slide.com">slide</a></p>
http://unethicalblogger.com/posts/2008/11/delightfully_wrong_about_git#commentsGitSlideSoftware DevelopmentSun, 23 Nov 2008 02:51:50 +0000R. Tyler Croy196 at http://unethicalblogger.comReliable Locks in Hudson
http://unethicalblogger.com/posts/2008/11/reliable_locks_hudson
<p>There has been some amount of discussion on the Hudson user's list recently about the status of the "<a href="http://hudson.gotdns.com/wiki/display/HUDSON/Locks+and+Latches+plugin" target="_blank">Locks and Latches</a>" plugin. The plugin allows for one to create "locks" for Jobs in a similar manner to how "locks" work in a multithreaded programming environment. The need for such a plugin becomes very clear once you start to run multiple jobs that depend on some set of shared resources, take the following example:
<ul>
<li>Jobs A,B,C must run unit tests that fetch data from a test site</li>
<li>Slave #1 can only run one instance of Apache at a time</li>
</ul>
<p>How one would accomplish this with the Locks and Latches plugin would be to create a lock like "Site Lock" in the Hudson configuration, and then bind Jobs A, B, C to that Lock. Making the (large) assumption that the plugin works correctly and locks properly in order to prevent A and B from running concurrently, this would be enough to satisfy the requirements we have for the scenario above. Unfortunately it seems the plugin is largely unmaintained and buggy; in the past couple weeks of experimenting with such a set up on a variety of different slaves <a href="http://slide.com">we've</a> noticed that the locks aren't always respected, causing some locked jobs to execute in parallel spewing bad test results and build failures (the crux of this issue seems ot have been reported by Sergio Fernandes in <a href="https://hudson.dev.java.net/issues/show_bug.cgi?id=2450">#2450</a>).</p>
<p><big>The Loopback Slave</big><br />
The easiest way I found to work around the short-comings of the Locks and Latches plugin was to "break up" the Locks. Locks are only really useful if you have more than one "executor" on a Hudson node, in order to allow Hudson to execute jobs simultaneously. In essence, if you only have one executor, the Hudson queueing system will technically perform your "lock" for you by default. And thus the "loopback slave" was born! When explaining this to a co-worker, I likened my workaround to the fork(2) call, whereas the Locks and Latches plugin is much more of a pthread_mutex_lock(2) call. According to the "<a href="http://hudson.gotdns.com/wiki/display/HUDSON/Distributed+builds">Distributed Builds</a>" page on the Hudson wiki, you can start slave agent headlessly on <em>any</em> machine, so why not the master node?<br />
<div style="width: 680px; overflow: auto;"><img src="http://agentdero.cachefly.net/unethicalblogger.com/images/hudson_loopback_slave.jpeg"/></div>
<p>Above is the configuration of one such "loopback slave" that took the place of one of the executors on the master node.<center><img src="http://agentdero.cachefly.net/unethicalblogger.com/images/hudson_tie_to_node.jpeg"/></center>After setting up the loopback slave, it's just a matter of tying the Job to that node for building.</p>
<p>In short our set up was before: Jobs A, B, C all use the Lock "Site Job" in order to queue properly. With this change, now there is no lock, and Jobs A, B, C are all bound to the loopback slave in place of the lock on the master node. While certainly not ideal, given the frustrations of the Locks and Latches plugin going unmaintained this is the best short-term solution I've come up with thus far.<br/></p>
<hr/>
<em>Did you know!</em> <a href="http://www.slide.com/static/jobs">Slide is hiring</a>! Looking for talented engineers to write some good Python and/or JavaScript, feel free to contact me at tyler[at]<a href="http://slide.com">slide</a></p>
http://unethicalblogger.com/posts/2008/11/reliable_locks_hudson#commentsHudsonSlideThu, 06 Nov 2008 05:23:41 +0000R. Tyler Croy195 at http://unethicalblogger.comGit back into Subversion, Mostly Automagically (Part 3/3)
http://unethicalblogger.com/posts/2008/10/git_back_subversion_mostly_automagically_part_33
<p>Thus far I've covered most of the issues and hurdles we've addressed while experimenting with <a href="http://git.or.cz/" target="_blank">Git</a> at <a href="http://www.slide.com" target="_blank">Slide</a> in parts <a href="http://unethicalblogger.com/posts/2008/07/experimenting_with_git_slide_part_13" target="_blank"><strong>1</strong></a> and <a href="http://unethicalblogger.com/posts/2008/09/team_development_with_git_part_23" target="_blank"><strong>2</strong></a> of this series, the one thing I've not covered that is <strong>very</strong> important to address is how to work in the "hybrid" environment we currently have at Slide, where as one team works with Git and the rest of the company works in Subversion. Our setup involves having a "Git-to-Subverison" proxy repository such that everything to the "left" of that proxy repository is entirely Subversion without exceptions and everything to the "right" of that repository is entirely Git, without exceptions. Part of my original motivation for putting this post at the end of the series was that, when I originally wrote the first post on "<a href="http://unethicalblogger.com/posts/2008/07/experimenting_with_git_slide_part_13" target="_blank">Experimenting with Git at Slide</a>" I actually didn't have this part of the process figured out. That is to say, I was bringing code back and forth between Git and Subversion courtesy of <a href="http://www.kernel.org/pub/software/scm/git/docs/git-svn.html" target="_blank">git-svn(1)</a> and some gnarly manual processes. </p>
<h3>No Habla Branching</h3>
<p>The primary issue when bringing changesets from Git to Subversion is based in the major differences between how the two handle branching and changesets to begin with. In theory, projects like <a href="http://progetti.arstecnica.it/tailor" target="_blank">Tailor</a> were created to help solve this issue by first translating both the source and destination repositories into an intermediary changeset format in order to cross-apply changes from one end to the other. Unfortunately after I spent a couple days battling with Tailor, I couldn't get it to properly handle some of the revisions in Slide's three year history.<br />
<!--break--><br />
If you've ever used <strong>git-svn(1)</strong> you might be familiar with the <strong>git-svn dcommit</strong> command, which will work for some percentage of users that want to maintain dual repositories between Git and Subversion, things break down however once you introduce branching into the mix.<br />
<center><img src="http://agentdero.cachefly.net/unethicalblogger.com/images/Git-SVN%20Branching.png" border="1"/></center>Up until Subversion 1.5, Subversion had no concept of "merge tracking" (even in 1.5, it requires the server and client to be 1.5, it also makes nasty use of svn props). Without the general support for "merge tracking" the concept of a changeset sourcing from a particular branch or the concept of a "merge commit" are entirely foreign in the land of Subversion. In less mumbo jumbo, this effectively means that the "revisions" that you would want to bring from Git into Subversion need to be "<em>flattened</em>" when being "dcommitted" into Subversion's trunk. Git supports a means of flattening revision history when merging and pulling by way of the "--squash" command line argument, so this flattening for git-svn <em>is</em> possible.</p>
<h2>Giant Disclaimer</h2>
<p>What I'm about to write I dutifully accept as Git-heresy, a nasty hack and not something I'm proud of.</p>
<h3>Flattening into Subversion</h3>
<p>First the icky bash script that supports properly flattening revisions into the "master" branch in the git-svn repository and dcommits the results:<br />
<div style="height: 400px; overflow: scroll;"><div class="geshifilter"><pre class="geshifilter-bash"><ol><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #808080; font-style: italic;">#!/bin/bash</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #007800;">MERGE_BRANCH=</span>mergemaster</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #007800;">REPO=</span>$<span style="color: #000000;">1</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #007800;">BRANCH=</span>$<span style="color: #000000;">2</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #000000; font-weight: bold;">if</span> <span style="color: #7a0874; font-weight: bold;">[</span><span style="color: #7a0874; font-weight: bold;">[</span> -z <span style="color: #ff0000;">"${1}"</span> <span style="color: #000000; font-weight: bold;">||</span> -z <span style="color: #ff0000;">"${2}"</span> <span style="color: #7a0874; font-weight: bold;">]</span><span style="color: #7a0874; font-weight: bold;">]</span>; <span style="color: #000000; font-weight: bold;">then</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"===> You must provide a <span style="color: #000099; font-weight: bold;">\"</span>remote<span style="color: #000099; font-weight: bold;">\"</span> and a <span style="color: #000099; font-weight: bold;">\"</span>refspec<span style="color: #000099; font-weight: bold;">\"</span> for Git to use!"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"===> Exiting :("</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">exit</span> <span style="color: #000000;">1</span>;</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #000000; font-weight: bold;">fi</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #007800;">LATEST_COMMIT=</span>`git log --max-<span style="color: #007800;">count=</span><span style="color: #000000;">1</span> --no-merges --<span style="color: #007800;">pretty=</span>format:<span style="color: #ff0000;">"%H"</span>`</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #000000; font-weight: bold;">function</span> master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">{</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"==> Making sure we're on 'master'"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git checkout master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">}</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #000000; font-weight: bold;">function</span> setup_mergemaster </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">{</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"==> Killing the old mergemaster branch"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git branch -D <span style="color: #007800;">$MERGE_BRANCH</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"==> Creating a new mergemaster branch"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git checkout -b <span style="color: #007800;">$MERGE_BRANCH</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git checkout master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">}</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #000000; font-weight: bold;">function</span> cleanup</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">{</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #c20cb9; font-weight: bold;">rm</span> -f .git<span style="color: #000000; font-weight: bold;">/</span>SVNPULL_MSG</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">}</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #000000; font-weight: bold;">function</span> prepare_message</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">{</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"===> Pulling from ${REPO}:${BRANCH}"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git pull <span style="color: #007800;">$<span style="color: #7a0874; font-weight: bold;">{</span>REPO<span style="color: #7a0874; font-weight: bold;">}</span><span style="color: #000000; font-weight: bold;">|</span>> <span style="color: #007800;">$<span style="color: #7a0874; font-weight: bold;">{</span>BRANCH<span style="color: #7a0874; font-weight: bold;">}</span><span style="color: #000000; font-weight: bold;">|</span>></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git checkout <span style="color: #007800;">$<span style="color: #7a0874; font-weight: bold;">{</span>MERGE_BRANCH<span style="color: #7a0874; font-weight: bold;">}</span><span style="color: #000000; font-weight: bold;">|</span>></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"==> Merging across change from master to ${MERGE_BRANCH}"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git pull --no-commit --squash . master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #c20cb9; font-weight: bold;">cp</span> .git<span style="color: #000000; font-weight: bold;">/</span>SQUASH_MSG .git<span style="color: #000000; font-weight: bold;">/</span>SVNPULL_MSG</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">}</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #000000; font-weight: bold;">function</span> merge_to_svn</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">{</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git reset --hard <span style="color: #007800;">$<span style="color: #7a0874; font-weight: bold;">{</span>LATEST_COMMIT<span style="color: #7a0874; font-weight: bold;">}</span><span style="color: #000000; font-weight: bold;">|</span>></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> setup_mergemaster</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"===> Pulling from ${REPO}:${BRANCH}"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git pull <span style="color: #007800;">$<span style="color: #7a0874; font-weight: bold;">{</span>REPO<span style="color: #7a0874; font-weight: bold;">}</span><span style="color: #000000; font-weight: bold;">|</span>> <span style="color: #007800;">$<span style="color: #7a0874; font-weight: bold;">{</span>BRANCH<span style="color: #7a0874; font-weight: bold;">}</span><span style="color: #000000; font-weight: bold;">|</span>></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git checkout <span style="color: #007800;">$<span style="color: #7a0874; font-weight: bold;">{</span>MERGE_BRANCH<span style="color: #7a0874; font-weight: bold;">}</span><span style="color: #000000; font-weight: bold;">|</span>></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"==> Merging across change from master to ${MERGE_BRANCH}"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git pull --no-commit --squash . master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"==> Committing..."</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git commit -a -F .git<span style="color: #000000; font-weight: bold;">/</span>SVNPULL_MSG <span style="color: #000000; font-weight: bold;">&&</span> git-svn dcommit --no-rebase</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> cleanup</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">}</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">setup_mergemaster</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">prepare_message</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">merge_to_svn</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal">master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"===> All done!"</span></div></li></ol></pre></div></div>
<p>Gross isn't it? There were some interesting things I learned when experimenting with this script, but first I'll explain how the script is used. As I mentioned above there is the "proxy repository", this script operates on the git-svn driven proxy repository, meaning this script is only invoked when code needs to be propogated from Git-to-Subversion as opposed to Subversion-to-Git which git-svn properly supports by default in all cases. Since this is a proxy repository, that means all the "real" code and goings-on occur in the "primary" Subversion, and "primary" Git repositories, so the code is going along this path: Primary_SVN <-> [proxy] <-> Primary_Git<br />
This setup means when we "pull" (or merge) from Primary_Git/master we are going to be flattening at that point in order to properly merge it into the Primary_SVN. Without further ado, here's the breakdown on the pieces of the script:<br />
<div class="geshifilter"><pre class="geshifilter-bash"><ol><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #000000; font-weight: bold;">function</span> setup_mergemaster </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">{</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"==> Killing the old mergemaster branch"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git branch -D <span style="color: #007800;">$MERGE_BRANCH</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"==> Creating a new mergemaster branch"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git checkout -b <span style="color: #007800;">$MERGE_BRANCH</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git checkout master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">}</span></div></li></ol></pre></div>What the setup_mergemaster branch is responsible for is deleting any prior branches that have been used for merging into the proxy repository and Primary_SVN. It gives us a "mergemaster" branch in the git-svn repository that is effectively at the same chronological point in time as the master branch <em>before</em> any merging occurs.<br />
<div class="geshifilter"><pre class="geshifilter-bash"><ol><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #000000; font-weight: bold;">function</span> prepare_message</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">{</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"===> Pulling from ${REPO}:${BRANCH}"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git pull <span style="color: #007800;">$<span style="color: #7a0874; font-weight: bold;">{</span>REPO<span style="color: #7a0874; font-weight: bold;">}</span><span style="color: #000000; font-weight: bold;">|</span>> <span style="color: #007800;">$<span style="color: #7a0874; font-weight: bold;">{</span>BRANCH<span style="color: #7a0874; font-weight: bold;">}</span><span style="color: #000000; font-weight: bold;">|</span>></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git checkout <span style="color: #007800;">$<span style="color: #7a0874; font-weight: bold;">{</span>MERGE_BRANCH<span style="color: #7a0874; font-weight: bold;">}</span><span style="color: #000000; font-weight: bold;">|</span>></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"==> Merging across change from master to ${MERGE_BRANCH}"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git pull --no-commit --squash . master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #c20cb9; font-weight: bold;">cp</span> .git<span style="color: #000000; font-weight: bold;">/</span>SQUASH_MSG .git<span style="color: #000000; font-weight: bold;">/</span>SVNPULL_MSG</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">}</span></div></li></ol></pre></div>The prepare_message function is part of the nastiest code in the entire script, in order to get an accurate "squashed commit" commit message when the changesets are pushed into Primary_SVN, we have to generate the commit message separately from the actual merging. Since this function is performing a `git pull` from "master" into "mergemaster" the changesets that are being pulled are going to be the only ones that show up (for reasons I'm about to explain).<br />
<div class="geshifilter"><pre class="geshifilter-bash"><ol><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #000000; font-weight: bold;">function</span> merge_to_svn</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">{</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git reset --hard <span style="color: #007800;">$<span style="color: #7a0874; font-weight: bold;">{</span>LATEST_COMMIT<span style="color: #7a0874; font-weight: bold;">}</span><span style="color: #000000; font-weight: bold;">|</span>></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> setup_mergemaster</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"===> Pulling from ${REPO}:${BRANCH}"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git pull <span style="color: #007800;">$<span style="color: #7a0874; font-weight: bold;">{</span>REPO<span style="color: #7a0874; font-weight: bold;">}</span><span style="color: #000000; font-weight: bold;">|</span>> <span style="color: #007800;">$<span style="color: #7a0874; font-weight: bold;">{</span>BRANCH<span style="color: #7a0874; font-weight: bold;">}</span><span style="color: #000000; font-weight: bold;">|</span>></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git checkout <span style="color: #007800;">$<span style="color: #7a0874; font-weight: bold;">{</span>MERGE_BRANCH<span style="color: #7a0874; font-weight: bold;">}</span><span style="color: #000000; font-weight: bold;">|</span>></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"==> Merging across change from master to ${MERGE_BRANCH}"</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git pull --no-commit --squash . master</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">"==> Committing..."</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> git commit -a -F .git<span style="color: #000000; font-weight: bold;">/</span>SVNPULL_MSG <span style="color: #000000; font-weight: bold;">&&</span> git-svn dcommit --no-rebase</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> cleanup</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #7a0874; font-weight: bold;">}</span></div></li></ol></pre></div>If you noticed above in the full script block, the "LATEST_COMMIT" code, here's where it's used, it is one of the most important pieces of the entire script. Basically the LATEST_COMMIT piece of script grabs the latest non-merge-commit hash from the `git log` output and saves it for later use (here) where it's used to rollback the proxy repository to the point in time <em>just before</em> the last merge commit. This is done to avoid issues with git-svn(1) not understanding how to handle merge commits whatsoever. After rolling back the proxy repository, a new "mergemaster" branch is created. After the mergemaster branch is created, the actual Primary_Git changesets that differ between the proxy repository and Primary_Git are pulled into the proxy repository's master branch, and sqaushed into the mergemaster branch where they are subsequently committed with the commit message that was prepared before. The "prepare_message" part of the script becomes important at that step because the "squashed commit" message that Git generates at this point in time will effectively contain <strong>every</strong> commit that has ever been proxied across in this manner ever.</p>
<p>After the "merge_to_svn" function has been run the "transaction" is entirely completed and the changesets that once differed between Primary_SVN/trunk and Primary_Git/master are now normalized.</p>
<h3>Mostly Automagically</h3>
<p>In the near future I intend on incorporating this script into the post-receive hook on Primary_Git in such a way that will <strong>truly</strong> propogate changesets automatically from Primary_Git into Primary_SVN, but currently I'm utilizing one of my new favorite "hammers', <a href="https://hudson.dev.java.net/" target="_blank">Hudson</a> (see: <a href="http://unethicalblogger.com/posts/2008/08/oneline_automated_testing" target="_blank">One-line Automated Testing</a>). Currently there are two jobs set up for proxying changesets across, the first "Subversion-to-Git" simply polls Subversion for changes and executes a series of commands when changes come in: <strong>git-svn fetch && git merge git-svn && git push $Primary_Git master</strong>. This is fairly straight-forward and fits in line with what git-svn(1) is intended to do. The other job that I created is "Git-to-Subversion" which must be manually invoked by a user, but still will automatically take care of squashing commits into Primary_SVN/trunk (i.e. <strong>bash svnproxy.sh $Primary_Git master</strong>).</p>
<h3>Wrap-up</h3>
<p>Admittedly, this sort of setup leaves a lot to be desired. In the ideal world, Tailor would have coped with both our Git and our Subversion repositories in such a way that would have made this script nothing more than a silly idea I had on a bus. Unfortunately that wasn't case and the time budget I had for figuring out a way to force Tailor to work was about 47.5 hours less than it took me to sit down and write the script above. I'd be interested to see other solutions other organizations are utilizing to migrate from one system to the other, but at the time of this writing I can't honestly say I've heard much about people dealing with the "hybrid" scenario that we have currently at Slide.<br/></p>
<hr/>
<em>Did you know!</em> <a href="http://www.slide.com/static/jobs">Slide is hiring</a>! Looking for talented engineers to write some good Python and/or JavaScript, feel free to contact me at tyler[at]<a href="http://slide.com">slide</a></p>
http://unethicalblogger.com/posts/2008/10/git_back_subversion_mostly_automagically_part_33#commentsGitSlideSoftware DevelopmentThu, 02 Oct 2008 06:41:52 +0000R. Tyler Croy192 at http://unethicalblogger.comTeam Development with Git (Part 2/3)
http://unethicalblogger.com/posts/2008/09/team_development_with_git_part_23
<p>In my last post on Git, <a href="http://unethicalblogger.com/posts/2008/07/experimenting_with_git_slide_part_13" target="_blank">Experimenting with Git at Slide</a>, I discussed most of the technical hurdles that stood in our way with evaluating Git for a Subversion tree that has 90k+ revisions and over 2GB of data held within. As I've learned from any project that involves more than just myself, technology is only half the battle, the other half is the human element. One of the most difficult things to "migrate" when switching to something as critical to a developer's workflow as a <a href="http://en.wikipedia.org/wiki/Revision_control" target="_blank">VCS</a>, is habits, good ones and bad ones.</p>
<h3>The Bad Habits</h3>
<p>When moving my team over to Git, I was able to identify some habits that I view as "bad" that could either be blamed on how we have used Subversion here at Slide, or the development workflow that Subversion encourages. For the sake of avoiding flamewars, I'll say it's 51% us, 49% the system.<br />
<!--break--></p>
<ul>
<li><strong>The Occasional Committer</strong></li>
</ul>
<div style="margin: 25px; margin-bottom: 0px; margin-top: 0px">Chances are that if you're working on "something super important!" you fall into this bad habit. Because of the nature of <strong>trunk</strong> in Subversion, if you commit half-finished work into a team-branch or trunk itself, you could cause plenty of pain for your fellow developers. As a result, you tend to commit at the end of a long day working on something, or only after something has been completed. The 9 hours of sweat and frustration you've spent pounding away on 300 lines of code is now summed up in one commit message:</div>
<blockquote><p>Turns out there was a race-condition here, re #52516</p></blockquote>
<div style="margin: 25px; margin-top: 0px; margin-bottom: 0px;">Now three months from now when you return to the same 300 lines of code and try to figure out what the hell led to this mess, you're left with the commit message above, and nothing more.</div>
<ul>
<li><strong>The Less-than-attentive Developer</strong></li>
</ul>
<div style="margin: 25px; margin-top: 0px; margin-bottom: 0px;">I've worked on a Mac for the majority of my time at Slide, as do most of my compatriots, and sooner or later one of two things will happen:<span class="geshifilter"><code class="bash geshifilter-bash">svn add some<span style="color: #000000; font-weight: bold;">/</span>directory<span style="color: #000000; font-weight: bold;">/</span></code></span> and/or <span class="geshifilter"><code class="bash geshifilter-bash">svn commit</code></span>. This usually results in a second commit to come into the tree with a commit message like:</div>
<blockquote><p>Whoops, accidentally checked in resource forks</p></blockquote>
<div style="margin: 25px; margin-top: 0px; margin-bottom: 0px;">This isn't that large of a problem, except for the implication of the second command there, <span class="geshifilter"><code class="bash geshifilter-bash">svn commit</code></span> will commit <strong>all</strong> outstanding changes in your working copy starting in the current working directory, and recursing through children directories. I'm probably more anal-retentive about my commits than most, but I usually do a <strong>diff</strong> before I commit to make sure I'm aware of what I'm about to commit, but I've seen plenty of developers skip this step.</div>
<ul>
<li><strong>The Over-Confident Merger</strong></li>
</ul>
<div style="margin: 25px; margin-top: 0px; margin-bottom: 0px;">I've fallen into this trap numerous times when merging "old" branches back into trunk, especially with binary files that may have been changed in trunk, or in my branch (hell if I know!). One thing I can speak to anecdotally from our work at Slide, is that the probability of nonsensical conflicts rises with a branch's age. The rate of our repository progresses at about 50 commits to trunk per day (~150 commits across the board), if there is a branch cut from trunk, usually within two weeks it can become <strong>extremely difficult</strong> to merge back into trunk without constant "refreshes" or merges from trunk into the branch. </p>
<p>If you're not careful when folding that branch back down into trunk, you can inadvertantly revert old binary files or even text files to previous states which will usually cause other individuals in the engineering organization gripe at you and your QA department to pelt you with rocks. For bonus points, you could (as I have done before) accidentally commit conflicting files earning a gold star and a dunce hat for the day. This merging pain led me to originally write my <a href="http://unethicalblogger.com/posts/r_tyler_ballance/subversion_branching_with_less_pain" target="_blank">merge-safe.py</a> script so long ago. </div>
<h3>The Slide Way to Git</h3>
<p>Fortunately for us, I think the decentralized nature of Git has helped us enforce some best practices when it comes to the bad habits above. "The Occassional Committer" is all but done away with thanks to the ability to atomically commit and revert revisions at a whim and have those changes not propogated to other developers until there has been an <em>explicit</em> push or pull. </p>
<p>Unfortunately however, "The Less-than-attentive Developer" isn't solved so easily. To date I've sat next to two engineers that were new to Git, and watched them both execute the same fateful command: <span class="geshifilter"><code class="bash geshifilter-bash">git add .</code></span><br />
Not realizing their mistake, they accidentally commit a truckload of build and temporary files (.so, .swp, .pyc, etc) interspersed with their regular work that they meant to commit. Git cannot prevent a developer from shooting themselves in the foot, but it does prevent them from shooting everybody else in the foot along with it (unless they commit, and then push their changes upwards).</p>
<p>"The Over-confident Merger" grows more and more confident in the Git-based workflow. Since Git handles changesets atomically, it becomes trivial to merge branch histories together or cherry-pick one revision and apply to an entirely separate branch. I've not yet seen a Git conflict that wasn't a <strong>true</strong> conflict insofar that it was quite literally one line of code changing in two different ways between branch histories. As an aside, when using <strong>git-svn</strong>, be prepared for all the merging "fun" that Subversion has to offer when propogating changes between the two systems.</p>
<h4>Basic Team Workflow</h4>
<p><center><a href="http://agentdero.cachefly.net/unethicalblogger.com/images/basic_slide_workflow.png" rel="lightbox"><img src="http://agentdero.cachefly.net/unethicalblogger.com/images/basic_slide_workflow.png" width="470"/></a></center>The manner in which we use Git is more like a centralized-decentralized version control system. We still have a "master" repository, which provides a central synchronization point when pushing stage servers, or when bringing code into Subversion to be pushed to live servers. For any particular project one of the developers will create a branch that will serve as the primary project branch, take the "superpoke-proj" branch as an example. That developer will push this branch to "origin" (or the master repository) such that other developers can "track" that branch and contribute code. For the purposes of this example, let's say Paul and Peter are working in "superpoke-proj", while Paul is working he will incrementally commit his work, but once he has resolved/fixed a ticket, he will perform a <span class="geshifilter"><code class="bash geshifilter-bash">git push</code></span> and then mark the ticket appropriately such that a QA engineer can verify the fix. If Paul and Peter are working on something that "breaks the build" but they need to collaborate on it together, Paul can perform a <span class="geshifilter"><code class="bash geshifilter-bash">git pull</code></span> from Peter and vice versa, and again, once they're done those changes will be <em>pushed</em> to origin. This model allows for developers to work in relative isolation so they're not inadvertantly stepping on each others' toes, but also close enough that they can collaborate in explicit terms, i.e. when they are ready for changes to be propogated to each other or the rest of the team.</p>
<h3>Conclusion</h3>
<p>Our workflow, like most things at companies under 500 employees is still a "work in progress™". I think we've found the right balance thus far for the team between freedom and process the allow for sufficient mucking around in the codebase in a way that provides the most amount of time actually writing code with as little possible time spent dealing with the overhead of anything else (merging, etc). There's nothing inherently special in the way we use Git, but we've found that it works for the way we work, which is to say in a very tight release schedule that's requires multiple branches per week and plenty of merging from branch-to-branch whether it be from another team or another part of the same team.</p>
<p>Of course, your mileage may vary.<br/></p>
<hr/>
<em>Did you know!</em> <a href="http://www.slide.com/static/jobs">Slide is hiring</a>! Looking for talented engineers to write some good Python and/or JavaScript, feel free to contact me at tyler[at]<a href="http://slide.com">slide</a></p>
http://unethicalblogger.com/posts/2008/09/team_development_with_git_part_23#commentsGitSlideSoftware DevelopmentTue, 30 Sep 2008 06:22:02 +0000R. Tyler Croy191 at http://unethicalblogger.comDon Quixote's new side-kick, Hudson
http://unethicalblogger.com/posts/2008/09/don_quixotes_new_sidekick_hudson
<p>I recently wrote about "<a href="http://www.unethicalblogger.com/posts/2008/08/oneline_automated_testing" target"_blank">one-line automated testing</a>" by way of <a href="https://hudson.dev.java.net/"><strong>Hudson</strong></a>, a Java-based tool that helps to automate building and test processes (akin to Cruise Control and Buildbot). If you were to read this blog regularly, you'd be well aware that I work primarily with Python these days, at a <a href="http://www.slide.com" target="_blank">web company</a> no less! What does a web company need with a continuous integration tool? Especially if they're not using a compiled language like Java or C# (heresy!). </p>
<p>As any engineering organization grows, it's bound to happen that you reach a critical mass of developers and either need to hire an equitable critical mass of QA engineers, or start to approach quality assurance from all sides. That is to say, automated unit testing and automated integration testing becomes a requirement for growing both as a engineering organization but as a web application provider (user's don't like broken web applications). With web products like <a href="http://www.topfriends.com" target="_blank">Top Friends</a>, <a href="http://www.superpoke.com" target="_blank">SuperPoke!</a> and <a href="http://apps.facebook.com/crazyfunpix/" target="_blank">Slide FunSpace</a> we have a large amount of ever-changing code, that has been in a constant state of flux for the past 16-18 months. We can accomodate for ever-changing code on the backend for the past year and half with <a href="http://pyunit.sourceforge.net/" target="_blank">PyUnit</a> and development discipline. </p>
<p>How do you deal with months of ever changing code for the aforementinoned products' front-ends? Your options are pretty slim, you can hire a legion of black-box QA engineers to manually go through regression tests and ensure your products are in tip-top shape, or you can hire a few talented black-box QA engineers to conscript a legion of robots to go through regression tests and ensure your products are in tip-top shape. Enter <a href="http://www.getwindmill.com" target="_blank"><strong><big>Windmill</big></strong></a>. Windmill is a web browser testing framework not entirely unlike <a href="http://selenium.openqa.org/" target="_blank">Selenium</a> or <a href="http://wtr.rubyforge.org/" target="_blank">Watir</a> with two major exceptions: Windmill is written in Python and Windmill has a great <strong>recorder</strong> (and lots of other <a href="http://www.getwindmill.com/features">features</a>). One of my colleagues at Slide, <a href="http://adamchristian.com/" target="_blank">Adam Christian</a> has been working tirelessly to push Windmill further and prepare it for enterprise adoption, the first enterprise to use it, Slide.</p>
<p>Adam and I have been working on bringing the two ends of the testing world together with Hudson. About half of the jobs currently running inside of our Hudson installation are running PyUnit tests on various Subversion and Git branches. The other half of the jobs are running Windmill tests, and reporting back into Hudson by way of Adam's <a href="http://www.getwindmill.com/archives/207" target="_blank">JUnit-compatible reporting</a> code. Thanks to the innate flexibility of PyUnit and Windmill's reporting infrastructure we were able to tie all these loose ends together with a tool like <a href="https://hudson.dev.java.net/" target="_blank">Hudson</a> that will handle Jabber-notifications or email notifications when test-runs fail and include details in it's reports.</p>
<p>We're still working out the kinks in the system, but to date this set up has helped us fix at least one critical issue a week (with a numerous other minor issues) since we've launched the Hudson system, more often than not before said issues reach the live site and real users. If you've got questions about Windmill or Hudson you can stop by the <strong>#windmill</strong> or the <strong>#hudson</strong> channels on <a href="http://freenode.net/" target="_blank">Freenode</a>.</p>
<p>Automated testing is like a really good blend of coffee, until you have it, you think "bah! I don't need that!" but after you start with it you can't help but wonder how you could tolerate the swill you used to drink.<br/></p>
<hr/>
<em>Did you know!</em> <a href="http://www.slide.com/static/jobs">Slide is hiring</a>! Looking for talented engineers to write some good Python and/or JavaScript, feel free to contact me at tyler[at]<a href="http://slide.com">slide</a></p>
http://unethicalblogger.com/posts/2008/09/don_quixotes_new_sidekick_hudson#commentsHudsonSlideSoftware DevelopmentSun, 07 Sep 2008 06:04:53 +0000R. Tyler Croy185 at http://unethicalblogger.comOne-line Automated Testing
http://unethicalblogger.com/posts/2008/08/oneline_automated_testing
<p>For about as long as my development team has been a number larger than one, I've been on a relatively steady "unit test" kick. With the product I've worked on for over a year gaining more than one cook in the kitchen, it became time to start both writing tests to prevent basic regressions (and save our QA team tedious hours of blackbox testing), but also to automate those tests in order to quickly spot issues.</p>
<p>While I've been on this pretty steadily lately, I'm proud to say that automated testing was one of my first pet projects at <a href="http://www.slide.com" target="_blank">Slide</a>. If you ever crack into the Slide corporate network you can find my workstation under the name "ccnet" which is short for <a href="http://ccnet.thoughtworks.com/" target="_blank">Cruise Control.NET</a>, my first failed attempt at getting automated testing going on our now defunct Windows desktop client. As our development focus shifted away from desktop applications to social applications the ability to reliably test those systems plummeted; accordingly our test suite for these applications became paltry at best. As the organization started to scale, this simply could not stand much longer else we might not be able to efficiently push stable releases on a near-nightly schedule. As we've started to back-fill tests (test-after development?) the need to automate these tests has arisen to which I started digging aronud for something less painful to deal with than <a href="http://cruisecontrol.sourceforge.net/" target="_blank">Cruise Control</a>, enter <a href="https://hudson.dev.java.net/" target="_blank"><strong>Hudson</strong></a>.</p>
<h3>Holy Hudson Batman!</h3>
<p>I was absolutely astounded that I, nor anybody I knew, was aware of the Hudson project. Hudson is absolutely amazing as far as continuous integration systems go. The only major caveat is that the entire system is written in Java, meaning I had to beg one of our sysadmins to install Java 1.5 on the unit test machine. Once that was sorted out, starting the Hudson instance up was incredibly simple:<br />
<span class="geshifilter"><code class="python geshifilter-python">java -jar hudson.<span style="color: black;">war</span></code></span><br />
In our case the following to keep the JVM within manageable virtual memory limits:<br />
<span class="geshifilter"><code class="python geshifilter-python">java -Xmx128m -jar hudson.<span style="color: black;">war</span> --httpPort=<span style="color: #ff4500;">8888</span></code></span></p>
<p>Once the Hudson instance was up and rnuning, I simply had to browse to <strong>http://unittestbox:8888/</strong> and the entire rest of the configuration was set up from the web UI. Muy easy. Muy bueno.</p>
<h3>Plug-it-in, plug-it-in!</h3>
<p>One of the most wonderful aspects of Hudson is it's extensible plugin architecture. Adding plugins like "Git", "Trac" and "Jabber" means that our Hudson instance is now properly linking to Trac revisions, sending out Jabber notifications on "build" (read: test run) failures and monitoring both Subversion and Git branches for changes. From what I've seen from their plugin architecture, it would be absolutely trivial to extend Hudson with Slide-specific plugins as the needs arise.</p>
<p>With the integration of the PyUnit XMLTestRunner (<a href="http://www.rittau.org/python/cruisecontrol/" target="_blank">found here</a>) and working an XML output plugin into <a href="http://windmill.osafoundation.org/" target="_blank">Windmill</a> we can easily automate testing of both our back-end code and our front-end.</p>
<p><a href="http://agentdero.cachefly.net/unethicalblogger.com/images/slidehudson.jpeg" rel="lightbox"><img src="http://agentdero.cachefly.net/unethicalblogger.com/images/slidehudson.jpeg" width="500"/></a><br/><strong>Hudson in action</strong></p>
<p>And all with one simple java command :)<br/></p>
<hr/>
<em>Did you know!</em> <a href="http://www.slide.com/static/jobs">Slide is hiring</a>! Looking for talented engineers to write some good Python and/or JavaScript, feel free to contact me at tyler[at]<a href="http://slide.com">slide</a></p>
http://unethicalblogger.com/posts/2008/08/oneline_automated_testing#commentsHudsonOpinionSlideSoftware DevelopmentWed, 20 Aug 2008 08:16:26 +0000R. Tyler Croy184 at http://unethicalblogger.comExperimenting with Git at Slide (Part 1/3)
http://unethicalblogger.com/posts/2008/07/experimenting_with_git_slide_part_13
<p>For the past two months I've been experimenting with varying levels of success with <a href="http://git.or.cz/" target="_blank">Git</a> inside of <a href="http://www.slide.com" target="_blank">Slide, Inc.</a>. Currently Slide makes use of <a href="http://subversion.tigris.org/" target="_blank">Subversion</a> and relies heavily on branches in Subversion for everything from project specific branches to release branches (branches that can live anywhere from under 12 hours to three weeks). There are plenty of other blog posts about the pitfalls of branching in Subversion that I won't go into here, suffice to say, it is...sub-par. Below is a rough diagram of our general current workflow with Subversion (I've had some other developers ask me "why don't you just work in trunk?" to which I usually wax poetic about the chaos of trunk when any project gets over 5 active developers (Slide engineering is somewhere between 30-50 engineers)).<br />
<center><a href="http://agentdero.cachefly.net/unethicalblogger.com/images/Subversion%20Workflow.png" rel="lightbox"><img src="http://agentdero.cachefly.net/unethicalblogger.com/images/Subversion%20Workflow.png" width="460"/></a></center></p>
<ul>
<li><a href="#catch">There's always a catch</a></li>
<li><a href="#svn">Subversion at Slide</a></li>
<li><a href="#toying">Toying with Git</a></li>
<li><a href="#git">Git at Slide</a></li>
</ul>
<p><a name="catch"/></p>
<h3>There's always a catch</h3>
<p>There are three major problems we've run up against with utilizing Subversion as our version control system at Slide:
<ul>
<li>Subversion's "branches" make context switching difficult</li>
<li>Depending on the age of a branch cut from <span style="font-family: monospace;">trunk/</span>, merges and maintainence is between difficult and impossible</li>
<li>Merging Subversion branches into each other causes a near total loss of revision history</li>
</ul>
<p>Given that branches are a critical part of Slide's development process, we've historically looked at branch-strong version control systems as alternatives, such as <a href="http://perforce.com" target="_blank">Perforce</a>. Before I joined Slide in April of 2007, I was a heavy user of Perforce for my own consulting projects as well as for some of my work with the <a href="http://FreeBSD.org" target="_blank">FreeBSD project</a> as part of the Summer of Code program. In fact, my <a href="http://stuffonfire.com/" target="_blank">boss</a> sent out a "Perforce Petition" to our engineering list on my third day at Slide...we still haven't switched away from Perforce. </p>
<p><center><a href="http://agentdero.cachefly.net/unethicalblogger.com/images/git_tweet.png" rel="lightbox"><img src="http://agentdero.cachefly.net/unethicalblogger.com/images/git_tweet.png" width="500"/></a></center></p>
<p>Up until earlier this year I hadn't given it a second thought until the team I was working with grew and grew such that between me and four other engineers we were pushing a release anywhere from once to three times a week. That meant we were creating a Subversion "branch" multiple times a week, and a significant part of my daily routine became merging to our release branch and refreshing project branches from <span style="font-family: monospace;">trunk/</span>. All of a sudden Git was looking prettier and prettier, despite some of its warts. At this point in time I was already using Git for some of my personal projects that I never have time for, so I knew at the bare minimum that it was functional. What I didn't know was how to deploy and use it with a large engineering team that works in very high churn short iterations, like Slide's.<br />
<a name="svn"/></p>
<h3>Subversion at Slide</h3>
<p>Moving our source tree over into a system other than Subversion, from Subverison, was destined to be painful. The tree at Slide is deceptively large, we have a substantial amount of <a href="http://www.python.org" target="_blank">Python</a> running around (as Slide is built, top-to-bottom, in Python) and an incredible amount of Adobe Flash assets (.swf files), Adobe Illustrator assets (.ai files) and plenty of binary files, like images (.png/gif/jpeg). Currently a full checkout of <span style="font-family: monospace;">trunk/</span> is roughly <strong>2.5GB</strong> including artwork, flash, server and web application code. We also have roughly <strong>88k</strong> revisions in Subversion, the summation of three years of the company's existence. Fortunately somebody along the line wrote a script (in Perl however) called "<a href="http://www.kernel.org/pub/software/scm/git/docs/git-svn.html" target="_blank">git-svn(1)</a>" that is designed to do exactly what I needed, move a giant tree from Subversion to Git, from start to finish (similar to <a href="http://public.perforce.com/wiki/SVN2P4" target="_blank">svn2p4</a> in Perforce parlance).</p>
<p><a name="toying"></p>
<h3>Toying with Git</h3>
<p>When I first ran the command `<span style="font-family: monospace;">git-svn init $SVN</span>` I let the the command run for somewhere close to 6-7 hours before completing, I was shocked at the size of the generated repository. If our Git repository were to be left unpacked <span style="font-family: monospace;">.git/</span> <strong>alone</strong> would be close to <strong>9GB</strong>, adding the actual code on top of it, ~11GB. I decided that maybe packing this repository would be a good idea so I ran `<span style="font-family: monospace;">git gc</span>` and went to grab a coke from the fridge ... and the machine ran out of memory. One of our quad-core, 8GB RAM, shared development machines ran out of memory?!<br />
<center><img src="http://agentdero.cachefly.net/unethicalblogger.com/images/do-not-want.jpg"/></center></p>
<p>After lurking in #git on Freenode for a while I determined two things
<ol>
<li>Apparently nobody uses Git for projects this large</li>
<li>Git was retaining too much memory, like a memory leak, but just don't call it a memory leak.</li>
</ol>
<p> To compound this, the rules for memory usage with Git are vastly different between a 32-bit machine and a 64-bit machine, and because we're just that cool, we're using 64-bit machines across the board. The amount of memory Git decides to keep resident while doing repository-intensive operations like the `<span style="font-family: monospace;">git gc</span>`, is 256MB on 32-bit machines, and 8GB on 64-bit machines. As these machines are shared between developers we use of <span style="font-family: monospace;">ulimit(1)</span>, when you limit memory usage with <span style="font-family: monospace;">ulimit(1)</span> it restricts <em>address space</em> meaning both virtual and resident memory. When Git tried to <span style="font-family: monospace;">mmap(2)</span> gigabytes of address space to do it's operations, the kernel stepped in to intervene and started returning <span style="font-family: monospace;">ENOMEM</span> to Git which promptly exited.</p>
<p>After raising this enough times, I finally caught <a href="http://www.spearce.org/" target="_blank">spearce</a> who was able to identify the problem and supply a <a href="http://marc.info/?l=git&m=121558741815427&w=2" target="_blank">patch</a> that fixed the memory allocation issues with Git and a repository of Slide's size. First obstacle overcome, now I could actually test a Git workflow inside of Slide.</p>
<p><a name="git"></p>
<h3>Git at Slide</h3>
<p>Now that I could pack the repository on our development machines, I could get the repository down to a reasonable 3.0GB, i.e. <span style="font-family: monospace;">.git/</span> weighed in at 3GB making a entire tree ~5.5GB (far more managable than 11GB). Despite Git being a decentralized version control system, we still needed some semblance of centralization to ensure a couple basic rules for a sane workflow:
<ul>
<li>A centralize place to synchronize distributed versions of the repository</li>
<li>Changesets cannot be lost, ever.</li>
<li>QA must not be over-burdened when testing releases</li>
</ul>
<p>This meant we needed a centralized, secure, repository which left us two options: Git over WebDav (https/http) <em>or</em> <a href="http://scie.nti.st/2007/11/14/hosting-git-repositories-the-easy-and-secure-way" target="_blank">Gitosis</a>. After discovering that `<span style="font-family: monospace;">git-http-push(1)</span>, the executable responsible for doing Git pushes over WebDav has tremendous memory issues, I abandoned that as an option (a `<span style="font-family: monospace;">git push</span>` of the repository resulted in memory usage peaking at 11GB virtual, 3.5GB resident memory).</p>
<p>If you are looking to deploy Git for a larger audience in a corporate environment, I highly recommend Gitosis. What Gitosis does is allows for SSH to be used as the transport protocol for Git, and provides authentication by use of limited-shell user accounts and SSH keys; it's not perfect but it's the closest thing to maintainable for larger installations of Git (in my opinion). </p>
<p>So far the experimenting with Git at Slide is pretty localized to just my team, but with a combination of Gitosis, git-svn(1) and some "best practices" defined for handling the new system we've successfully continued development for over the past month without any major issues. </p>
<p>As this post is already quite lengthy, I'll be discussing the following two parts of our experimenting in subsequent posts:
<ul>
<li><a href="http://unethicalblogger.com/posts/2008/09/team_development_with_git_part_23">Team Development with Git</a></li>
<li>Git back to Subversion, mostly automatically.</li>
</ul>
<p><br/></p>
<hr/>
<em>Did you know!</em> <a href="http://www.slide.com/static/jobs">Slide is hiring</a>! Looking for talented engineers to write some good Python and/or JavaScript, feel free to contact me at tyler[at]<a href="http://slide.com">slide</a></p>
http://unethicalblogger.com/posts/2008/07/experimenting_with_git_slide_part_13#commentsGitOpinionSlideSoftware DevelopmentSun, 27 Jul 2008 12:23:48 +0000R. Tyler Croy182 at http://unethicalblogger.comParsing HTML with Python
http://unethicalblogger.com/posts/2008/05/parsing_html_python
<p>A while ago I jotted down about seven or so ideas of stuff that I thought would make good blog posts, somehow "markup parsers in Python" is next on the list, so I might as well spill the beans on how incredibly easy it is to process (X)HTML with Python and a little built in class called <strong><a href="http://docs.python.org/lib/module-HTMLParser.html" target="_blank">HTMLParser</a></strong>.</p>
<p>There have been a few occasions when I needed a quick (and dirty) way to perform transforms on some chunk of HTML or merely "search and replace" parts of it. While it might be cleaner to do something with <a href="http://en.wikipedia.org/wiki/XSLT" target="_blank">XSLT</a> or the likes, using them doesn't even begin to match the speed of development of an HTMLParser-based class in Python. </p>
<p><strong><big>Getting Started</big></strong><br />
One major thing to keep in mind when working with HTMLParser, especially if you're newer to Python, is that it is what's referred to as an "old styled" object, meaning subclassing it is a bit different than "new styled" classes. Since HTMLParser is an old-styled object, any time you'd want to call a super-class defined method you would need to perform <span class="geshifilter"><code class="python geshifilter-python"><span style="color: #dc143c;">HTMLParser</span>.<span style="color: black;">superMethod</span><span style="color: black;">(</span>arg<span style="color: black;">)</span></code></span> instead of <span class="geshifilter"><code class="python geshifilter-python"><span style="color: #008000;">super</span><span style="color: black;">(</span>SubHTMLParser, <span style="color: #008000;">self</span><span style="color: black;">)</span>.<span style="color: black;">superMethod</span><span style="color: black;">(</span>arg<span style="color: black;">)</span></code></span><br />
<br/><br />
<strong><big>Creating the HTML parser</big></strong><br />
For the purposes of this example, I want something simple, so we're just going to take a block of markup and "tweak" all the <a> tags within it to be "sad" (whereas "sad" means they'll be bold, blue, and blinkey). The actual code to do so is only 50 lines long and is as follows: <div class="geshifilter"><pre class="geshifilter-python"><ol><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">import</span> <span style="color: #dc143c;">HTMLParser</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"><span style="color: #ff7700;font-weight:bold;">class</span> SadHTML<span style="color: black;">(</span><span style="color: #dc143c;">HTMLParser</span>.<span style="color: #dc143c;">HTMLParser</span><span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #483d8b;">''</span><span style="color: #483d8b;">'A simple HTML transform-class based upon HTMLParser. All links shall be bold, blue and blinky :('</span><span style="color: #483d8b;">''</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">def</span> <span style="color: #0000cd;">__init__</span><span style="color: black;">(</span><span style="color: #008000;">self</span>, <span style="color: #66cc66;">*</span>args, <span style="color: #66cc66;">**</span>kwargs<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #dc143c;">HTMLParser</span>.<span style="color: #dc143c;">HTMLParser</span>.<span style="color: #0000cd;">__init__</span><span style="color: black;">(</span><span style="color: #008000;">self</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #008000;">self</span>.<span style="color: black;">stack</span> = <span style="color: black;">[</span><span style="color: black;">]</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">def</span> handle_starttag<span style="color: black;">(</span><span style="color: #008000;">self</span>, tag, attrs<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> attrs = <span style="color: #008000;">dict</span><span style="color: black;">(</span>attrs<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">if</span> tag.<span style="color: black;">lower</span><span style="color: black;">(</span><span style="color: black;">)</span> == <span style="color: #483d8b;">'a'</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #008000;">self</span>.<span style="color: black;">stack</span>.<span style="color: black;">append</span><span style="color: black;">(</span><span style="color: #008000;">self</span>.__html_start_tag<span style="color: black;">(</span><span style="color: #483d8b;">'blink'</span>, <span style="color: #008000;">None</span><span style="color: black;">)</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> attrs<span style="color: black;">[</span><span style="color: #483d8b;">'style'</span><span style="color: black;">]</span> = <span style="color: #483d8b;">'%s%s'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span>attrs.<span style="color: black;">get</span><span style="color: black;">(</span><span style="color: #483d8b;">'style'</span>, <span style="color: #483d8b;">''</span><span style="color: black;">)</span>, <span style="color: #483d8b;">'color: blue; font-weight: bold;'</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #008000;">self</span>.<span style="color: black;">stack</span>.<span style="color: black;">append</span><span style="color: black;">(</span><span style="color: #008000;">self</span>.__html_start_tag<span style="color: black;">(</span>tag, attrs<span style="color: black;">)</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">def</span> handle_endtag<span style="color: black;">(</span><span style="color: #008000;">self</span>, tag<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #008000;">self</span>.<span style="color: black;">stack</span>.<span style="color: black;">append</span><span style="color: black;">(</span><span style="color: #008000;">self</span>.__html_end_tag<span style="color: black;">(</span>tag<span style="color: black;">)</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">if</span> tag.<span style="color: black;">lower</span><span style="color: black;">(</span><span style="color: black;">)</span> == <span style="color: #483d8b;">'a'</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #008000;">self</span>.<span style="color: black;">stack</span>.<span style="color: black;">append</span><span style="color: black;">(</span><span style="color: #008000;">self</span>.__html_end_tag<span style="color: black;">(</span><span style="color: #483d8b;">'blink'</span><span style="color: black;">)</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">def</span> handle_startendtag<span style="color: black;">(</span><span style="color: #008000;">self</span>, tag, attrs<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #008000;">self</span>.<span style="color: black;">stack</span>.<span style="color: black;">append</span><span style="color: black;">(</span><span style="color: #008000;">self</span>.__html_startend_tag<span style="color: black;">(</span>tag, attrs<span style="color: black;">)</span><span style="color: black;">)</span> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">def</span> handle_data<span style="color: black;">(</span><span style="color: #008000;">self</span>, data<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #008000;">self</span>.<span style="color: black;">stack</span>.<span style="color: black;">append</span><span style="color: black;">(</span>data<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">def</span> __html_start_tag<span style="color: black;">(</span><span style="color: #008000;">self</span>, tag, attrs<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #483d8b;">'<%s%s>'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span>tag, <span style="color: #008000;">self</span>.__html_attrs<span style="color: black;">(</span>attrs<span style="color: black;">)</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">def</span> __html_startend_tag<span style="color: black;">(</span><span style="color: #008000;">self</span>, tag, attrs<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #483d8b;">'<%s%s/>'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span>tag, <span style="color: #008000;">self</span>.__html_attrs<span style="color: black;">(</span>attrs<span style="color: black;">)</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">def</span> __html_end_tag<span style="color: black;">(</span><span style="color: #008000;">self</span>, tag<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #483d8b;">'</%s>'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span>tag<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">def</span> __html_attrs<span style="color: black;">(</span><span style="color: #008000;">self</span>, attrs<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> _attrs = <span style="color: #483d8b;">''</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">if</span> attrs:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> _attrs = <span style="color: #483d8b;">' %s'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span><span style="color: #483d8b;">' '</span>.<span style="color: black;">join</span><span style="color: black;">(</span><span style="color: black;">[</span><span style="color: black;">(</span><span style="color: #483d8b;">'%s="%s"'</span> <span style="color: #66cc66;">%</span> <span style="color: black;">(</span>k,v<span style="color: black;">)</span><span style="color: black;">)</span> <span style="color: #ff7700;font-weight:bold;">for</span> k,v <span style="color: #ff7700;font-weight:bold;">in</span> attrs.<span style="color: black;">iteritems</span><span style="color: black;">(</span><span style="color: black;">)</span><span style="color: black;">]</span><span style="color: black;">)</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">return</span> _attrs</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> </div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> @<span style="color: #008000;">classmethod</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">def</span> depreshun<span style="color: black;">(</span>cls, markup<span style="color: black;">)</span>:</div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> _p = cls<span style="color: black;">(</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> _p.<span style="color: black;">feed</span><span style="color: black;">(</span>markup<span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> _p.<span style="color: black;">close</span><span style="color: black;">(</span><span style="color: black;">)</span></div></li><li style="font-family: monospace; font-weight: normal;"><div style="font-family: monospace; font-weight: normal; font-style: normal"> <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #483d8b;">''</span>.<span style="color: black;">join</span><span style="color: black;">(</span>_p.<span style="color: black;">stack</span><span style="color: black;">)</span></div></li></ol></pre></div><br />
<br/>The actual ins-and-outs of the parser are very simple; markup like "<strong><a href="#">Hello</a><br/></strong>" would execute accordingly:
<ul>
<li><span class="geshifilter"><code class="python geshifilter-python">handle_starttag<span style="color: black;">(</span><span style="color: #483d8b;">'a'</span>, <span style="color: black;">[</span><span style="color: black;">(</span><span style="color: #483d8b;">'href'</span>, <span style="color: #483d8b;">'#'</span><span style="color: black;">)</span><span style="color: black;">]</span><span style="color: black;">)</span></code></span></li>
<li><span class="geshifilter"><code class="python geshifilter-python">handle_data<span style="color: black;">(</span><span style="color: #483d8b;">'Hello'</span><span style="color: black;">)</span></code></span></li>
<li><span class="geshifilter"><code class="python geshifilter-python">handle_endtag<span style="color: black;">(</span><span style="color: #483d8b;">'a'</span><span style="color: black;">)</span></code></span></li>
<li><span class="geshifilter"><code class="python geshifilter-python">handle_startendtag<span style="color: black;">(</span><span style="color: #483d8b;">'br'</span>, <span style="color: black;">[</span><span style="color: black;">]</span><span style="color: black;">)</span></code></span></li>
</ul>
<p>Since HTMLParser just gives you element tag names, and there attributes, SadHTML simply builds a list of strings out of what data is passed to it via the super class and then when everything is finished, ties the list back together with: <span class="geshifilter"><code class="python geshifilter-python"><span style="color: #483d8b;">''</span>.<span style="color: black;">join</span><span style="color: black;">(</span>list_of_tags<span style="color: black;">)</span></code></span>.<br />
Executing the SadHTML.depreshun method on the contents of my last blog post is a good example, part of the post was:</p>
<blockquote><p>An informal poll at the <a href="http://www.slide.com">Slide</a> offices this past week yielded these interesting results: at Slide.com, nearly 100% of white people seem to like <a href="http://stuffwhitepeoplelike.wordpress.com">"Stuff White People Like"</a>.</p></blockquote>
<p>After running it through "SadHTML", the following markup is generated instead:</p>
<blockquote><p>An informal poll at the <blink><a style="color: blue; font-weight: bold;" href="http://www.slide.com">Slide</a></blink> offices this past week yielded these interesting results: at Slide.com, nearly 100% of white people seem to like <blink><a style="color: blue; font-weight: bold;" href="http://stuffwhitepeoplelike.wordpress.com">"Stuff White People Like"</a></blink>.</p></blockquote>
<p>If you're curious as to how much more you can do with <a href="http://docs.python.org/lib/module-HTMLParser.html">HTMLParser</a>, do check out the documentation. It's far more lenient than using eXpat for parsing HTML, and it's still fast enough to be used on longer documents (there's also <a href="http://docs.python.org/lib/module-htmllib.html">htmllib</a> available for Python but I've not used it yet).</p>
http://unethicalblogger.com/posts/2008/05/parsing_html_python#commentsMiscellaneousSlideSoftware DevelopmentSun, 04 May 2008 06:54:43 +0000R. Tyler Croy180 at http://unethicalblogger.com