This is my research notebook. I'm an OSX / iOS indie developer. After 8 years of Objective-C I really enjoy Swift nowadays. Trying to publish all my research on Development, Swift & other technologies here.
Thu, 24 Jan 2019 #
Gitsi On GitHub
Youtube Video
Over Christmas I decided on a whim to solve a workflow-related problem that I've had for some time and to also go back and write a small-scale project in C. The result is Gitsi
a small, lightweight, interactive git status client that offers fast shortcuts to quickly manage the git status output. Let me go back to the actual problem I had.
1 Using Git on the Commandline
Over the years I've tried and used many different git clients. SourceTree
, GitUp
, Gitk
, Magit
, to name a few. Magit
(a Git client build into Emacs) is the one I still use the most, but over the years I've more and more moved towards plain git on the terminal for most of my daily activities. However, one thing always bugged me: If I have an output from git status (say the following one):
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: Readme.md
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: component_stats.db
modified: q.sql
Untracked files:
(use "git add <file>..." to include in what will be committed)
documentation/README.md
api_design/design_draft.md
Then if I want to add the two untracked files and the q.sql to the index (in order to commit them) I'd have to write:
git add documentation/README.md
git add pi_design/design_draft.md
git add q.sql
That's a lot of typing. Usually I go about copy pasting with the mouse. That also takes forever. Surely using a git GUI tool (like SourceTree) is much faster but as I don't need it for any of the other tasks I usually do, I'd need to start it first, which also takes forever. What I envisioned was an easy way to add, stage, unstage files in the git index, workspace and untracked files. Bonus points for also allowing git checkout --
to remove changes to a specific file.
I know there're tools for that (such as tig
) but I wanted something that only did one thing, status management. No logging, no pushing or pulling, etc. This felt to me like a great opportunity to write a small scale C project, something I hadn't done for at least more than 10 years.
3 Writing in C
As a iOS developer I've had my fair share of Objective-C, however all the nice abstractions that Apple put in place actually mean that usually I don't really touch C code. So, writing something bigger in C was really interesting when comparing it to developing something in Swift (doh, obviously).
It relies on two libraries:
- libgit2: The interface to
git
. Interestingly git
(the commandline tool) and libgit2
are separate entities. Meaning git
does not use libgit. This means that for some things that git does, there is no easy equivalent in libgit. This makes some things very hard.
- ncurses: This is the library to use when developing TUI (terminal UI) applications. It allows you to move the cursor around in the terminal, color it, draw windows, etc.
3.1 The fun parts
- Compile times were beyond beautiful
- The limited featureset of the language made it surprisingly fun to work on a project of this size. There's no questioning which abstraction to use, there's usually only one that fits. If there're more, they require more indepth knowledge of the language.
- This is not particular to C, but the reactive approach of writing a TUI app by just rendering over a mainloop (also like most games) feels very refreshing compared to normal iOS work (obvious comparison to React, etc)
- Getting it to work on a different platform (Linux) was also kinda easy, though I struggled more with it than I would have expected. In particular there're several useful functions that are not part of C99 (the 99 C standard). Using them requires the correct compile flag (
-std=gnu11
). However, some functions are also not part of the C standard, but specific GNU extensions, though they're also supported on all the platforms I care about. Using these requires adding a #define _GNU_SOURCE
at the top of your source file. However, not on macOS, but on Linux. This was tricky to figure out.
3.2 The less fun parts
- Memory management. This was totally expected, but it consumed more time than I'd have expected. Thankfully,
valgrind
is a great tool to find these issues. Sadly, valgrind seems to not run on macOS right now, so I ran it via Docker. That worked great though. I had a lot of small issues that seem to be (mostly) fixed now. This experience alone makes me so grateful for Swift's Arc and Rust's Borrow Checker.
- Documentation. This was terrible. The
libgit
documentation is mostly ok, but for the C stdlib
and epecially ncurses
, the documentation is just awful. Since they all differ platform by platform, implementation by implementation, there's no one reference. Sometimes it would take me forever to just figure out what the parameters to a certain function would do. Getting this done involved a lot of googling.
- Lack of tooling suite. Sure, almost every editor supports C, but almost everything in tooling requires choices, research and pain. There's no one package manager / ecosystem. How to handle building, there're a ton of build systems, how to handle tests, there're a ton of solutions. Since everything is so free and open, everything is also a mess. It is probably much easier when you're working on a pre-existing, pre-defined project, but there's a huge overhead of things just to get started. I tried to keep it as simple as possible. System Package Manager (
homebrew
/ apt-get
), vim
, cmake
and no tests.
4 Using it with Xcode
Working on a project like this from Xcode
works surprisingly well. I set up an Xcode project, added the required libraries (ncurses
and libgit
) imported the main.c
file and I could compile it. Debugging was a wee bit more work. Since Xcode can't run ncurses executables (i.e. it can't run them in a terminal) we have to tell Xcode to compile the app and then wait for the process to start and then to attach to it. After that, I can head to a terminal and run the just-compiled app and then Xcode will attach to it and all the breakpoints etc work. It is a wee bit more cumbersome but works fine. I had to introduce a particular command line flag though that makes sure that attaching via Xcode doesn't break things.
5 Testing
There're no tests. Yet. I've started on the necessary prerequisites to run integration tests but I wanted to release it first. The idea is to start the binary in a special mode and give it a string of tasks (i.e. go down 2 lines, do a git add, go up one line, etc) and then make sure that the end result is what's expected.
6 Recap
This was a fun project, but now I'm also done with C for the rest of this year (except for small additions to gitsi, of course). I'm already longing to do something in Swift or Rust again.
{:description "An interactive Git Status client, written in C",
:keyword-tags (:c :linux),
:projects ({:project "Sarbatka", :link "/electronic-music.html"}),
:postlist
({:title "Blogstrapped.",
:url "/2011/12/01/blogstrapped/",
:tags "entrepeneurship blog",
:keyword-tags (:entrepeneurship :blog),
:date "Thu, 1 Dec 2011",
:keywords "entrepeneurship videro fear unknown stylemac",
:keyword-keywords
(:entrepeneurship :videro :fear :unknown :stylemac)}
{:title "Fast NSDictionary traversal in Objective-C",
:url "/2011/12/07/fast-nsdictionary-traversal-in-objective-c/",
:tags "objective-c ios cocoa",
:keyword-tags (:objective-c :ios :cocoa),
:date "Wed, 7 Dec 2011"}
{:title "How the iPad can improve focus",
:url "/2011/12/09/how-the-ipad-can-improve-focus/",
:tags "opinion",
:keyword-tags (:opinion),
:date "Fri, 9 Dec 2011"}
{:title "Use VIM as an Xcode alternative",
:url "/2013/01/29/use-vim-as-xcode-alternative-ios-mac-cocoa/",
:tags "cocoa objective-c ios",
:keyword-tags (:cocoa :objective-c :ios),
:date "Tue, 29 Jan 2013"}
{:title "Now Running Clojure",
:url "/2014/01/20/now-running-clojure/",
:tags "clojure blog",
:keyword-tags (:clojure :blog),
:date "Mon, 20 Jan 2014"}
{:title
"Debugging entitlement issues in Maps, iCloud, In-App, Keychain, or GameCenter",
:url
"/2014/01/21/debugging-entitlement-maps-icloud-gamecenter-issues/",
:tags "ios cocoa",
:keyword-tags (:ios :cocoa),
:date "Tue, 21 Jan 2014",
:keywords "ios cocoa entitlements",
:keyword-keywords (:ios :cocoa :entitlements)}
{:title
"Clojure/Enlive Static Site Generator that keeps your HTML intact",
:url
"/2014/01/22/clojure-enlive-static-site-generator-that-keeps-html-intact/",
:tags "blog clojure",
:keyword-tags (:blog :clojure),
:date "Wed, 22 Jan 2014",
:keywords "clojure static site generator jekyll html enlive",
:keyword-keywords
(:clojure :static :site :generator :jekyll :html :enlive)}
{:title "Finding a good URL Partitioning Scheme for PostgreSQL",
:url "/2014/01/24/finding-url-partitioning-scheme-postgres/",
:tags "postgresql clojure",
:keyword-tags (:postgresql :clojure),
:date "Fri, 24 Jan 2014",
:keywords "clojure postgresql partitioning scheme",
:keyword-keywords (:clojure :postgresql :partitioning :scheme)}
{:title "An Emacs Lisp tooling tutorial, writing a bulk mailer",
:url "/2014/01/29/emacs-lisp-tooling-tutorial-writing-bulk-mailer/",
:tags "emacs",
:keyword-tags (:emacs),
:date "Wed, 29 Jan 2014",
:keywords
"emacs lisp bulk mailer tutorial email vim vimscript evil",
:keyword-keywords
(:emacs :lisp :bulk :mailer :tutorial :email :vim :vimscript :evil)}
{:title
"Creating a Swift syntax extension: the Lisp 'cond' function",
:url "/2014/06/08/writing-simple-syntax-extensions-in-swift/",
:tags "swift ios cocoa",
:keyword-tags (:swift :ios :cocoa),
:date "Sun, 8 Jun 2014",
:keywords
"clojure lisp swift cond syntax macro extension cocoa ios feature",
:keyword-keywords
(:clojure
:lisp
:swift
:cond
:syntax
:macro
:extension
:cocoa
:ios
:feature)}
{:title "Swift optionals made simple",
:url "/2014/06/13/swift-optionals-made-simple/",
:tags "swift ios cocoa",
:keyword-tags (:swift :ios :cocoa),
:date "Fri, 13 Jun 2014",
:keywords
"lisp swift optional scala simple optionals switch chaining feature",
:keyword-keywords
(:lisp
:swift
:optional
:scala
:simple
:optionals
:switch
:chaining
:feature)}
{:title "Generic method overloading by protocol in Swift",
:url "/2015/06/17/swift-method-overloading-by-protocol/",
:tags "swift ios cocoa",
:keyword-tags (:swift :ios :cocoa),
:date "Wed, 17 Jun 2015",
:keywords
"swift optional simple overloading method protocol extensions generics feature",
:keyword-keywords
(:swift
:optional
:simple
:overloading
:method
:protocol
:extensions
:generics
:feature)}
{:title "Using try / catch in Swift with asynchronous closures",
:url "/2015/06/19/swift-try-catch-asynchronous-closures/",
:tags "swift ios",
:keyword-tags (:swift :ios),
:date "Fri, 19 Jun 2015",
:keywords "swift try catch errortype closure async result feature",
:keyword-keywords
(:swift :try :catch :errortype :closure :async :result :feature)}
{:title "Debugging advanced compilation errors in ClojureScript",
:url "/2015/07/02/debugging-clojurescript-advanced-compilation/",
:tags "clojure",
:keyword-tags (:clojure),
:date "Thu, 2 Jul 2015",
:keywords "debugging clojure clojurescript externs",
:keyword-keywords (:debugging :clojure :clojurescript :externs)}
{:title "Tuples in Swift, Advanced Usage and Best Practices",
:url "/2015/07/19/tuples-swift-advanced-usage-best-practices/",
:tags "swift",
:keyword-tags (:swift),
:date "Sun, 19 Jul 2015",
:keywords "swift tuples generics feature",
:keyword-keywords (:swift :tuples :generics :feature)}
{:title "Match Me if you can: Swift Pattern Matching in Detail.",
:url "/2015/08/20/swift-pattern-matching-in-detail/",
:tags "swift ios cocoa",
:keyword-tags (:swift :ios :cocoa),
:date "Thu, 20 Aug 2015",
:keywords
"feature lisp swift optional scala simple optionals switch chaining for pattern matching clojure haskell",
:keyword-keywords
(:feature
:lisp
:swift
:optional
:scala
:simple
:optionals
:switch
:chaining
:for
:pattern
:matching
:clojure
:haskell)}
{:title "Optional throw via try? in Swift 2 beta 6",
:url "/2015/08/25/optional-throw-swift/",
:tags "swift",
:keyword-tags (:swift),
:date "Tue, 25 Aug 2015",
:keywords
"swift error throw result either rethrow try syntax swift2",
:keyword-keywords
(:swift
:error
:throw
:result
:either
:rethrow
:try
:syntax
:swift2)}
{:title "Getting your iPhone 6s Chip Foundry from Swift",
:url "/2015/09/30/getting-iphone6s-foundry-from-swift/",
:tags "swift",
:keyword-tags (:swift),
:date "Wed, 30 Sep 2015",
:keywords
"swift iphone6s iphone tsmc samsung gestalt private api foundation",
:keyword-keywords
(:swift
:iphone6s
:iphone
:tsmc
:samsung
:gestalt
:private
:api
:foundation)}
{:title "Advanced and Practical Enum usage in Swift",
:url "/2015/10/17/advanced-practical-enum-examples/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Sat, 17 Oct 2015",
:keywords
"feature swift enum algebraic caseclass union case switch pattern simple practical advanced example",
:keyword-keywords
(:feature
:swift
:enum
:algebraic
:caseclass
:union
:case
:switch
:pattern
:simple
:practical
:advanced
:example)}
{:title "The Swift Reflection API and what you can do with it",
:url "/2015/10/24/swift-reflection-api-what-you-can-do/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Sat, 24 Oct 2015",
:keywords
"feature swift reflection struct class displayType mirror api reflecting any anyobject",
:keyword-keywords
(:feature
:swift
:reflection
:struct
:class
:displayType
:mirror
:api
:reflecting
:any
:anyobject)}
{:title "Reduce all the things",
:url "/2015/11/30/reduce-all-the-things/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Mon, 30 Nov 2015",
:keywords
"feature swift reduce map filter group partition split interpose chunk functional programming flatMap",
:keyword-keywords
(:feature
:swift
:reduce
:map
:filter
:group
:partition
:split
:interpose
:chunk
:functional
:programming
:flatMap)}
{:title
"Swift Package Manager: Create and Use a X11 package on Linux",
:url "/2015/12/08/swift-ubuntu-x11-window-app/",
:tags "swift linux",
:keyword-tags (:swift :linux),
:date "Tue, 8 Dec 2015",
:keywords "linux x11 swift libx11 xserver xorg",
:keyword-keywords (:linux :x11 :swift :libx11 :xserver :xorg)}
{:title "Hirundo: Comfortably follow Swift Mailing Lists on OSX",
:url "/2016/02/02/hirundo-mac-app-swift-mailing-lists/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Tue, 2 Feb 2016",
:keywords
"swift mac cocoa mailing list swift-dev swift-eveolution swift-users reading macosx",
:keyword-keywords
(:swift
:mac
:cocoa
:mailing
:list
:swift-dev
:swift-eveolution
:swift-users
:reading
:macosx)}
{:title "Three tips for concise Swift using the Guard statement",
:url "/2016/03/29/three-tips-for-clean-swift-code/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Tue, 29 Mar 2016",
:keywords
"swift mac cocoa guard let enum pattern matching patterns",
:keyword-keywords
(:swift :mac :cocoa :guard :let :enum :pattern :matching :patterns)}
{:title "Using Git Hooks to prevent commiting test code",
:url "/2016/04/04/prevent-accidental-test-code-commits/",
:tags "git",
:keyword-tags (:git),
:date "Mon, 4 Apr 2016",
:keywords "git hook commit debug test code",
:keyword-keywords (:git :hook :commit :debug :test :code)}
{:title
"Force optionals in multi-unwrapped \"guard let\" or \"if let\"",
:url
"/2016/04/14/force-optionals-in-guard-or-let-optional-binding/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Thu, 14 Apr 2016",
:keywords
"swift guard let unwrap bind binding unwrapped optional some none optionals",
:keyword-keywords
(:swift
:guard
:let
:unwrap
:bind
:binding
:unwrapped
:optional
:some
:none
:optionals)}
{:title "Raw value initializers for enums with associated types",
:url "/2016/04/23/associated-types-enum-raw-value-initializers/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Sat, 23 Apr 2016",
:keywords
"swift optional enum raw value initializers associated type",
:keyword-keywords
(:swift
:optional
:enum
:raw
:value
:initializers
:associated
:type)}
{:title "SwiftWatch",
:url "/2016/04/28/swiftwatch/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Thu, 28 Apr 2016",
:keywords
"swift hackernews reddit designernews lamernews socialnews swiftlang programming community post vote comment",
:keyword-keywords
(:swift
:hackernews
:reddit
:designernews
:lamernews
:socialnews
:swiftlang
:programming
:community
:post
:vote
:comment)}
{:title "Data in Swift 3 parsing a Doom WAD File",
:url "/2016/07/15/swift3-nsdata-data/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Fri, 15 Jul 2016",
:keywords "swift doom wad lumps data nsdata swift3 binary bytes",
:keyword-keywords
(:swift :doom :wad :lumps :data :nsdata :swift3 :binary :bytes)}
{:title "Value Types for Simple Difference Detection",
:url "/2017/09/30/value-types-for-simple-difference-detection/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Sat, 30 Sep 2017",
:keywords
"swift value types uitableview uicollectionview valuetypes struct class equatable tuple",
:keyword-keywords
(:swift
:value
:types
:uitableview
:uicollectionview
:valuetypes
:struct
:class
:equatable
:tuple)}
{:title "Taming SourceKitService for Less Xcode Memory Consumption",
:url "/2017/10/08/taming-sourcekitd/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Sun, 8 Oct 2017",
:keywords "xcode sourcekit swift SourceKitService",
:keyword-keywords (:xcode :sourcekit :swift :SourceKitService)}
{:title "Patterns for Working With Associated Types",
:url "/2017/12/10/patterns-for-working-with-associated-types/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Sun, 10 Dec 2017",
:keywords
"swift protocol protocols associated associatedtype typealias pattern pat",
:keyword-keywords
(:swift
:protocol
:protocols
:associated
:associatedtype
:typealias
:pattern
:pat)}
{:title "Useful Optional Extensions",
:url "/2018/01/10/optional-extensions/",
:tags "swift cocoa ios",
:keyword-tags (:swift :cocoa :ios),
:date "Wed, 10 Jan 2018",
:keywords "swift protocol optional optionals extensions",
:keyword-keywords
(:swift :protocol :optional :optionals :extensions)}
{:title "Expanding Swift's Reach",
:url "/2018/05/03/expanding-swifts-reach/",
:tags "swift linux",
:keyword-tags (:swift :linux),
:date "Thu, 3 April 2018",
:keywords "swift linux server opensource",
:keyword-keywords (:swift :linux :server :opensource)}
{:title "An interactive Git Status client, written in C",
:url "/2019/01/24/interactive-git-status-written-in-c/",
:tags "c linux",
:keyword-tags (:c :linux),
:date "Thu, 24 Jan 2019",
:keywords "c linux macos ncurses git libgit",
:keyword-keywords (:c :linux :macos :ncurses :git :libgit)}),
:tags
"benedikt, c, clojure, clojurescript, cocoa, git, html, ios, javascript, libgit, linux, mac, macos, ncurses, objective-c, photodesk, research, stylemac, terhechte",
:type :post,
:keywords "c linux macos ncurses git libgit",
:title "An interactive Git Status client, written in C",
:author "Benedikt Terhechte",
:summary "An interactive Git Status client, written in C",
:categories
({:tag "blog", :url "/tags/blog/index.html", :count 3}
{:tag "c", :url "/tags/c/index.html", :count 1}
{:tag "clojure", :url "/tags/clojure/index.html", :count 4}
{:tag "cocoa", :url "/tags/cocoa/index.html", :count 20}
{:tag "emacs", :url "/tags/emacs/index.html", :count 1}
{:tag "entrepeneurship",
:url "/tags/entrepeneurship/index.html",
:count 1}
{:tag "git", :url "/tags/git/index.html", :count 1}
{:tag "ios", :url "/tags/ios/index.html", :count 21}
{:tag "linux", :url "/tags/linux/index.html", :count 3}
{:tag "objective-c", :url "/tags/objective-c/index.html", :count 2}
{:tag "opinion", :url "/tags/opinion/index.html", :count 1}
{:tag "postgresql", :url "/tags/postgresql/index.html", :count 1}
{:tag "swift", :url "/tags/swift/index.html", :count 23}),
:url "/2019/01/24/interactive-git-status-written-in-c/",
:options "toc:nil",
:blog-index nil,
:watching nil,
:site-title "Appventure",
:keyword-keywords (:c :linux :macos :ncurses :git :libgit)}
[{:keyword-tags (:c :linux),
:tags "c linux",
:date "Thu, 24 Jan 2019",
:footnotes nil,
:meta
{:title "An interactive Git Status client, written in C",
:keyword-tags (:c :linux),
:tags "c linux",
:keyword-keywords (:c :linux :macos :ncurses :git :libgit),
:keywords "c linux macos ncurses git libgit",
:summary "An interactive Git Status client, written in C",
:description "An interactive Git Status client, written in C",
:options "toc:nil"},
:content
"\n<div class=\"figure\">\n<p><img src=\"https://j.gifs.com/JyDPZy.gif\" alt=\"JyDPZy.gif\" />\n</p>\n</div>\n<a href=\"https://github.com/terhechte/gitsi\" class=\"button round small\">\n<i class=\"fi-social-github\"></i>\nGitsi On GitHub</a>\n\n<a href=\"https://www.youtube.com/watch?v=pAxquqis56I&feature=youtu.be\" class=\"button round small\">\nYoutube Video</a>\n\n<p>\nOver Christmas I decided on a whim to solve a workflow-related problem that I've had for some time and to also go back and write a small-scale project in C. The result is <code>Gitsi</code> a small, lightweight, interactive git status client that offers fast shortcuts to quickly manage the git status output. Let me go back to the actual problem I had.\n</p>\n\n<div id=\"outline-container-org7e04465\" class=\"outline-2\">\n<h2 id=\"org7e04465\"><span class=\"section-number-2\">1</span> Using Git on the Commandline</h2>\n<div class=\"outline-text-2\" id=\"text-1\">\n<p>\nOver the years I've tried and used many different git clients. <code>SourceTree</code>, <code>GitUp</code>, <code>Gitk</code>, <code>Magit</code>, to name a few. <code>Magit</code> (a Git client build into Emacs) is the one I still use the most, but over the years I've more and more moved towards plain git on the terminal for most of my daily activities. However, one thing always bugged me: If I have an output from git status (say the following one):\n</p>\n\n<div class=\"org-src-container\">\n<pre class=\"src src-bash\">On branch master\nYour branch is up to date with <span style=\"font-style: italic;\">'origin/master'</span>.\n\nChanges to be committed:\n (use <span style=\"font-style: italic;\">\"git reset HEAD <file>...\"</span> to unstage)\n\n modified: Readme.md\n\nChanges not staged for commit:\n (use <span style=\"font-style: italic;\">\"git add <file>...\"</span> to update what will be committed)\n (use <span style=\"font-style: italic;\">\"git checkout -- <file>...\"</span> to discard changes<span style=\"font-weight: bold;\"> in</span> working directory)\n\n modified: component_stats.db\n modified: q.sql\n\nUntracked files:\n (use <span style=\"font-style: italic;\">\"git add <file>...\"</span> to include<span style=\"font-weight: bold;\"> in</span> what will be committed)\n\n documentation/README.md\n api_design/design_draft.md\n</pre>\n</div>\n\n<p>\nThen if I want to add the two untracked files and the q.sql to the index (in order to commit them) I'd have to write:\n</p>\n\n<div class=\"org-src-container\">\n<pre class=\"src src-bash\">git add documentation/README.md\ngit add pi_design/design_draft.md\ngit add q.sql\n</pre>\n</div>\n\n<p>\nThat's a lot of typing. Usually I go about copy pasting with the mouse. That also takes forever. Surely using a git GUI tool (like SourceTree) is much faster but as I don't need it for any of the other tasks I usually do, I'd need to start it first, which also takes forever. What I envisioned was an easy way to add, stage, unstage files in the git index, workspace and untracked files. Bonus points for also allowing <code>git checkout --</code> to remove changes to a specific file.\n</p>\n\n<p>\nI know there're tools for that (such as <code>tig</code>) but I wanted something that only did one thing, status management. No logging, no pushing or pulling, etc. This felt to me like a great opportunity to write a small scale C project, something I hadn't done for at least more than 10 years.\n</p>\n</div>\n</div>\n\n<div id=\"outline-container-org325e9c8\" class=\"outline-2\">\n<h2 id=\"org325e9c8\"><span class=\"section-number-2\">2</span> Gitsi</h2>\n<div class=\"outline-text-2\" id=\"text-2\">\n<img src=\"/cimg/logo_gitsi.svg\" height=\"150\" />\n\n<p>\nThis is how gitsi was born. Have a <a href=\"https://github.com/terhechte/gitsi\">look at the project if you're interested</a>, it offers a nice handful of features such as all of the above plus: VIM Keybindings, filtering, diffing, interactive <code>git add</code> and more.\n</p>\n\n<p>\n<a href=\"https://www.youtube.com/watch?v=pAxquqis56I&feature=youtu.be\">Here's a short video that shows it in action</a>\n</p>\n</div>\n</div>\n\n<div id=\"outline-container-org62c252b\" class=\"outline-2\">\n<h2 id=\"org62c252b\"><span class=\"section-number-2\">3</span> Writing in C</h2>\n<div class=\"outline-text-2\" id=\"text-3\">\n<p>\nAs a iOS developer I've had my fair share of Objective-C, however all the nice abstractions that Apple put in place actually mean that usually I don't really touch C code. So, writing something bigger in C was really interesting when comparing it to developing something in Swift (doh, obviously).\n</p>\n\n<p>\nIt relies on two libraries:\n</p>\n<ul class=\"org-ul\">\n<li><a href=\"https://libgit2.org/\">libgit2</a>: The interface to <code>git</code>. Interestingly <code>git</code> (the commandline tool) and <code>libgit2</code> are separate entities. Meaning <code>git</code> does not use libgit. This means that for some things that git does, there is no easy equivalent in libgit. This makes some things very hard.</li>\n<li><a href=\"https://en.wikipedia.org/wiki/Ncurses\">ncurses</a>: This is <i>the</i> library to use when developing TUI (terminal UI) applications. It allows you to move the cursor around in the terminal, color it, draw windows, etc.</li>\n</ul>\n</div>\n\n<div id=\"outline-container-org805d9b2\" class=\"outline-3\">\n<h3 id=\"org805d9b2\"><span class=\"section-number-3\">3.1</span> The fun parts</h3>\n<div class=\"outline-text-3\" id=\"text-3-1\">\n<ul class=\"org-ul\">\n<li>Compile times were beyond beautiful</li>\n<li>The limited featureset of the language made it surprisingly fun to work on a project of this size. There's no questioning which abstraction to use, there's usually only one that fits. If there're more, they require more indepth knowledge of the language.</li>\n<li>This is not particular to C, but the reactive approach of writing a TUI app by just rendering over a mainloop (also like most games) feels very refreshing compared to <i>normal</i> iOS work (obvious comparison to React, etc)</li>\n<li>Getting it to work on a different platform (Linux) was also kinda easy, though I struggled more with it than I would have expected. In particular there're several useful functions that are not part of C99 (the 99 C standard). Using them requires the correct compile flag (<code>-std=gnu11</code>). However, some functions are also not part of the C standard, but specific GNU extensions, though they're also supported on all the platforms I care about. Using these requires adding a <code>#define _GNU_SOURCE</code> at the top of your source file. However, not on macOS, but on Linux. This was tricky to figure out.</li>\n</ul>\n</div>\n</div>\n\n<div id=\"outline-container-org23a9cfb\" class=\"outline-3\">\n<h3 id=\"org23a9cfb\"><span class=\"section-number-3\">3.2</span> The less fun parts</h3>\n<div class=\"outline-text-3\" id=\"text-3-2\">\n<ul class=\"org-ul\">\n<li>Memory management. This was totally expected, but it consumed more time than I'd have expected. Thankfully, <code>valgrind</code> is a great tool to find these issues. Sadly, valgrind seems to not run on macOS right now, so I ran it via Docker. That worked great though. I had a lot of small issues that seem to be (mostly) fixed now. This experience alone makes me so grateful for Swift's Arc and Rust's Borrow Checker.</li>\n<li>Documentation. This was terrible. The <code>libgit</code> documentation is mostly ok, but for the C <code>stdlib</code> and epecially <code>ncurses</code>, the documentation is just awful. Since they all differ platform by platform, implementation by implementation, there's no <i>one</i> reference. Sometimes it would take me forever to just figure out what the parameters to a certain function would do. Getting this done involved a lot of googling.</li>\n<li>Lack of tooling suite. Sure, almost every editor supports C, but almost everything in tooling requires choices, research and pain. There's no one package manager / ecosystem. How to handle building, there're a ton of build systems, how to handle tests, there're a ton of solutions. Since everything is so free and open, everything is also a mess. It is probably much easier when you're working on a pre-existing, pre-defined project, but there's a huge overhead of things just to get started. I tried to keep it as simple as possible. System Package Manager (<code>homebrew</code> / <code>apt-get</code>), <code>vim</code>, <code>cmake</code> and no tests.</li>\n</ul>\n</div>\n</div>\n</div>\n\n<div id=\"outline-container-orgd3886c1\" class=\"outline-2\">\n<h2 id=\"orgd3886c1\"><span class=\"section-number-2\">4</span> Using it with Xcode</h2>\n<div class=\"outline-text-2\" id=\"text-4\">\n<p>\nWorking on a project like this from <code>Xcode</code> works surprisingly well. I set up an Xcode project, added the required libraries (<code>ncurses</code> and <code>libgit</code>) imported the <code>main.c</code> file and I could compile it. Debugging was a wee bit more work. Since Xcode can't run ncurses executables (i.e. it can't run them in a terminal) we have to tell Xcode to compile the app and then wait for the process to start and then to attach to it. After that, I can head to a terminal and run the just-compiled app and then Xcode will attach to it and all the breakpoints etc work. It is a wee bit more cumbersome but works fine. I had to introduce a particular command line flag though that makes sure that attaching via Xcode doesn't break things. \n</p>\n</div>\n</div>\n\n<div id=\"outline-container-org71eb7d6\" class=\"outline-2\">\n<h2 id=\"org71eb7d6\"><span class=\"section-number-2\">5</span> Testing</h2>\n<div class=\"outline-text-2\" id=\"text-5\">\n<p>\nThere're no tests. Yet. I've started on the necessary prerequisites to run integration tests but I wanted to release it first. The idea is to start the binary in a special mode and give it a string of tasks (i.e. go down 2 lines, do a git add, go up one line, etc) and then make sure that the end result is what's expected.\n</p>\n</div>\n</div>\n\n<div id=\"outline-container-org301f2a0\" class=\"outline-2\">\n<h2 id=\"org301f2a0\"><span class=\"section-number-2\">6</span> Recap</h2>\n<div class=\"outline-text-2\" id=\"text-6\">\n<p>\nThis was a fun project, but now I'm also done with C for the rest of this year (except for small additions to gitsi, of course). I'm already longing to do something in Swift or Rust again.\n</p>\n</div>\n</div>\n",
:keywords "c linux macos ncurses git libgit",
:title "An interactive Git Status client, written in C",
:filename "2019-01-24-interactive-git-status-written-in-c.org",
:id -1794912213,
:url "/2019/01/24/interactive-git-status-written-in-c/",
:javadate #inst "2019-01-23T23:00:00.000-00:00",
:keyword-keywords (:c :linux :macos :ncurses :git :libgit)}]