<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>git Archives | Clever Cloud</title>
	<atom:link href="https://www.clever.cloud/blog/tag/git/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.clever.cloud/blog/tag/git/</link>
	<description>From Code to Product</description>
	<lastBuildDate>Mon, 07 Jan 2019 16:53:00 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://cdn.clever-cloud.com/uploads/2023/03/cropped-cropped-favicon-32x32.png</url>
	<title>git Archives | Clever Cloud</title>
	<link>https://www.clever.cloud/blog/tag/git/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>How to Fork Things the Right Way</title>
		<link>https://www.clever.cloud/blog/engineering/2019/01/07/git-am-how-to-fork/</link>
		
		<dc:creator><![CDATA[Valeriane Venance]]></dc:creator>
		<pubDate>Mon, 07 Jan 2019 16:53:00 +0000</pubDate>
				<category><![CDATA[Engineering]]></category>
		<category><![CDATA[fork]]></category>
		<category><![CDATA[git]]></category>
		<guid isPermaLink="false">https://www2.cleverapps.io/wp/blog/technology/2019/01/07/git-am-how-to-fork/</guid>

					<description><![CDATA[<p><img width="1400" height="540" src="https://cdn.clever-cloud.com/uploads/2021/08/git-am-1.jpg" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="git am 1" decoding="async" fetchpriority="high" srcset="https://cdn.clever-cloud.com/uploads/2021/08/git-am-1.jpg 1400w, https://cdn.clever-cloud.com/uploads/2021/08/git-am-1-300x116.jpg 300w, https://cdn.clever-cloud.com/uploads/2021/08/git-am-1-1024x395.jpg 1024w, https://cdn.clever-cloud.com/uploads/2021/08/git-am-1-768x296.jpg 768w, https://cdn.clever-cloud.com/uploads/2021/08/git-am-1-1368x528.jpg 1368w" sizes="(max-width: 1400px) 100vw, 1400px" /></p><p>Some open source git repos host really useful code but are sometimes not maintained.</p>
<p>If you use that code on a regular basis, you may need to fork it, improve it, and use your enhanced version of that code.</p>
<p>Then someday the original repo is updated, a brand new version of the code you forked is released, and either you will lose all your changes or you will miss the new features.</p>
<span id="more-2815"></span>

<p>To get the best of the two versions, you need to merge them.</p>
<p>There are many solutions and any that fits your needs is valid.</p>
<p>My problem was to merge up to 50 commits and I hate having to fix so many conflicts; it can take days. I also knew there was code that would need refactoring, so my idea was to avoid merging the whole changes in one batch. In this paradigm the <code>am</code> command is my best git option.</p>
<h2 id="what-is-git-am">What is <code>git am</code>?</h2>
<p><code>git am</code> is a super useful command which can be used for many things as it’s reading and parsing the <a href="http://alblue.bandlem.com/2011/12/git-tip-of-week-patches-by-email.html">mailbox</a> containing the commits you specify and allows you to use only parts of the extracted data such as the code diff, the autor, the message…</p>
<p>In this case, I used it to <strong>cherry pick a commit from a repo and add it to another</strong>, but this command is way more powerful than this.</p>
<p>You can find the whole documentation here <a href="https://git-scm.com/docs/git-am">https://git-scm.com/docs/git-am</a></p>
<h2 id="knowledge-required">knowledge required</h2>
<p>UNIX shell basic usage</p>
<h2 id="how-do-we-use-it">How do we use it?</h2>
<p>For this use case, you need to have the two repos locally.</p>
<p>Let’s say we have this architecture:</p>
<pre><code class="language-bash">$ demo ls
my-forked-repo   the-last-version-repo
</code></pre>
<p>Go to last version repo</p>
<p><code>$ cd the-last-version-repo/</code></p>
<p>Use the following line for each commit you want to import in the new version</p>
<pre><code class="language-bash">$ git --git-dir=../&lt;My-forked-repo&gt;/.git \
format-patch -k -1 --stdout &lt;your commit SHA&gt; | \
git am -3 -k
</code></pre>
<p>Here is the full explanation of the command: <a href="https://stackoverflow.com/a/9507417">https://stackoverflow.com/a/9507417</a></p>
<p>If there’s no conflicts, console will just display</p>
<p><code>$ Applying &lt;your commit message&gt;</code></p>
<p>And if you type in your console:</p>
<p><code>$ git status</code></p>
<p>You will see that you have a commit ahead of HEAD.</p>
<h2 id="ive-got-a-conflict">I’ve got a conflict</h2>
<p>If for some reason the commit cannot be merged automatically, console will display a message with the conflicting files list and a small explanation of the failure. In this case, you can get more information with:</p>
<p><code>$ git am --show-current-patch</code></p>
<h3 id="i-want-to-solve-it">I want to solve it</h3>
<p>To resolve the conflicts, go to the files, manually keep the code you want then go to your console and add them to git:</p>
<p><code>$ git add &lt;path/to/file&gt;</code></p>
<p>After adding the files to git, you will be able to do:</p>
<p><code>$ git am --continue</code></p>
<p>which will commit the files for you. A <code>$ git status</code> will produce the same result as before.</p>
<h3 id="i-do-not-want-to-solve-it">I do not want to solve it</h3>
<p>In the case you want to handle the conflict without git, you can just</p>
<p><code>$ git am --abort</code></p>
<p>Which will abort all the commits you’re trying to import. Or you can use:</p>
<p><code>$ git am --skip</code></p>
<p>which will abort the import of the current commit having a conflict. In both cases, the files will be left as they were before you typed the command.</p>
<p>Notice that you can’t use these commands to resolve a conflict triggered by another git command (e.g <code>git merge</code>…)</p>
<h2 id="i-imported-my-commit-so-what-now">I imported my commit, so what now?</h2>
<p>Basically, nothing, you’re all good to go, push the commits to your branch whenever you want with</p>
<p><code>$ git push &lt;your remote name&gt; &lt;your branch name&gt;</code></p>
<p>And that’s it. If for some reason you need to update the message of the commit, use</p>
<p><code>$ git commit --amend</code></p>
<p>Careful, it only works with the last commit. There are ways to do the same with an older commit, but it involves rewriting the tree and this will change the commit SHA as well, read the git documentation for more details.</p>
<p>If you made a lot of changes on a commit that wasn’t originally yours, it may be accurate to change the author to yourself, for this use</p>
<p><code>$ git commit --amend --author=&quot;Author Name &lt;email@address.com&gt;&quot;</code></p>
<p>On your last commit. For olders commit, it’s exactly as above.</p>
<p>Happy merging!</p>
]]></description>
										<content:encoded><![CDATA[<p><img width="1400" height="540" src="https://cdn.clever-cloud.com/uploads/2021/08/git-am-1.jpg" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="git am 1" decoding="async" srcset="https://cdn.clever-cloud.com/uploads/2021/08/git-am-1.jpg 1400w, https://cdn.clever-cloud.com/uploads/2021/08/git-am-1-300x116.jpg 300w, https://cdn.clever-cloud.com/uploads/2021/08/git-am-1-1024x395.jpg 1024w, https://cdn.clever-cloud.com/uploads/2021/08/git-am-1-768x296.jpg 768w, https://cdn.clever-cloud.com/uploads/2021/08/git-am-1-1368x528.jpg 1368w" sizes="(max-width: 1400px) 100vw, 1400px" /></p><p>Some open source git repos host really useful code but are sometimes not maintained.</p>
<p>If you use that code on a regular basis, you may need to fork it, improve it, and use your enhanced version of that code.</p>
<p>Then someday the original repo is updated, a brand new version of the code you forked is released, and either you will lose all your changes or you will miss the new features.</p>
<span id="more-2815"></span>

<p>To get the best of the two versions, you need to merge them.</p>
<p>There are many solutions and any that fits your needs is valid.</p>
<p>My problem was to merge up to 50 commits and I hate having to fix so many conflicts; it can take days. I also knew there was code that would need refactoring, so my idea was to avoid merging the whole changes in one batch. In this paradigm the <code>am</code> command is my best git option.</p>
<h2 id="what-is-git-am">What is <code>git am</code>?</h2>
<p><code>git am</code> is a super useful command which can be used for many things as it’s reading and parsing the <a href="http://alblue.bandlem.com/2011/12/git-tip-of-week-patches-by-email.html">mailbox</a> containing the commits you specify and allows you to use only parts of the extracted data such as the code diff, the autor, the message…</p>
<p>In this case, I used it to <strong>cherry pick a commit from a repo and add it to another</strong>, but this command is way more powerful than this.</p>
<p>You can find the whole documentation here <a href="https://git-scm.com/docs/git-am">https://git-scm.com/docs/git-am</a></p>
<h2 id="knowledge-required">knowledge required</h2>
<p>UNIX shell basic usage</p>
<h2 id="how-do-we-use-it">How do we use it?</h2>
<p>For this use case, you need to have the two repos locally.</p>
<p>Let’s say we have this architecture:</p>
<pre><code class="language-bash">$ demo ls
my-forked-repo   the-last-version-repo
</code></pre>
<p>Go to last version repo</p>
<p><code>$ cd the-last-version-repo/</code></p>
<p>Use the following line for each commit you want to import in the new version</p>
<pre><code class="language-bash">$ git --git-dir=../&lt;My-forked-repo&gt;/.git \
format-patch -k -1 --stdout &lt;your commit SHA&gt; | \
git am -3 -k
</code></pre>
<p>Here is the full explanation of the command: <a href="https://stackoverflow.com/a/9507417">https://stackoverflow.com/a/9507417</a></p>
<p>If there’s no conflicts, console will just display</p>
<p><code>$ Applying &lt;your commit message&gt;</code></p>
<p>And if you type in your console:</p>
<p><code>$ git status</code></p>
<p>You will see that you have a commit ahead of HEAD.</p>
<h2 id="ive-got-a-conflict">I’ve got a conflict</h2>
<p>If for some reason the commit cannot be merged automatically, console will display a message with the conflicting files list and a small explanation of the failure. In this case, you can get more information with:</p>
<p><code>$ git am --show-current-patch</code></p>
<h3 id="i-want-to-solve-it">I want to solve it</h3>
<p>To resolve the conflicts, go to the files, manually keep the code you want then go to your console and add them to git:</p>
<p><code>$ git add &lt;path/to/file&gt;</code></p>
<p>After adding the files to git, you will be able to do:</p>
<p><code>$ git am --continue</code></p>
<p>which will commit the files for you. A <code>$ git status</code> will produce the same result as before.</p>
<h3 id="i-do-not-want-to-solve-it">I do not want to solve it</h3>
<p>In the case you want to handle the conflict without git, you can just</p>
<p><code>$ git am --abort</code></p>
<p>Which will abort all the commits you’re trying to import. Or you can use:</p>
<p><code>$ git am --skip</code></p>
<p>which will abort the import of the current commit having a conflict. In both cases, the files will be left as they were before you typed the command.</p>
<p>Notice that you can’t use these commands to resolve a conflict triggered by another git command (e.g <code>git merge</code>…)</p>
<h2 id="i-imported-my-commit-so-what-now">I imported my commit, so what now?</h2>
<p>Basically, nothing, you’re all good to go, push the commits to your branch whenever you want with</p>
<p><code>$ git push &lt;your remote name&gt; &lt;your branch name&gt;</code></p>
<p>And that’s it. If for some reason you need to update the message of the commit, use</p>
<p><code>$ git commit --amend</code></p>
<p>Careful, it only works with the last commit. There are ways to do the same with an older commit, but it involves rewriting the tree and this will change the commit SHA as well, read the git documentation for more details.</p>
<p>If you made a lot of changes on a commit that wasn’t originally yours, it may be accurate to change the author to yourself, for this use</p>
<p><code>$ git commit --amend --author=&quot;Author Name &lt;email@address.com&gt;&quot;</code></p>
<p>On your last commit. For olders commit, it’s exactly as above.</p>
<p>Happy merging!</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>The story of clever-tools 1.0.0</title>
		<link>https://www.clever.cloud/blog/features/2018/10/15/the-story-of-clever-tools-1-0-0/</link>
		
		<dc:creator><![CDATA[Hubert Sablonnière]]></dc:creator>
		<pubDate>Mon, 15 Oct 2018 16:00:00 +0000</pubDate>
				<category><![CDATA[Features]]></category>
		<category><![CDATA[clever tools]]></category>
		<category><![CDATA[CLI]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[jenkins]]></category>
		<guid isPermaLink="false">https://www2.cleverapps.io/wp/blog/technology/2018/10/15/the-story-of-clever-tools-1-0-0/</guid>

					<description><![CDATA[<p><img width="1400" height="540" src="https://cdn.clever-cloud.com/uploads/2021/08/clever-tools-1-0-0-story-1.png" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="clever tools 1 0 0 story 1" decoding="async" srcset="https://cdn.clever-cloud.com/uploads/2021/08/clever-tools-1-0-0-story-1.png 1400w, https://cdn.clever-cloud.com/uploads/2021/08/clever-tools-1-0-0-story-1-300x116.png 300w, https://cdn.clever-cloud.com/uploads/2021/08/clever-tools-1-0-0-story-1-1024x395.png 1024w, https://cdn.clever-cloud.com/uploads/2021/08/clever-tools-1-0-0-story-1-768x296.png 768w, https://cdn.clever-cloud.com/uploads/2021/08/clever-tools-1-0-0-story-1-1368x528.png 1368w" sizes="(max-width: 1400px) 100vw, 1400px" /></p><p>We have some news about our dear clever-tools, the Clever Cloud CLI for humans, robots and others.</p>
<p>Back in January, we released version 0.10.1 which contained <a href="https://github.com/CleverCloud/clever-tools/blob/master/CHANGELOG.md#0101-2018-01-16">some new commands and a few bug fixes</a>. Today we&#39;re releasing version 1.0.0!</p>
<p>You can find all the details about the user oriented changes in the <a href="https://github.com/CleverCloud/clever-tools/blob/master/CHANGELOG.md#100-2018-10-15">CHANGELOG</a>. In this article, we&#39;ll cover the structural changes related to the project:</p>
<ul>
<li>Improved error handling = better usage in CI/CD pipelines</li>
<li>ES2015 refactor + ESLint = better contributing experience</li>
<li>Goodbye nodegit, hello isomorphic-git!</li>
<li>Improved Jenkins build = more release methods and beta channel (npm, deb, rpm, arch, homebrew and chocolatey)</li>
</ul>
<p>Now let me tell you the story of this release...</p>
<span id="more-2945"></span>

<h2 id="improved-error-handling--better-usage-in-cicd-pipelines">Improved error handling = better usage in CI/CD pipelines</h2>
<p>It all started with <a href="https://github.com/CleverCloud/clever-tools/issues/195">issue 195</a>. With the previous version, when a user tried to <code>clever deploy</code> a non existing branch, the error message was <a href="https://github.com/CleverCloud/clever-tools/issues/195#issuecomment-371101087">a bit too raw</a>. Our colleague <a href="https://twitter.com/k33g_org">Philippe</a> wanted emojis, but we had to decline 😭.</p>
<p>While investigating this error message and how to improve it, we realized that the command was not returning a proper error exit status. This is a very important rule to follow when you&#39;re writing a terminal command/script:</p>
<ul>
<li>If everything went well: return exit status <code>0</code></li>
<li>If something went wrong: return exit status <code>1</code> (or <a href="http://www.tldp.org/LDP/abs/html/exitcodes.html">another one more specific</a>)</li>
</ul>
<p>Our CLI is built for humans AND robots. We want to empower our terminal addicted users. Moreover, we want to empower our users to automate anything related to Clever Cloud in their CI/CD pipeline. In such cases, returning a proper error exit status is crucial to mark a CI/CD job as successful or failed.</p>
<p>We took a quick look at the rest of the codebase and noticed that other commands were concerned by this problem. We reworked the code of <code>clever deploy</code> and decided to apply the same error handling to all the other commands:</p>
<ul>
<li>For humans: Always have an <code>[ERROR]</code> prefix (colored in red) when we display an error message.</li>
<li>For robots: Always log error messages to <code>stderr</code>.</li>
<li>For robots: Always return <code>1</code> as exit status when something goes wrong.</li>
</ul>
<p>This obviously required some significant work. We had to go through all the 28 commands. Nevertheless, we&#39;re sure this will help you to build more robust and automated jobs, interacting with the Clever Cloud platform.</p>
<h2 id="es2015-refactor--eslint--better-contributing-experience">ES2015 refactor + ESLint = better contributing experience</h2>
<p>While we went through the codebase, we took the opportunity to do a bit of refactoring.</p>
<p>First, we decided to update all our code to ES2015+ syntax. We tried to use most of the modern good practices of the JavaScript ecosystem when it made sense (destructuring, rest parameters, fat arrows...). We also decided to setup an <a href="https://github.com/CleverCloud/clever-tools/blob/master/.eslintrc.js">ESLint config file</a> (based on <a href="https://github.com/standard/standard">StandardJS</a>) to enforce our codestyle for the next iterations. This lint check is now part of our automated test suite on <a href="https://travis-ci.org/CleverCloud/clever-tools">Travis CI</a> which is triggered on each pull-request.</p>
<p>This big refactoring took some time but it allowed us to reduce the number of lines of code while improving the overall consistency and readability. We hope this will drive more users to customize the clever-tools and propose pull-requests for bug fixes and feature requests.</p>
<p>Looking at how easily the new <code>clever console</code> was added by <a href="https://github.com/CleverCloud/clever-tools/pull/248">an outside contributor</a>, it seems like we&#39;re on the right path.</p>
<h2 id="goodbye-nodegit">Goodbye nodegit</h2>
<p>During this refactoring, we updated most of our dependencies but one of them required special attention: <a href="https://github.com/nodegit/nodegit">NodeGit</a>.</p>
<p>NodeGit is a node.js module which provides bindings over <a href="https://libgit2.org/">libgit2</a>, a portable and pure C implementation of the git core methods. This is commonly referred to as a &quot;native module&quot; in the node.js ecosystem. Each time you install NodeGit, npm will try to build libgit2 from source (in some situations, it will try to download pre-builds). Because we want our users to have a straightforward installation experience, this native module thing had many drawbacks:</p>
<ul>
<li>In our Travis CI config, we had to <a href="https://github.com/CleverCloud/clever-tools/blob/0.10.0/.travis.yml#L5-L10">install some native dependencies</a> so npm could build libgit2 from source.</li>
<li>Some of our users tried to install the clever-tools via npm and had difficulties because of this.</li>
<li>Our users don&#39;t all use the same version of node.js, this also multiplied the number of weird cases we had to support.</li>
<li>Each time we wanted to update NodeGit, it was a puzzle to resolve.</li>
</ul>
<p>All those problems pushed us to stop releasing the clever-tools via npm. ⚠️ SPOILER ALERT: it&#39;s back for version 1.0.0.</p>
<p>The second problem is that we use <a href="https://github.com/zeit/pkg/">pkg</a>. This tool is great. It lets you compile a node.js project to different portable binaries for MacOS, Windows and GNU/Linux. We&#39;ve been using it for a while to provide various installation methods. When we dropped installation via npm for 0.10.1, the binaries built with pkg where the only official way to use the clever-tools.</p>
<p>This mix of NodeGit + pkg complicated the situation even more:</p>
<ul>
<li>The binaries built with pkg did not include native dependencies. We had to distribute the right <code>nodegit.node</code> file along with our main binary file and users had to have it in their <code>PATH</code>.</li>
<li>On some GNU/Linux distributions, we <a href="https://github.com/nodegit/nodegit/search?q=libcurl-gnutls&amp;type=Issues">had problems with native dependencies</a> like <code>libcurl-gnutls</code>, <code>libnghttp2-git</code> or <code>libssh2</code></li>
</ul>
<p>We already looked for a replacement solution but a fairly new one came up <a href="https://twitter.com/mojavelinux/status/968432035680284673">to our attention in February</a>...</p>
<h2 id="hello-isomorphic-git">Hello isomorphic-git</h2>
<p><a href="https://github.com/isomorphic-git/isomorphic-git">isomorphic-git</a> is a &quot;pure JavaScript implementation of git that works in node and browser environments&quot;. The project was created by <a href="https://twitter.com/wmhilton">William Hilton</a>. This guy is awesome!</p>
<p>As I said, we heard about it in February but we only tried to use it for our use-case in July. It does not support all the git feature set (yet) but it has strong foundations and we only need a few things for the clever-tools:</p>
<ul>
<li>list and resolve branches</li>
<li>list and resolve commits</li>
<li>add/rm remotes</li>
<li>push to repos (via HTTPS)</li>
</ul>
<p>We were missing a few features so we reached out to William to see if we could help and/or contribute. In the end, <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=hsablonniere">we contributed a few features</a> in the codebase and <a href="https://isomorphic-git.org/blog/2018/07/23/isomorphic-git-dot-org-corporate-sponsors-and-a-new-CORS-proxy">we sponsored the hosting of the CORS-proxy</a> which is used to demo the project in browsers.</p>
<p>By replacing NodeGit with isomorphic-git, we can say goodbye to most of the problems we described earlier:</p>
<ul>
<li>no need to worry about different node.js versions</li>
<li>no need to have native dependencies on our CI server to build/test the project</li>
<li>no need to distribute <code>nodegit.node</code> with the pkg binary</li>
<li>no need for a user to have the right native dependencies on his/her system to use it</li>
<li>npm releases are back</li>
</ul>
<h2 id="improved-jenkins-build--more-release-methods-and-beta-channel">Improved Jenkins build = more release methods and beta channel</h2>
<p>Because we decided to ditch NodeGit, we were able to simplify the packaging (no more <code>nodegit.node</code> and no native dependencies). This required some rework on our Jenkins builds but it was a good occasion to migrate 5 different jobs to one multibranch pipeline project.</p>
<p>We&#39;ll discuss the details of this new build system and how we set it up in another article. For now though, here&#39;s what changed:</p>
<ul>
<li>All platforms:<ul>
<li>Added: installation via npm is back</li>
</ul>
</li>
<li>GNU/Linux:<ul>
<li>Added: rpm packages (stable &amp; beta) with a <a href="https://bintray.com/clevercloud/rpm/clever-tools">yum repo on Bintray</a></li>
<li>Added: deb packages (stable &amp; beta) with an <a href="https://bintray.com/clevercloud/deb/clever-tools">apt repo on Bintray</a></li>
<li>Added: Exherbo exheres <a href="https://github.com/CleverCloud/CleverCloud-exheres/tree/master/packages/dev-util/clever-tools-bin">clever-tools-bin</a></li>
<li>Changed: <a href="https://aur.archlinux.org/packages/clever-tools-bin">dedicated AUR package</a> for beta versions</li>
</ul>
</li>
<li>MacOS:<ul>
<li>Changed: <a href="https://github.com/CleverCloud/homebrew-tap">dedicated homebrew tap</a> for beta versions</li>
</ul>
</li>
<li>Windows:<ul>
<li>Changed: chocolatey packages are now automatically published (no more manual upload, no more manual review from chocolatey&#39;s team)</li>
<li>Changed: chocolatey packages are no longer published on chocolatey.org</li>
<li>Changed: chocolatey packages are published (beta &amp; stable) on <a href="https://bintray.com/clevercloud/nupkg/clever-tools">Bintray</a> but you can still use the <code>choco</code> CLI to install them (see <a href="https://github.com/CleverCloud/clever-tools/#using-chocolatey">chocolatey installation docs</a> for the specifics).</li>
</ul>
</li>
</ul>
<p>The structural changes of this release will help us to be more reactive on the project. We&#39;ll be able to publish new features and bug fixes more easily and more often. Hopefully, this will improve the quality of the clever-tools and push more customers who only use the Web console to try the CLI way of life.</p>
<p>We&#39;re looking forward to read your feedbacks on this release through are usual channels: support chat, email, Twitter, GitHub issues…</p>
]]></description>
										<content:encoded><![CDATA[<p><img width="1400" height="540" src="https://cdn.clever-cloud.com/uploads/2021/08/clever-tools-1-0-0-story-1.png" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="clever tools 1 0 0 story 1" decoding="async" loading="lazy" srcset="https://cdn.clever-cloud.com/uploads/2021/08/clever-tools-1-0-0-story-1.png 1400w, https://cdn.clever-cloud.com/uploads/2021/08/clever-tools-1-0-0-story-1-300x116.png 300w, https://cdn.clever-cloud.com/uploads/2021/08/clever-tools-1-0-0-story-1-1024x395.png 1024w, https://cdn.clever-cloud.com/uploads/2021/08/clever-tools-1-0-0-story-1-768x296.png 768w, https://cdn.clever-cloud.com/uploads/2021/08/clever-tools-1-0-0-story-1-1368x528.png 1368w" sizes="auto, (max-width: 1400px) 100vw, 1400px" /></p><p>We have some news about our dear clever-tools, the Clever Cloud CLI for humans, robots and others.</p>
<p>Back in January, we released version 0.10.1 which contained <a href="https://github.com/CleverCloud/clever-tools/blob/master/CHANGELOG.md#0101-2018-01-16">some new commands and a few bug fixes</a>. Today we&#39;re releasing version 1.0.0!</p>
<p>You can find all the details about the user oriented changes in the <a href="https://github.com/CleverCloud/clever-tools/blob/master/CHANGELOG.md#100-2018-10-15">CHANGELOG</a>. In this article, we&#39;ll cover the structural changes related to the project:</p>
<ul>
<li>Improved error handling = better usage in CI/CD pipelines</li>
<li>ES2015 refactor + ESLint = better contributing experience</li>
<li>Goodbye nodegit, hello isomorphic-git!</li>
<li>Improved Jenkins build = more release methods and beta channel (npm, deb, rpm, arch, homebrew and chocolatey)</li>
</ul>
<p>Now let me tell you the story of this release...</p>
<span id="more-2945"></span>

<h2 id="improved-error-handling--better-usage-in-cicd-pipelines">Improved error handling = better usage in CI/CD pipelines</h2>
<p>It all started with <a href="https://github.com/CleverCloud/clever-tools/issues/195">issue 195</a>. With the previous version, when a user tried to <code>clever deploy</code> a non existing branch, the error message was <a href="https://github.com/CleverCloud/clever-tools/issues/195#issuecomment-371101087">a bit too raw</a>. Our colleague <a href="https://twitter.com/k33g_org">Philippe</a> wanted emojis, but we had to decline 😭.</p>
<p>While investigating this error message and how to improve it, we realized that the command was not returning a proper error exit status. This is a very important rule to follow when you&#39;re writing a terminal command/script:</p>
<ul>
<li>If everything went well: return exit status <code>0</code></li>
<li>If something went wrong: return exit status <code>1</code> (or <a href="http://www.tldp.org/LDP/abs/html/exitcodes.html">another one more specific</a>)</li>
</ul>
<p>Our CLI is built for humans AND robots. We want to empower our terminal addicted users. Moreover, we want to empower our users to automate anything related to Clever Cloud in their CI/CD pipeline. In such cases, returning a proper error exit status is crucial to mark a CI/CD job as successful or failed.</p>
<p>We took a quick look at the rest of the codebase and noticed that other commands were concerned by this problem. We reworked the code of <code>clever deploy</code> and decided to apply the same error handling to all the other commands:</p>
<ul>
<li>For humans: Always have an <code>[ERROR]</code> prefix (colored in red) when we display an error message.</li>
<li>For robots: Always log error messages to <code>stderr</code>.</li>
<li>For robots: Always return <code>1</code> as exit status when something goes wrong.</li>
</ul>
<p>This obviously required some significant work. We had to go through all the 28 commands. Nevertheless, we&#39;re sure this will help you to build more robust and automated jobs, interacting with the Clever Cloud platform.</p>
<h2 id="es2015-refactor--eslint--better-contributing-experience">ES2015 refactor + ESLint = better contributing experience</h2>
<p>While we went through the codebase, we took the opportunity to do a bit of refactoring.</p>
<p>First, we decided to update all our code to ES2015+ syntax. We tried to use most of the modern good practices of the JavaScript ecosystem when it made sense (destructuring, rest parameters, fat arrows...). We also decided to setup an <a href="https://github.com/CleverCloud/clever-tools/blob/master/.eslintrc.js">ESLint config file</a> (based on <a href="https://github.com/standard/standard">StandardJS</a>) to enforce our codestyle for the next iterations. This lint check is now part of our automated test suite on <a href="https://travis-ci.org/CleverCloud/clever-tools">Travis CI</a> which is triggered on each pull-request.</p>
<p>This big refactoring took some time but it allowed us to reduce the number of lines of code while improving the overall consistency and readability. We hope this will drive more users to customize the clever-tools and propose pull-requests for bug fixes and feature requests.</p>
<p>Looking at how easily the new <code>clever console</code> was added by <a href="https://github.com/CleverCloud/clever-tools/pull/248">an outside contributor</a>, it seems like we&#39;re on the right path.</p>
<h2 id="goodbye-nodegit">Goodbye nodegit</h2>
<p>During this refactoring, we updated most of our dependencies but one of them required special attention: <a href="https://github.com/nodegit/nodegit">NodeGit</a>.</p>
<p>NodeGit is a node.js module which provides bindings over <a href="https://libgit2.org/">libgit2</a>, a portable and pure C implementation of the git core methods. This is commonly referred to as a &quot;native module&quot; in the node.js ecosystem. Each time you install NodeGit, npm will try to build libgit2 from source (in some situations, it will try to download pre-builds). Because we want our users to have a straightforward installation experience, this native module thing had many drawbacks:</p>
<ul>
<li>In our Travis CI config, we had to <a href="https://github.com/CleverCloud/clever-tools/blob/0.10.0/.travis.yml#L5-L10">install some native dependencies</a> so npm could build libgit2 from source.</li>
<li>Some of our users tried to install the clever-tools via npm and had difficulties because of this.</li>
<li>Our users don&#39;t all use the same version of node.js, this also multiplied the number of weird cases we had to support.</li>
<li>Each time we wanted to update NodeGit, it was a puzzle to resolve.</li>
</ul>
<p>All those problems pushed us to stop releasing the clever-tools via npm. ⚠️ SPOILER ALERT: it&#39;s back for version 1.0.0.</p>
<p>The second problem is that we use <a href="https://github.com/zeit/pkg/">pkg</a>. This tool is great. It lets you compile a node.js project to different portable binaries for MacOS, Windows and GNU/Linux. We&#39;ve been using it for a while to provide various installation methods. When we dropped installation via npm for 0.10.1, the binaries built with pkg where the only official way to use the clever-tools.</p>
<p>This mix of NodeGit + pkg complicated the situation even more:</p>
<ul>
<li>The binaries built with pkg did not include native dependencies. We had to distribute the right <code>nodegit.node</code> file along with our main binary file and users had to have it in their <code>PATH</code>.</li>
<li>On some GNU/Linux distributions, we <a href="https://github.com/nodegit/nodegit/search?q=libcurl-gnutls&amp;type=Issues">had problems with native dependencies</a> like <code>libcurl-gnutls</code>, <code>libnghttp2-git</code> or <code>libssh2</code></li>
</ul>
<p>We already looked for a replacement solution but a fairly new one came up <a href="https://twitter.com/mojavelinux/status/968432035680284673">to our attention in February</a>...</p>
<h2 id="hello-isomorphic-git">Hello isomorphic-git</h2>
<p><a href="https://github.com/isomorphic-git/isomorphic-git">isomorphic-git</a> is a &quot;pure JavaScript implementation of git that works in node and browser environments&quot;. The project was created by <a href="https://twitter.com/wmhilton">William Hilton</a>. This guy is awesome!</p>
<p>As I said, we heard about it in February but we only tried to use it for our use-case in July. It does not support all the git feature set (yet) but it has strong foundations and we only need a few things for the clever-tools:</p>
<ul>
<li>list and resolve branches</li>
<li>list and resolve commits</li>
<li>add/rm remotes</li>
<li>push to repos (via HTTPS)</li>
</ul>
<p>We were missing a few features so we reached out to William to see if we could help and/or contribute. In the end, <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=hsablonniere">we contributed a few features</a> in the codebase and <a href="https://isomorphic-git.org/blog/2018/07/23/isomorphic-git-dot-org-corporate-sponsors-and-a-new-CORS-proxy">we sponsored the hosting of the CORS-proxy</a> which is used to demo the project in browsers.</p>
<p>By replacing NodeGit with isomorphic-git, we can say goodbye to most of the problems we described earlier:</p>
<ul>
<li>no need to worry about different node.js versions</li>
<li>no need to have native dependencies on our CI server to build/test the project</li>
<li>no need to distribute <code>nodegit.node</code> with the pkg binary</li>
<li>no need for a user to have the right native dependencies on his/her system to use it</li>
<li>npm releases are back</li>
</ul>
<h2 id="improved-jenkins-build--more-release-methods-and-beta-channel">Improved Jenkins build = more release methods and beta channel</h2>
<p>Because we decided to ditch NodeGit, we were able to simplify the packaging (no more <code>nodegit.node</code> and no native dependencies). This required some rework on our Jenkins builds but it was a good occasion to migrate 5 different jobs to one multibranch pipeline project.</p>
<p>We&#39;ll discuss the details of this new build system and how we set it up in another article. For now though, here&#39;s what changed:</p>
<ul>
<li>All platforms:<ul>
<li>Added: installation via npm is back</li>
</ul>
</li>
<li>GNU/Linux:<ul>
<li>Added: rpm packages (stable &amp; beta) with a <a href="https://bintray.com/clevercloud/rpm/clever-tools">yum repo on Bintray</a></li>
<li>Added: deb packages (stable &amp; beta) with an <a href="https://bintray.com/clevercloud/deb/clever-tools">apt repo on Bintray</a></li>
<li>Added: Exherbo exheres <a href="https://github.com/CleverCloud/CleverCloud-exheres/tree/master/packages/dev-util/clever-tools-bin">clever-tools-bin</a></li>
<li>Changed: <a href="https://aur.archlinux.org/packages/clever-tools-bin">dedicated AUR package</a> for beta versions</li>
</ul>
</li>
<li>MacOS:<ul>
<li>Changed: <a href="https://github.com/CleverCloud/homebrew-tap">dedicated homebrew tap</a> for beta versions</li>
</ul>
</li>
<li>Windows:<ul>
<li>Changed: chocolatey packages are now automatically published (no more manual upload, no more manual review from chocolatey&#39;s team)</li>
<li>Changed: chocolatey packages are no longer published on chocolatey.org</li>
<li>Changed: chocolatey packages are published (beta &amp; stable) on <a href="https://bintray.com/clevercloud/nupkg/clever-tools">Bintray</a> but you can still use the <code>choco</code> CLI to install them (see <a href="https://github.com/CleverCloud/clever-tools/#using-chocolatey">chocolatey installation docs</a> for the specifics).</li>
</ul>
</li>
</ul>
<p>The structural changes of this release will help us to be more reactive on the project. We&#39;ll be able to publish new features and bug fixes more easily and more often. Hopefully, this will improve the quality of the clever-tools and push more customers who only use the Web console to try the CLI way of life.</p>
<p>We&#39;re looking forward to read your feedbacks on this release through are usual channels: support chat, email, Twitter, GitHub issues…</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How We Cut Latency Down by 30k% on Our Git Server</title>
		<link>https://www.clever.cloud/blog/engineering/2015/06/09/git-server-30k-improvement/</link>
		
		<dc:creator><![CDATA[Marc-Antoine Perennou]]></dc:creator>
		<pubDate>Tue, 09 Jun 2015 14:01:00 +0000</pubDate>
				<category><![CDATA[Engineering]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[performances]]></category>
		<guid isPermaLink="false">https://www2.cleverapps.io/wp/blog/technology/2015/06/09/git-server-30k-improvement/</guid>

					<description><![CDATA[<p><img width="1400" height="540" src="https://cdn.clever-cloud.com/uploads/2021/08/ars-perfs-1.jpg" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="ars perfs 1" decoding="async" loading="lazy" srcset="https://cdn.clever-cloud.com/uploads/2021/08/ars-perfs-1.jpg 1400w, https://cdn.clever-cloud.com/uploads/2021/08/ars-perfs-1-300x116.jpg 300w, https://cdn.clever-cloud.com/uploads/2021/08/ars-perfs-1-1024x395.jpg 1024w, https://cdn.clever-cloud.com/uploads/2021/08/ars-perfs-1-768x296.jpg 768w, https://cdn.clever-cloud.com/uploads/2021/08/ars-perfs-1-1368x528.jpg 1368w" sizes="auto, (max-width: 1400px) 100vw, 1400px" /></p><p>At Clever Cloud, git is core in the deployment process. With the years, our needs evolved, especially in terms of performance. This is how we dealt with it.</p>
<span id="more-2800"></span>

<h3 id="a-bit-of-background-first">A bit of background first</h3>
<p>When we started Clever Cloud, we chose to use <a href="http://gitolite.com/gitolite/index.html">gitolite</a> to manage our git repositories. It appeared to be a complete and functional solution. We used it internally first, for managing our own internal repositories even when the product wasn&#39;t released yet.</p>
<p>After several months of testing, we were convinced that it was a really good solution, and chose to go with it.</p>
<h4 id="managing-gitolite-configuration">Managing gitolite configuration</h4>
<p>gitolite is primary designed to be configured manually by a sysadmin. You have to describe every user, group of users and repositories in configuration files. Gitolite will then use these files to generate its internal stuff for dealing with access rights. Gitolite ensures repositories are created and hooks propagated.</p>
<p>We were in the need of automatic reconfiguration, for quite obvious reasons. We therefore developed a tool called <code>etilotig</code> which aim was to keep gitolite&#39;s configuration up to date. On startup, this tool would load its initial configuration from the API and then listen to <a href="https://www.amqp.org/">AMQP</a> events to update its configuration cache and write the new gitolite configuration accordingly.</p>
<p>It worked actually quite well for longer than we expected, and we used it in production until May 5th 2015.</p>
<h3 id="the-drawbacks-of-gitolite">The drawbacks of gitolite</h3>
<p>gitolite was great but had a few major drawbacks we weren&#39;t happy with.</p>
<ul>
<li>as previously said, it wasn&#39;t meant to be dynamically configurable, which required some non-trivial hacks</li>
<li>it required duplicating the configuration in both the API and gitolite itself</li>
<li>all the repositories were created in a same directory</li>
<li>at each repository creation, it did a full pass on all the repositories to check whether the hooks were up to date
or not</li>
<li>rewriting only part of its configuration wasn&#39;t trivial at all so we ended up rewriting most of it for each change</li>
</ul>
<p>Those problems were annoying but not critical at the beginning, but some of them became quite interesting to us.</p>
<p>The fact that all repositories are created in a same directory is a huge performance issue when the number of repositories become really high.</p>
<p>We actually dropped the part that checks for the hooks at each repository creation from the gitolite code a while back as it took half the time of the gitolite internal configuration regeneration on update.</p>
<h3 id="the-new-etilotig">The new etilotig</h3>
<p>While gitolite used to be efficient, lately it had a lot of performance problems, and depending on the timing of the events, it could take up to five (!) minutes to create a new repository. This went far beyond our acceptable limits, so we had to find another solution.</p>
<p>The idea was simple: drop gitolite totally and improve our configuration management tool etilotig to do what was needed by itself.</p>
<p>The checklist we needed to accomplish was quite tiny:</p>
<ul>
<li>ssh keys management</li>
<li>authorization management</li>
<li>repositories creation</li>
<li>hooks installation</li>
</ul>
<h4 id="ssh-keys-management">Ssh keys management</h4>
<p>When its goal was only to manage the gitolite configuration, etilotig only forwarded them to gitolite, which in turn handled them. Now, we have to manage the <code>authorized_keys</code> file by ourselves.</p>
<p>We basically write a new one each time an ssh key is added or removed and then replace the old one with the new one.</p>
<p>Each line is printed as such:</p>
<pre><code class="language-text">command=&quot;AUTH_SCRIPT USER_ID&quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty PUBLIC_KEY
</code></pre>
<p>With <code>AUTH_SCRIPT</code> pointing to the authorization script that I&#39;ll speak of later, <code>USER_ID</code> being the user id of the key owner and <code>PUBLIC_KEY</code> being their public key.</p>
<p>The authorization script will thus be called with the user id as first parameter.</p>
<p>That solves the first point of our TODO list.</p>
<h4 id="repositories-management">Repositories management</h4>
<p>At startup, etilotig writes some bash configuration files defining basic things such as the directory in which repositories have to be created.</p>
<p>Then we have a tiny bash script in charge of the repository creation. We now create them in a deeper directory hierarchy to get as few repositories as possible per directory. Say we had <code>/data/app_18c6021b-0860-4f97-a08d-0663f45cf3f0.git</code> before, we now have <code>/data/app_18/c6/02/app_18c6021b-0860-4f97-a08d-0663f45cf3f0.git</code> which makes things waaaaay faster.</p>
<pre><code class="language-bash">#!/bin/bash

create_repo() {
local repo_dir=&quot;${1}&quot;

mkdir -p &quot;${repo_dir}&quot;
pushd &quot;${repo_dir}&quot; &amp;&gt;/dev/null
git init --bare
popd &amp;&gt;/dev/null
}

main() {
    local repo=&quot;${1}&quot;
    local repo_dir
    
repo_dir=&quot;${REPOS_DIR}/${repo:0:6}/${repo:6:2}/${repo:8:2}/${repo}&quot;

[[ -d &quot;${repo_dir}&quot;/hooks ]] || create_repo &quot;${repo_dir}&quot;
}

. &quot;${HOME}&quot;/.etilotig/.etilotigrc

main &quot;${@}&quot;
</code></pre>
<p>Then we have another one for hooks installation.</p>
<pre><code class="language-bash">#!/bin/bash

shopt -s nullglob

main() {
    local repo=&quot;${1}&quot;
    local repo_dir
    
repo_dir=&quot;${REPOS_DIR}/${repo:0:6}/${repo:6:2}/${repo:8:2}/${repo}&quot;

for hook in ${HOME}/.etilotig/hooks/*; do
ln -sf &quot;${hook}&quot; &quot;${repo_dir}&quot;/hooks/
done
}

. &quot;${HOME}&quot;/.etilotig/.etilotigrc

main &quot;${@}&quot;
</code></pre>
<p>With those two simple scripts, we only have to call them for each repository at startup to ensure everything is OK, and at each repository creation, which solves two of the four points from our TODO list, only one left.</p>
<h4 id="authorization">Authorization</h4>
<p>Now, the goal was <em>not</em> to duplicate the configuration anymore, but rather use the configuration from the API, thus externalising the whole thing. Dropping all the configuration management from etilotig reduced its size by more than 50%.</p>
<p>The way etilotig manages authorization is quite simple: when it generates its internal configuration, it actually generates a perl script which is called on each ssh connection attempt. The script then authorises or not the transaction.</p>
<p>We made a similar script which prints the users some information about which repositories they have access to if they just run <code>ssh git@push.par.clever-cloud.com</code> or such, checks if they are authorized when they try to git push/pull or rejects any other request.</p>
<p>It looks like this (with some extra stuff added):</p>
<pre><code class="language-bash">#!/bin/bash

sanity_check() {
    if [[ -z &quot;${SSH_CONNECTION}&quot; ]]; then
        echo &quot;Who the hell are you?&quot; &gt;&amp;2
        exit 1
    fi

if [[ -z &quot;${SSH_ORIGINAL_COMMAND}&quot; ]]; then
        export SSH_ORIGINAL_COMMAND=&quot;info&quot;
fi
}

ask_for_info() {
    local userid=&quot;${1}&quot;

# make the request to the API to retrieve user info message
echo &quot;some info&quot;
}

ask_for_authorization() {
    local userid=&quot;${1}&quot;
    local appid=&quot;${2}&quot;

# make the request and return the HTTP status code here. 200 means authorized.
echo 200
}

authorize() {
    local userid=&quot;${1}&quot;
    local verb=&quot;${2}&quot;
    local appid=&quot;${3}&quot;
    local ret=1
    case &quot;${verb}&quot; in
        &quot;git-receive-pack&quot;|&quot;git-upload-pack&quot;)
            local code
            code=$(ask_for_authorization &quot;${userid}&quot; &quot;${appid}&quot;)
            [[ &quot;${code}&quot; == &quot;200&quot; ]] &amp;&amp; ret=0
            ;;
    esac
    return &quot;${ret}&quot;
}

final_abort() {
    echo &quot;What are you trying to achieve here?&quot; &gt;&amp;2
    exit 2
}
main() {
    sanity_check
    local userid=&quot;${1}&quot;
    local verb
    local repo
    local repo_dir
    verb=$(echo &quot;${SSH_ORIGINAL_COMMAND}&quot; | cut -d &#39; &#39; -f 1)
    repo=$(echo &quot;${SSH_ORIGINAL_COMMAND}&quot; | cut -d &#39; &#39; -f 2 | tr -d &quot;&#39;\&quot;&quot;)
    [[ &quot;${repo}&quot; == /* ]] &amp;&amp; repo=${repo:1}
    repo_dir=&quot;${REPOS_DIR}/${repo:0:6}/${repo:6:2}/${repo:8:2}/${repo}&quot;
    if [[ &quot;${verb}&quot; == &quot;info&quot; ]]; then
        ask_for_info &quot;${userid}&quot;
    elif authorize &quot;${userid}&quot; &quot;${verb}&quot; &quot;${repo}&quot;; then
        export CC_USER=&quot;${userid}&quot;
        export CC_NOTIFY_SCRIPT=&quot;${HOME}/.etilotig/send-push-event&quot;
        exec &quot;${verb}&quot; &quot;${repo_dir}&quot;
    else
        final_abort
    fi
}
. &quot;${HOME}&quot;/.etilotig/.etilotigrc
main &quot;${@}&quot;
</code></pre>
<p>With this in place, nearly everything was ready. Two tiny hooks on top of that to only allow users to push on the master branch, and to trigger a deployment on git push:</p>
<p><code>hooks/update</code></p>
<pre><code class="language-bash">#!/bin/bash

main() {
    local rev=&quot;${1}&quot;
    if [[ &quot;${rev}&quot; == refs/tags/* ]]; then
        exit 0
    fi
    if [[ &quot;${rev}&quot; != &quot;refs/heads/master&quot; ]]; then
        echo &quot;You tried to push to a custom branch.&quot;
        echo &quot;Only master is allowed.&quot;
        exit 1
    fi
}

main &quot;${@}&quot;
</code></pre>
<p><code>hooks/post-update</code></p>
<pre><code class="language-bash">#!/bin/bash

sanity_check() {
    local rev=&quot;${1}&quot;
    if [[ &quot;${rev}&quot; == refs/tags/* ]]; then
        exit 0
    fi
}

main () {
    local rev=&quot;${1}&quot;
    sanity_check &quot;${rev}&quot;
    local repo=$(basename $(pwd))
    local appId=${repo/.git/}
    local commitId=$(git rev-parse &quot;${rev}&quot;)
    &quot;${CC_NOTIFY_SCRIPT}&quot; &quot;${appId}&quot; &quot;${commitId}&quot; &quot;${CC_USER}&quot;
    echo &quot;[SUCCESS] The application has successfully been queued for redeploy.&quot;
}

main &quot;${@}&quot;
</code></pre>
<h3 id="conclusion">Conclusion</h3>
<p>That&#39;s it, we have our new git server manager up and running, which works pretty well.</p>
<p>The performance gain? We went from between 3 and more than 5 minutes to less than 1 second per action, while dropping the whole gitolite codebase and reducing the size of etilotig by 50%, with an average performance gain of 30k%.</p>
<p>gitolite has been very useful both in its utilisation and its codebase to better comprehend the whole authentication mechanism, so a great thanks to this awesome tool.</p>
<p>Now gitolite is dead, long live etilotig!</p>
]]></description>
										<content:encoded><![CDATA[<p><img width="1400" height="540" src="https://cdn.clever-cloud.com/uploads/2021/08/ars-perfs-1.jpg" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="ars perfs 1" decoding="async" loading="lazy" srcset="https://cdn.clever-cloud.com/uploads/2021/08/ars-perfs-1.jpg 1400w, https://cdn.clever-cloud.com/uploads/2021/08/ars-perfs-1-300x116.jpg 300w, https://cdn.clever-cloud.com/uploads/2021/08/ars-perfs-1-1024x395.jpg 1024w, https://cdn.clever-cloud.com/uploads/2021/08/ars-perfs-1-768x296.jpg 768w, https://cdn.clever-cloud.com/uploads/2021/08/ars-perfs-1-1368x528.jpg 1368w" sizes="auto, (max-width: 1400px) 100vw, 1400px" /></p><p>At Clever Cloud, git is core in the deployment process. With the years, our needs evolved, especially in terms of performance. This is how we dealt with it.</p>
<span id="more-2800"></span>

<h3 id="a-bit-of-background-first">A bit of background first</h3>
<p>When we started Clever Cloud, we chose to use <a href="http://gitolite.com/gitolite/index.html">gitolite</a> to manage our git repositories. It appeared to be a complete and functional solution. We used it internally first, for managing our own internal repositories even when the product wasn&#39;t released yet.</p>
<p>After several months of testing, we were convinced that it was a really good solution, and chose to go with it.</p>
<h4 id="managing-gitolite-configuration">Managing gitolite configuration</h4>
<p>gitolite is primary designed to be configured manually by a sysadmin. You have to describe every user, group of users and repositories in configuration files. Gitolite will then use these files to generate its internal stuff for dealing with access rights. Gitolite ensures repositories are created and hooks propagated.</p>
<p>We were in the need of automatic reconfiguration, for quite obvious reasons. We therefore developed a tool called <code>etilotig</code> which aim was to keep gitolite&#39;s configuration up to date. On startup, this tool would load its initial configuration from the API and then listen to <a href="https://www.amqp.org/">AMQP</a> events to update its configuration cache and write the new gitolite configuration accordingly.</p>
<p>It worked actually quite well for longer than we expected, and we used it in production until May 5th 2015.</p>
<h3 id="the-drawbacks-of-gitolite">The drawbacks of gitolite</h3>
<p>gitolite was great but had a few major drawbacks we weren&#39;t happy with.</p>
<ul>
<li>as previously said, it wasn&#39;t meant to be dynamically configurable, which required some non-trivial hacks</li>
<li>it required duplicating the configuration in both the API and gitolite itself</li>
<li>all the repositories were created in a same directory</li>
<li>at each repository creation, it did a full pass on all the repositories to check whether the hooks were up to date
or not</li>
<li>rewriting only part of its configuration wasn&#39;t trivial at all so we ended up rewriting most of it for each change</li>
</ul>
<p>Those problems were annoying but not critical at the beginning, but some of them became quite interesting to us.</p>
<p>The fact that all repositories are created in a same directory is a huge performance issue when the number of repositories become really high.</p>
<p>We actually dropped the part that checks for the hooks at each repository creation from the gitolite code a while back as it took half the time of the gitolite internal configuration regeneration on update.</p>
<h3 id="the-new-etilotig">The new etilotig</h3>
<p>While gitolite used to be efficient, lately it had a lot of performance problems, and depending on the timing of the events, it could take up to five (!) minutes to create a new repository. This went far beyond our acceptable limits, so we had to find another solution.</p>
<p>The idea was simple: drop gitolite totally and improve our configuration management tool etilotig to do what was needed by itself.</p>
<p>The checklist we needed to accomplish was quite tiny:</p>
<ul>
<li>ssh keys management</li>
<li>authorization management</li>
<li>repositories creation</li>
<li>hooks installation</li>
</ul>
<h4 id="ssh-keys-management">Ssh keys management</h4>
<p>When its goal was only to manage the gitolite configuration, etilotig only forwarded them to gitolite, which in turn handled them. Now, we have to manage the <code>authorized_keys</code> file by ourselves.</p>
<p>We basically write a new one each time an ssh key is added or removed and then replace the old one with the new one.</p>
<p>Each line is printed as such:</p>
<pre><code class="language-text">command=&quot;AUTH_SCRIPT USER_ID&quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty PUBLIC_KEY
</code></pre>
<p>With <code>AUTH_SCRIPT</code> pointing to the authorization script that I&#39;ll speak of later, <code>USER_ID</code> being the user id of the key owner and <code>PUBLIC_KEY</code> being their public key.</p>
<p>The authorization script will thus be called with the user id as first parameter.</p>
<p>That solves the first point of our TODO list.</p>
<h4 id="repositories-management">Repositories management</h4>
<p>At startup, etilotig writes some bash configuration files defining basic things such as the directory in which repositories have to be created.</p>
<p>Then we have a tiny bash script in charge of the repository creation. We now create them in a deeper directory hierarchy to get as few repositories as possible per directory. Say we had <code>/data/app_18c6021b-0860-4f97-a08d-0663f45cf3f0.git</code> before, we now have <code>/data/app_18/c6/02/app_18c6021b-0860-4f97-a08d-0663f45cf3f0.git</code> which makes things waaaaay faster.</p>
<pre><code class="language-bash">#!/bin/bash

create_repo() {
local repo_dir=&quot;${1}&quot;

mkdir -p &quot;${repo_dir}&quot;
pushd &quot;${repo_dir}&quot; &amp;&gt;/dev/null
git init --bare
popd &amp;&gt;/dev/null
}

main() {
    local repo=&quot;${1}&quot;
    local repo_dir
    
repo_dir=&quot;${REPOS_DIR}/${repo:0:6}/${repo:6:2}/${repo:8:2}/${repo}&quot;

[[ -d &quot;${repo_dir}&quot;/hooks ]] || create_repo &quot;${repo_dir}&quot;
}

. &quot;${HOME}&quot;/.etilotig/.etilotigrc

main &quot;${@}&quot;
</code></pre>
<p>Then we have another one for hooks installation.</p>
<pre><code class="language-bash">#!/bin/bash

shopt -s nullglob

main() {
    local repo=&quot;${1}&quot;
    local repo_dir
    
repo_dir=&quot;${REPOS_DIR}/${repo:0:6}/${repo:6:2}/${repo:8:2}/${repo}&quot;

for hook in ${HOME}/.etilotig/hooks/*; do
ln -sf &quot;${hook}&quot; &quot;${repo_dir}&quot;/hooks/
done
}

. &quot;${HOME}&quot;/.etilotig/.etilotigrc

main &quot;${@}&quot;
</code></pre>
<p>With those two simple scripts, we only have to call them for each repository at startup to ensure everything is OK, and at each repository creation, which solves two of the four points from our TODO list, only one left.</p>
<h4 id="authorization">Authorization</h4>
<p>Now, the goal was <em>not</em> to duplicate the configuration anymore, but rather use the configuration from the API, thus externalising the whole thing. Dropping all the configuration management from etilotig reduced its size by more than 50%.</p>
<p>The way etilotig manages authorization is quite simple: when it generates its internal configuration, it actually generates a perl script which is called on each ssh connection attempt. The script then authorises or not the transaction.</p>
<p>We made a similar script which prints the users some information about which repositories they have access to if they just run <code>ssh git@push.par.clever-cloud.com</code> or such, checks if they are authorized when they try to git push/pull or rejects any other request.</p>
<p>It looks like this (with some extra stuff added):</p>
<pre><code class="language-bash">#!/bin/bash

sanity_check() {
    if [[ -z &quot;${SSH_CONNECTION}&quot; ]]; then
        echo &quot;Who the hell are you?&quot; &gt;&amp;2
        exit 1
    fi

if [[ -z &quot;${SSH_ORIGINAL_COMMAND}&quot; ]]; then
        export SSH_ORIGINAL_COMMAND=&quot;info&quot;
fi
}

ask_for_info() {
    local userid=&quot;${1}&quot;

# make the request to the API to retrieve user info message
echo &quot;some info&quot;
}

ask_for_authorization() {
    local userid=&quot;${1}&quot;
    local appid=&quot;${2}&quot;

# make the request and return the HTTP status code here. 200 means authorized.
echo 200
}

authorize() {
    local userid=&quot;${1}&quot;
    local verb=&quot;${2}&quot;
    local appid=&quot;${3}&quot;
    local ret=1
    case &quot;${verb}&quot; in
        &quot;git-receive-pack&quot;|&quot;git-upload-pack&quot;)
            local code
            code=$(ask_for_authorization &quot;${userid}&quot; &quot;${appid}&quot;)
            [[ &quot;${code}&quot; == &quot;200&quot; ]] &amp;&amp; ret=0
            ;;
    esac
    return &quot;${ret}&quot;
}

final_abort() {
    echo &quot;What are you trying to achieve here?&quot; &gt;&amp;2
    exit 2
}
main() {
    sanity_check
    local userid=&quot;${1}&quot;
    local verb
    local repo
    local repo_dir
    verb=$(echo &quot;${SSH_ORIGINAL_COMMAND}&quot; | cut -d &#39; &#39; -f 1)
    repo=$(echo &quot;${SSH_ORIGINAL_COMMAND}&quot; | cut -d &#39; &#39; -f 2 | tr -d &quot;&#39;\&quot;&quot;)
    [[ &quot;${repo}&quot; == /* ]] &amp;&amp; repo=${repo:1}
    repo_dir=&quot;${REPOS_DIR}/${repo:0:6}/${repo:6:2}/${repo:8:2}/${repo}&quot;
    if [[ &quot;${verb}&quot; == &quot;info&quot; ]]; then
        ask_for_info &quot;${userid}&quot;
    elif authorize &quot;${userid}&quot; &quot;${verb}&quot; &quot;${repo}&quot;; then
        export CC_USER=&quot;${userid}&quot;
        export CC_NOTIFY_SCRIPT=&quot;${HOME}/.etilotig/send-push-event&quot;
        exec &quot;${verb}&quot; &quot;${repo_dir}&quot;
    else
        final_abort
    fi
}
. &quot;${HOME}&quot;/.etilotig/.etilotigrc
main &quot;${@}&quot;
</code></pre>
<p>With this in place, nearly everything was ready. Two tiny hooks on top of that to only allow users to push on the master branch, and to trigger a deployment on git push:</p>
<p><code>hooks/update</code></p>
<pre><code class="language-bash">#!/bin/bash

main() {
    local rev=&quot;${1}&quot;
    if [[ &quot;${rev}&quot; == refs/tags/* ]]; then
        exit 0
    fi
    if [[ &quot;${rev}&quot; != &quot;refs/heads/master&quot; ]]; then
        echo &quot;You tried to push to a custom branch.&quot;
        echo &quot;Only master is allowed.&quot;
        exit 1
    fi
}

main &quot;${@}&quot;
</code></pre>
<p><code>hooks/post-update</code></p>
<pre><code class="language-bash">#!/bin/bash

sanity_check() {
    local rev=&quot;${1}&quot;
    if [[ &quot;${rev}&quot; == refs/tags/* ]]; then
        exit 0
    fi
}

main () {
    local rev=&quot;${1}&quot;
    sanity_check &quot;${rev}&quot;
    local repo=$(basename $(pwd))
    local appId=${repo/.git/}
    local commitId=$(git rev-parse &quot;${rev}&quot;)
    &quot;${CC_NOTIFY_SCRIPT}&quot; &quot;${appId}&quot; &quot;${commitId}&quot; &quot;${CC_USER}&quot;
    echo &quot;[SUCCESS] The application has successfully been queued for redeploy.&quot;
}

main &quot;${@}&quot;
</code></pre>
<h3 id="conclusion">Conclusion</h3>
<p>That&#39;s it, we have our new git server manager up and running, which works pretty well.</p>
<p>The performance gain? We went from between 3 and more than 5 minutes to less than 1 second per action, while dropping the whole gitolite codebase and reducing the size of etilotig by 50%, with an average performance gain of 30k%.</p>
<p>gitolite has been very useful both in its utilisation and its codebase to better comprehend the whole authentication mechanism, so a great thanks to this awesome tool.</p>
<p>Now gitolite is dead, long live etilotig!</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
