Encapsuler git

Voici une solution pour conserver des branches divergentes avec git. Parce qu’il est facile de merger par erreur, je propose un script qui encapsule le comportement de git pour interdire certains merges dangereux. Mais qui permet aussi de faire des merges en cascades de la racines vers les autres branches.

Se prémunir contre les erreurs

Je travaille sur un projet dans lequel certaines de mes branches git doivent rester divergentes. Et les divergences devraient aller en s’accentuant.

J’utilise aussi certaines branches qui contiennent la partie commune de ces projets.

Disons que j’ai les branches :

  • master: commune à toutes les branches
  • dev: branche instable pour le développement
  • client: Branche commune à plusieurs clients
  • clientA: le projet spécialisé pour le client A
  • clientB: le projet spécialisé pour le client B

Voilà comment je souhaiterai que ça fonctionne

Dynamic branching

Et plus précisément la hiérarchie des branches :

Branch hierarchy

Une flèche de A vers B signifie que l’on peut merger A dans B. S’il n’y a pas de flèche de A vers B cela signifie qu’il est interdit de merger A dans B. Voici le code ruby correspondant :

$architecture={ :master => [ :dev, :client ], :dev => [ :master ], :client => [ :clientA, :clientB ] }

:master => [ :dev, :client ] signifie que l’on peut merger la branche master dans la branche dev et la branche client.

Je fait une erreur si je tape git checkout master && git merge clientA. C’est pour éviter ça que j’ai fait un script pour encapsuler le comportement de git.

Mais ce script fait bien plus que ça. Il fait des merges en cascade de la racine vers les feuilles avec l’acion allmerges.

git co dev && git merge master git co client && git merge master git co clientA && git merge client git co clientB && git merge client

Je peux ainsi mettre à jour toutes les branches. L’algorithme ne boucle pas même s’il y a des cycles dans la structure des branches.
Le voici :

#!/usr/bin/env ruby # encoding: utf-8

architecture

master <-> dev

master -> client

clien -> clientA | clientB

merge using two of these branches should be

restricted to these rules

merge to one of these branch and an unknown one should

raise a warning, and may the option to add this new branch

to the hierarchy

$architecture={ :master => [ :dev, :client ], :dev => [ :master ], :client => [ :clientA, :clientB ] }

def get_current_branch() (git branch --no-color | awk '$1 == "*" {print $2}').chop.intern end

if ARGV.length == 0 puts %{usage: $0:t [git_command or local_command]

local commands: allmerges: merge from top to down} exit 0 end

require ‘set’ $known_branches=Set.new $architecture.each do |k,v| $known_branches.add(k) v.each { |b| $known_branches.add(b) } end

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

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

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 f system %{git merge #{src_branch}} puts %{Warning! #{src_branch} not mentionned in rb configuration} end end end

case ARGV[0] when ‘allmerges’ then do_all_merges when ‘merge’ then do_merge else system %{git #{ARGV.join(’ ’)}} end

Pour que ça fonctionne il vous suffit de copier eng dans un répertoire présent dans votre PATH.

Bien entendu, il faut essayer de faire aussi peu que possible des cherry-pick et des rebase. Ce script a pour but de s’intégrer avec les workflow basé sur les pull et merge.