How We Built an Awesome Git Client for Windows
Table of Contents
It is a truth universally acknowledged, that software development is full of surprises. Starting out on the implementation of an innocent-seeming feature, you may soon find yourself en route to someplace different entirely, and so it went with Tower 3 for Windows. Released in November 2021, Tower 3 amounted to a complete overhaul of our Git client for Windows, but that's not how it started out. In this article, we take an in-depth look at the development of Tower 3, and how our team works on high-quality native apps for two different platforms.
We're lucky to have users who care enough about our app to let us know when things could be working better. It was clear to us that Tower 2 had room for improvement, and we were eager to bring the app up to the highest standard. There was work to be done on the performance and stability front and Nora, who handles our support, was certainly seeing some issues related to these areas. Most users who got in touch, however, did so to ask for a specific feature: dark mode. Explaining how the implementation of this feature led to Tower 3 for Windows requires a little detour through the world of user interface frameworks.
Through the Looking-Glass
"If once you start down the dark path, forever will it dominate your destiny."
Tower 3 for Windows, as Tower 2 before it, uses WPF as its user interface framework. WPF has been around in some form since 2006, so it's very mature. It's also flexible, giving you control over every pixel on the screen, should you so desire. For someone wanting to develop apps for Windows, there's a host of alternatives to choose from, with the main candidates today probably being WinUI 3 and WPF. Along the road, we've also had UWP, WinUI 2 (a different thing entirely from WinUI 3) and WinForms, and the venerable Win32 API is still around. This is all before getting into cross-platform options like .NET MAUI and React Native for Windows... safe to say, the choice of framework when starting on a Windows app today requires some careful consideration. While WinUI 3 is the new kid on the block, it's not clear that it's mature enough for Tower at this point, and, in any case, Tower 2 used WPF, so this carried over to Tower 3.
However, implementing dark mode in Tower required some restructuring of the UI code. Unlike WinUI 3, WPF doesn't have support for dark mode built-in so Pete, our Windows developer, had to come up with a custom solution. WPF lets you tie attributes of your UI controls to data, and when the data changes, so do the controls. In short, our dark mode implementation assigns semantically named colors to controls and, as the actual color associated with a name changes, so does the control.
Digging around in the UI code shone a spotlight on some other potential improvements. Many small UI inconsistencies were fixed, spacing and colors were tweaked, and some views were completely revamped. Tower 3 also switched to using all vector graphics instead of raster images for things like icons in the UI.
This work didn't only uncover graphical issues, but also performance pain points: places where it was clear certain scenarios would cause the app to slow down unnecessarily. As mentioned before, users were also notifying our support about encountering such performance problems. These were addressed as well, and at some point, it was clear Tower 3 was going to be a major overhaul, with the goal of making Tower a first-rate Windows app. This, of course, meant that no long-standing issue in the backlog was safe. While the UI and performance updates were right in front of the user, other areas in need of improvement were behind the scenes.
Zen and the Art of Code Obfuscation
One issue to tackle had to do with code obfuscation. When you're working with .NET, the compiler doesn't output native bytecode directly. Rather, it produces code in an intermediate language, designed to be platform-independent and translated into native instructions in Just-In-Time fashion by the runtime. Importantly, this output retains quite a bit of information about the original source code, making decompilation easy. There are quite a few obfuscators on the market — programs that take your original code and transform it to make the decompiled code much harder to read and reason about, while retaining the original functionality.
For Tower Windows, we previously used a commercial product, which came with some drawbacks. For one, the obfuscated app was slower. Using this commercial product also tied us to the crash reporter provided by the same framework, as getting useful information like stack traces depended on being able to deobfuscate the code. With this release, we took the opportunity to switch to an open-source obfuscation framework. In addition to giving us better performance, this framework doesn't obfuscate embedded strings, allowing us to use whatever crash reporter we see fit.
Another improvement on the infrastructure side concerns installing and updating the app. The installer used previously required admin privileges and installation was a slow process, requiring several clicks before the app was installed and ready to run. We've now switched to the Squirrel installer, which installs the app without any user interaction beyond activating the installer. It's as fast as can be, updates the app in the background without requiring the app to be restarted, and works with permissions in such a way as to not require admin privileges.
100 Years of Solitude
As the scope grew, the development of Tower 3 stretched out over quite some time. We were eager to get this release into the hands of users, but also wanted to take our time and make sure we got it right. Pete kept working on the update alongside maintaining the previous version and, over the course of one-and-a-half years, he and other team members shaped the app into something that would live up to the expectations of our users (and ourselves).
We've already touched on the multitude of frameworks available for Windows development. This variety also means it’s not always obvious what a great Windows app is supposed to look and feel like. Compared to the Mac, where there’s often one best practice for solving a certain problem, on Windows you're sometimes left to your own devices. Making the app work as well as possible occasionally required us to design our own solutions from scratch.
On this topic, it might be mentioned that there's a thriving market for third-party UI components for Windows. With the plethora of Windows app frameworks that exist at various levels of maturity, it's no surprise that quite a selection of libraries exist to provide specialized components and fill in gaps between frameworks. We did use some third-party components before Tower 3 — an example being the sidebar in Tower 2. The component we used, however, was slow, buggy, and way more complicated than necessary for our use case, so Pete got to work on rewriting this. Rewriting an existing component may sound like a nice and well-defined problem, but it comes with its own challenges.
Even if a component has its drawbacks, the fact that it has made it into the app means that it has filled some function. The component has provided some functionality, and users now have expectations for how it behaves. Any regressions are likely to stand out like a sore thumb, which means great care is needed when creating our own implementation.
Regardless of what it might sound like at this point, Pete wasn't locked away in a silo, chipping away at the Windows app while oblivious to the outside world. On the contrary, the fact that we make native apps for both Mac and Windows makes for some interesting collaboration opportunities, which we'll look at next.
A Tale of Two Platforms
Developing separate native apps for Mac and Windows does influence our development process. When working on parts of the Windows app that already have corresponding features in the Mac app, Pete made a point of following the Mac app code structure very closely. This even entailed using similar names and writing helper code to be able to tackle problems in a similar way. At first glance, this may seem weird: why not work "with the grain" of the platform instead of going out of your way to follow conventions from another platform? However, this approach has some huge benefits in our case.
If the Mac team works on a feature later added to the Windows app (or vice versa), the team implementing the feature puts a lot of thought into its workings: how it's presented to the user, how it might interact with other functionality, what edge cases it comes with, and so on. The more closely the other app follows the structure of the original implementation, the easier it is to “reuse” all the thought that already has gone into the feature. Working like this gives us faster progress and less bugs, and helps set the stage for more efficient collaboration on future development. It also helps when applying a bugfix or change across both platforms. A smaller but still valid benefit is that working this way provided a free code review for the Mac app — indeed Pete did discover some bugs in the Mac app when going through the code as he was working on the Windows app.
This begs the question, though: if it's so useful to have the same code structure on both platforms, why not have the same code on both platforms? In other words, why not implement shared parts of the app in one programming language that compiles to native code on both platforms, with some platform-specific native user interface code on top? This sounds obvious in theory, but there are a few difficulties with this approach.
While there are certainly languages that compile to native, performant code on both Mac and Windows, the significance of tooling and libraries should not be underestimated. When working on a Mac app, the best tooling available is in the Swift/Objective-C ecosystem, while for Windows, the favored environment is .NET.
Another important factor is the amount of familiarity we have with a certain technology: our team has acquired lots of experience writing Mac and Windows apps, not so much writing production code in C++, Rust, Go, or any other cross-platform language candidate. Learning a new programming language well enough to cobble an app together is one thing, writing performant, correct, maintainable and extensible code is quite another. As both of our apps keep moving forward at a fast pace, having our developers take the time to learn new tech and rewrite major pieces of functionality would be a major time sink and risk, especially as Tower for Mac uses a lot of macOS-native libraries not available on Windows — not only for the user interface, but also for things like networking, concurrency and persistence.
That's not to say that sharing code between the apps is completely out of the question. We do keep an eye out for opportunities in this area. For example, with the effort now going into Swift support on Windows, who knows — maybe we'll be able to use some of the code already written for the Mac app on Windows in the future!
As mentioned, we wanted to take our time on Tower 3 to get it right. Development on the new version happened alongside maintenance of Tower 2, and around one-and-a-half-year after development on the new version started, Tower 3 for Windows was finally released in November 2021! The update touches almost every aspect of the app: Tower 3 is more stable while delivering better performance, a nicer UI, and more features. We’re very proud of this release and happy that the feedback from our users confirms our expectations: Tower 3 is an awesome Windows app!
While getting the update out the door was a big milestone, the story doesn't end there. In addition to its other improvements, Tower 3 provides a better base to build on, and there's no shortage of features to come. To mention one among these, the impressive Undo feature from Tower for Mac, allowing you to undo just about any Git action with a single click, is on its way to Windows. There are many other improvements on their way for both Mac and Windows, some of which are detailed in our most recent roadmap post. We hope Tower makes working with Git more enjoyable for you, and look forward to releasing new updates and hearing your feedback!
Join Over 100,000 Developers & Designers
Be the first to know about new content from the Tower blog as well as giveaways and freebies via email.