<?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>script &#8211; Best DevOps</title>
	<atom:link href="https://www.bestdevops.com/tag/script/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.bestdevops.com</link>
	<description>Lets Learn, Do it &#38; Share! Thats a Best DevOps!!!</description>
	<lastBuildDate>Mon, 12 Oct 2026 05:00:14 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>
	<item>
		<title>How Not to Write Your Git Update Hooks: Performance Pitfalls and Smarter Patterns</title>
		<link>https://www.bestdevops.com/how-not-to-write-your-git-update-hooks/</link>
					<comments>https://www.bestdevops.com/how-not-to-write-your-git-update-hooks/#respond</comments>
		
		<dc:creator><![CDATA[anil]]></dc:creator>
		<pubDate>Thu, 12 Jan 2017 07:25:39 +0000</pubDate>
				<category><![CDATA[DevOps Best Practices]]></category>
		<category><![CDATA[DevOps Tools]]></category>
		<category><![CDATA[DevOps Updates]]></category>
		<category><![CDATA[Test Driven DevOps]]></category>
		<category><![CDATA[DevOps tool]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[Git Hooks]]></category>
		<category><![CDATA[Git Tutorials]]></category>
		<category><![CDATA[Hook scripts]]></category>
		<category><![CDATA[script]]></category>
		<guid isPermaLink="false">http://www.bestdevops.com/?p=313</guid>

					<description><![CDATA[Learn how to write efficient Git update hooks that enforce central repository rules without slowing down your development workflow. Covers commit verification, file-size checks, and commit-message validation.]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><em>Originally sourced from DZone. This article expands on the lessons learned when optimizing Git hook performance after migrating a central repository from SVN to Git.</em></p>



<h2 class="wp-block-heading">Background: Why Git Hooks Matter After an SVN Migration</h2>



<p class="wp-block-paragraph">Switching a team&#8217;s primary code repository from SVN to Git brings considerable freedom — but also new responsibilities. One powerful mechanism Git provides is <strong>hooks</strong>: executable scripts placed in the <code>.git/hooks</code> directory (or <code>your_repo.git/hooks</code> for a bare repository) that run automatically at specific points in the Git workflow. Hooks can write output to the console during a push or commit, and can even abort those operations entirely.</p>



<p class="wp-block-paragraph">Common use cases for hooks on a central (bare) repository include:</p>



<ul class="wp-block-list">
<li>Preventing large binary files from being pushed into the repository.</li>
<li>Enforcing a commit-message naming convention (e.g., requiring an issue tracker ID).</li>
<li>Blocking pushes to protected branches without passing CI checks.</li>
</ul>



<p class="wp-block-paragraph">The hook covered in depth here is the <strong>update hook</strong>, which is the right tool for enforcing rules on a central shared repository.</p>



<h2 class="wp-block-heading">Understanding the Update Hook</h2>



<p class="wp-block-paragraph">The update hook fires on the <em>server side</em> every time someone pushes to a repository. It runs once <strong>per ref</strong> being updated, meaning a push that updates three branches will invoke the hook three times.</p>



<p class="wp-block-paragraph">Git passes three positional arguments to the hook:</p>



<ol class="wp-block-list">
<li><strong>The ref name</strong> being updated — for example, <code>refs/heads/my_fancy_branch</code>.</li>
<li><strong>The old object SHA-1</strong> — the commit currently at the tip of that ref in the remote repository.</li>
<li><strong>The new object SHA-1</strong> — the commit at the tip of the ref in the developer&#8217;s local repository.</li>
</ol>



<p class="wp-block-paragraph">Exit with a non-zero status to reject the push; exit with zero to accept it. Because the hook runs before branch labels are updated, you can inspect exactly which commits are new without worrying about race conditions with other pushes.</p>



<h2 class="wp-block-heading">The Naive Approach — and Why It Falls Apart</h2>



<p class="wp-block-paragraph">The most obvious implementation iterates over every commit between <code>$old_ref</code> and <code>$new_ref</code> and checks each one individually:</p>


<pre class="wp-block-code"><span><code class="hljs language-php"><span class="hljs-comment"># Pseudo-code — do not copy verbatim</span>
commits=$(git rev-<span class="hljs-keyword">list</span> $old_ref..$new_ref)

<span class="hljs-keyword">for</span> commit in $commits; <span class="hljs-keyword">do</span>
    <span class="hljs-keyword">if</span> ! check_commit <span class="hljs-string">"$commit"</span>; then
        <span class="hljs-keyword">echo</span> <span class="hljs-string">"Commit $commit failed the check."</span>
        <span class="hljs-keyword">exit</span> <span class="hljs-number">1</span>
    fi
done</code></span></pre>


<p class="wp-block-paragraph">This works in simple cases but has three serious problems:</p>



<ol class="wp-block-list">
<li><strong>Slow per-commit overhead.</strong> Each call to <code>check_commit</code> typically spawns one or more subprocesses (e.g., <code>grep</code>, <code>awk</code>, <code>git cat-file</code>). Process-spawn overhead accumulates quickly across hundreds of commits.</li>
<li><strong>Early exit hides multiple problems.</strong> The script exits on the first bad commit. Developers must fix issues one at a time and re-push repeatedly before they see all violations — a frustrating experience.</li>
<li><strong>Merge commits inflate the revision range.</strong> <code>git rev-list $old_ref..$new_ref</code> returns <em>all</em> reachable commits, including those pulled in from <code>master</code> via a merge. Those commits were already validated when they landed on <code>master</code>; re-checking them wastes time and can push hook execution into the minutes range on long-running feature branches.</li>
</ol>



<h3 class="wp-block-heading">The Merge-Inflation Problem Illustrated</h3>



<p class="wp-block-paragraph">Consider this common workflow:</p>



<ul class="wp-block-list">
<li>Developer works on <code>feature_branch</code>.</li>
<li>Developer merges <code>master</code> into <code>feature_branch</code> to stay current.</li>
<li>Developer adds a few more commits on <code>feature_branch</code>.</li>
<li>Developer pushes.</li>
</ul>



<p class="wp-block-paragraph">The naive <code>rev-list</code> call returns every commit reachable from <code>$new_ref</code> but not from <code>$old_ref</code> — including potentially hundreds of commits that came from <code>master</code>. On a busy repository, a feature branch delayed by a few weeks could drag hook execution to several minutes per push. That is unacceptable for developer experience.</p>



<h2 class="wp-block-heading">The Smarter Approach</h2>



<p class="wp-block-paragraph">Two goals drive the improved design:</p>



<ol class="wp-block-list">
<li>Skip commits that are already reachable from existing branches (i.e., already verified).</li>
<li>Validate all remaining commits in a single pipeline rather than one at a time.</li>
</ol>



<h3 class="wp-block-heading">Filtering Already-Known Commits</h3>



<p class="wp-block-paragraph">The key insight is that the update hook runs <em>after</em> all new objects have been received but <em>before</em> any branch labels are moved. That means existing branch labels still point to where they were before the push. You can exploit this to exclude any commit reachable from a known branch:</p>


<pre class="wp-block-code"><span><code class="hljs language-php">git rev-<span class="hljs-keyword">list</span> $old_ref..$new_ref --not --branches=<span class="hljs-string">'*'</span></code></span></pre>


<p class="wp-block-paragraph">The <code>--not --branches='*'</code> flag tells Git to exclude any commit reachable from any local branch ref. Because branch labels haven&#8217;t moved yet, this effectively limits the output to commits that are genuinely new to the repository — exactly the set that needs checking.</p>



<h3 class="wp-block-heading">Processing Commits in One Pipeline</h3>



<p class="wp-block-paragraph">Instead of looping and spawning a process per commit, pipe the entire set of new commits through a sequence of Git and Unix commands in one shot. Collect all violations at once, report them all, and then exit with a failure code if any exist. Developers see all problems in a single push attempt.</p>



<h2 class="wp-block-heading">Practical Example 1: Block Oversized Files</h2>



<p class="wp-block-paragraph">Large binary files committed to a Git repository cause repository bloat that is painful to undo. Here is an efficient pipeline to detect and reject them:</p>


<pre class="wp-block-code"><span><code class="hljs language-php"><span class="hljs-comment"># Step 1: list SHA1 hashes and paths of all objects touched by new commits</span>
git rev-<span class="hljs-keyword">list</span> --objects $old_ref..$new_ref --not --branches=<span class="hljs-string">'*'</span> \

<span class="hljs-comment"># Step 2: for each object, print its SHA1, size in bytes, and path</span>
  | git cat-file --batch-check=<span class="hljs-string">'%(objectname) %(objectsize) %(rest)'</span> \

<span class="hljs-comment"># Step 3: filter objects exceeding the size limit, remove duplicates</span>
  | awk -v limit=<span class="hljs-string">"$FILESIZE_LIMIT_BYTES"</span> <span class="hljs-string">'$2 &gt; limit { print }'</span> \
  | sort \
  | uniq --skip-fields=<span class="hljs-number">2</span></code></span></pre>


<p class="wp-block-paragraph">The output is a list of <code>SHA1 size path</code> triplets for every oversized object. If the output is non-empty, print a helpful message listing each offending file and its size, then exit 1 to reject the push.</p>



<p class="wp-block-paragraph"><strong>Why this is fast:</strong> <code>git cat-file --batch-check</code> reads all objects in a single pass without checking out file contents. The entire pipeline runs in roughly constant time relative to the number of commits, not file sizes.</p>



<h2 class="wp-block-heading">Practical Example 2: Enforce Commit-Message Format</h2>



<p class="wp-block-paragraph">If your team requires every commit message to reference an issue tracker ID (e.g., <code>PROJ-1234</code>), the following pipeline validates all new commits in one pass:</p>


<pre class="wp-block-code"><span><code class="hljs language-php"><span class="hljs-comment"># List SHA1 and subject line for non-merge commits only</span>
git log --no-merges --pretty=<span class="hljs-string">'%H %s'</span> $old_ref..$new_ref --not --branches=<span class="hljs-string">'*'</span> \

<span class="hljs-comment"># Filter out commits whose subject line matches the required pattern</span>
  | grep -Ev <span class="hljs-string">"$MESSAGE_REGEX"</span></code></span></pre>


<p class="wp-block-paragraph">Any line remaining after the <code>grep -Ev</code> filter represents a commit that violates the naming convention. Collect the output, print the offending commit hashes and subjects as a single error block, and exit 1 only after showing everything. This way developers can fix all non-conforming messages in one go before re-pushing.</p>



<p class="wp-block-paragraph"><strong>Note:</strong> The <code>--no-merges</code> flag skips merge commits, which typically have auto-generated messages that do not follow the team convention.</p>



<h2 class="wp-block-heading">Edge Cases to Handle</h2>



<p class="wp-block-paragraph">The examples above cover the common push case. Real-world hooks also need to handle:</p>



<ul class="wp-block-list">
<li><strong>New branch creation:</strong> <code>$old_ref</code> will be the null SHA-1 (<code>0000000000000000000000000000000000000000</code>). Your script must detect this and adjust the <code>rev-list</code> range accordingly — otherwise the range is invalid.</li>
<li><strong>Branch deletion:</strong> <code>$new_ref</code> will be the null SHA-1. There are no commits to check; just allow or deny based on policy.</li>
<li><strong>Force pushes:</strong> The old and new refs may not share a common ancestor. Consider whether your checks should still apply and how your <code>rev-list</code> range behaves.</li>
<li><strong>Tags:</strong> The ref name will start with <code>refs/tags/</code> rather than <code>refs/heads/</code>. Decide whether your hook logic applies to tag objects.</li>
</ul>



<h2 class="wp-block-heading">General Principles for Hook Performance</h2>



<ul class="wp-block-list">
<li><strong>Prefer batch operations over per-commit loops.</strong> Unix pipelines and Git&#8217;s built-in batch modes (<code>--batch-check</code>, <code>--pretty</code> format strings) are far more efficient than spawning a subprocess per commit.</li>
<li><strong>Report all violations before exiting.</strong> Collect every problem, display them all at once, then exit. Never make developers push multiple times just to discover new errors.</li>
<li><strong>Exclude already-verified commits.</strong> Use <code>--not --branches='*'</code> to avoid re-checking work that is already on an accepted branch.</li>
<li><strong>Keep hooks focused.</strong> One hook file can chain multiple checks, but each check should be independently understandable and testable.</li>
<li><strong>Test hooks locally before deploying.</strong> Hooks can be copied into a local <code>.git/hooks</code> directory and invoked manually with representative SHA-1 arguments.</li>
</ul>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">Git&#8217;s update hook is a powerful enforcement point on a central repository, but a naïve per-commit loop can turn every push into a slow, frustrating experience. By using <code>--not --branches='*'</code> to skip already-accepted commits and processing the remaining set in a single pipeline, you can enforce rules like file-size limits and commit-message conventions in a matter of seconds. The result is a hook infrastructure that protects code quality without getting in developers&#8217; way.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.bestdevops.com/how-not-to-write-your-git-update-hooks/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
