<spanclass="tomenu"><ahref="#navigation">↓ Menu ↓</a></span>
<spanclass="flush"></span>
</div>
</div>
<divid="titre">
<h1>Encapsulate git</h1>
</div>
<divclass="flush"></div>
<divid="afterheader"class="article">
<divclass="corps">
<divclass="intro">
<p>Here is a solution to maintain divergent branches in git. Because it is easy to merge by mistake. I give a script that encapsulate git in order to forbid some merge and warn you some merge should be dangerous.</p>
</div>
<h2id="how-to-protect-against-your-own-dumb">how to protect against your own dumb</h2>
<p>I work on a project in which some of my git branches should remain divergent. And divergences should grow.</p>
<p>I also use some branch to contain what is common between projects.</p>
<p>Say I have some branches:</p>
<p>master: common to all branches dev: branch devoted to unstable development client: branch with features for all client but not general enough for master clientA: project adapted for client A clientB: project adapted for client B</p>
<p>An arrow from A to B means, you can merge A in B. If there is no arrow from A to B that means it is <em>forbidden</em> to merge A in B. Here is the corresponding rubycode:</p>
<p>Having a <code>:master => [ :dev, :client ]</code> means you can merge <code>master</code> branch into <code>dev</code> and <code>client</code>.</p>
<p>If by mistake I make a <code>git checkout master && git merge clientA</code>, I made a mistake. This is why I made a script which encapsulate the git behaviour to dodge this kind of mistake.</p>
<p>But this script do far more than that. It also merge from top to down. The action <code>allmerges</code> will do:</p>
<div>
<codeclass="zsh"> git co dev && git merge master git co client && git merge master git co clientA && git merge client git co clientB && git merge client </code>
</div>
<p>That means, I can update all branches. The algorithm will not make loop even if there is a cycle in the branch hierarchy.</p>
<p>def rec_merge(branch) if $architecture[branch].nil? return end $architecture[branch].each do |b| if $flag.has_key?(b.to_s + branch.to_s) next end flagname=branch.to_s + b.to_s if $flag.has_key?(flagname) next end if system %{eng checkout #{b}} if get_current_branch != b puts “Can’t checkout to #{b}” exit 2 end if system %{eng merge #{branch}} $flag[flagname]=true rec_merge(b) else exit 1 end else exit 1 end end end</p>
<p>def do_all_merges puts ‘Will merge from father to sons’ current_branch=get_current_branch $flag={} rec_merge(:master) system %{git co #{current_branch}} end</p>
<p>def do_merge current_branch=get_current_branch src_branch=ARGV[1].intern puts %{do_merge: #{src_branch} => #{current_branch}} if $known_branches.include?(current_branch) if $known_branches.include?(src_branch) if $architecture.has_key?(src_branch) and $architecture[src_branch].include?(current_branch) system %{git merge #{src_branch}} else puts %{Forbidden merge: #{src_branch} => #{current_branch}} end else puts %{Warning! #{src_branch} not mentionned in rb configuration} sleep 2 system %{git merge #{src_branch}} puts %{Warning! #{src_branch} not mentionned in rb configuration} end end end</p>
case ARGV[0] when ‘allmerges’ then do_all_merges when ‘merge’ then do_merge else system %{git #{ARGV.join(’’)}} end </code>
</div>
<p>All you need to do to make it work is simply to copy eng in a directory contained in your PATH.</p>
<p>Of course try to use as few as possible <code>cherry-pick</code> and <code>rebase</code>. This script was intended to work with workflow using <code>pull</code> and <code>merge</code>.</p>