#!/bin/bash
# Copyright (c) 2004 Guillaume Outters
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

set -e

mkdir -p /tmp/$$

### Fonctions utilisables à l'extérieur ###

machin()
{
	[ "$1" = 0 ] && echo base && return 0
	echo patch
}

commentaire_brut()
{
	tla cat-archive-log $PROJET--dev--$1--`machin "$2"`-$2 2> /dev/null | enroule
}

commentaire_filtrage_texte()
{
	sed -e '1,/^$/d'
}

commentaire_filtrage_resume()
{
	sed -e '/^Summary: /!d' -e 's/^Summary: //'
}

commentaire_filtrage_date()
{
	sed -e '/^Standard-date: /!d' -e 's/[^0-9]//g'
}

# Obtention des commentaires d'une rustine
# Paramètres:
# commentaire [-r|-t] <version> <rustine>
#   -r: résumé uniquement
#   -t: texte uniquement
#   -d: date (à la place des résumé et/ou texte)

commentaire()
{
	local mode=
	case "$1" in
		-r|-t|-d) mode="$1" ; shift ;;
	esac
	commentaire_brut "$@" | case "$mode" in
		-d) commentaire_filtrage_date ;;
		-r) commentaire_filtrage_resume ;;
		-t) commentaire_filtrage_texte ;;
		*)
			tee /tmp/$$/descr.arch | commentaire_filtrage_resume
			commentaire_filtrage_texte < /tmp/$$/descr.arch
			;;
	esac
	return 0
}

# Récupération d'une rustine d'une autre branche
# Paramètres:
# recuperer <version d'origine> <nom de rustine> <version de destination> <numero arch de destination>

recuperer()
{
	( cd "$SILO/$PROJET-$3" && darcs pull -a -p ".*$2.*" "$SILO/$PROJET-$1" )
	# Et on maintient arch à jour
	[ ! -z "$4" ] && local rep=`ls -tdr /tmp/$PROJET--dev--$3--* 2> /dev/null | tail -1` && ( cd "$rep" && tla replay "$PROJET--dev--$3--`machin $4`-$4" > /dev/null 2>&1 )
}

### Lutte serrée ###

# J'ai déjà eu le cas dans une même rustine arch d'un fichier A/B, dont le
# répertoire était déplacé une première fois (le mettant en C/B), puis lui-même
# encore bougé (C/D/B). Pour me le signifier, arch m'a indiqué les
# déplacements A vers C puis A/B vers C/D/B. Conclusion, il faut mémoriser le
# chemin parcouru par un fichier pour pouvoir effectuer le déplacement réel
# (séquentiel) sous darcs.
# On utilise un fichier, tenu à jour par les opérations de déplacement, qui
# donne une ligne sur deux un truc déplacé et la suivante son nouvel
# emplacement.
# À FAIRE: tester comment il aurait enregistré la suppression de B au final.
# S'il indique que c'est A/B qui disparaît, il faudra rappeler cette fonction
# aussi à la suppression.

touch /tmp/$$/deplacements
cheminReel()
{
	awk 'BEGIN{l="'"$1"'"}{if(alternance==0){alternance=1;if(substr(l"/",1,length($0)+1)==$0"/"){traduire=1;l=substr(l,length($0)+1,length(l))}else{traduire=0}}else{alternance=0;if(traduire)l=$0"/"l;}}END{print l}' < /tmp/$$/deplacements
}

### Corps du programme ###

auSecours()
{
	nom=`basename "$0"`
	cat >&2 << TERMINE
# $nom
# Met sous Darcs un patch arch
# © 2004 Guillaume Outters

Utilisation: $nom [-e] [-a auteur] [[<projet>] <version du projet>] <numéro du patch>
  -e: éditer le commentaire manuellement (par \$EDITOR)
Il est possible d'utiliser les variables PROJET, VERSION et AUTEUR à la place
des paramètres correspondants, et la variable SILO pour donner un SILO différent
de \$HOME/Library/Application Support/darcs.
TERMINE
	exit 1
}

analyserParametres()
{
	#PROJET=
	#VERSION=
	#RUSTINE=
	FIN=
	EDITER=non
	while [ $# -gt 0 ] ; do
		case "$1" in
			-e) EDITER=oui ;;
			-a) shift ; AUTEUR="$1" ;;
			*)
				for i in PROJET VERSION RUSTINE FIN ; do
					if eval test -z \$$i ; then
						eval $i=\""$1"\"
						break
					fi
				done
				;;
		esac
		shift
	done
	[ "x$FIN" = x ] && FIN="$RUSTINE"
	return 0
}

### Point d'appel habituel ###

avd1()
{

local PROJET="$PROJET"
local VERSION="$VERSION"
local RUSTINE="$RUSTINE"
analyserParametres "$@"

[ -z "$RUSTINE" -o -z "$VERSION" -o -z "$PROJET" ] && auSecours

while [ "$RUSTINE" -le "$FIN" ] ; do

init_uniquement=non
[ -z "$SILO" ] && SILO="$HOME/Library/Application Support/darcs"
cd "$SILO"
case "$RUSTINE" in
	0)
		# Branche depuis la version précédente, si possible
		# À FAIRE: ne le faire que si cat-archive-log indique
		# Continuation-of.
		precedente=$(echo "$VERSION" | sed -e 's/[^.]*$//')$(($(echo "$VERSION" | sed -e 's/^.*\.//') - 1))
		if [ -d "$PROJET-$precedente" ] ; then
			darcs get "$PROJET-$precedente" "$PROJET-$VERSION"
			init_uniquement=oui # Une branche arch ne contient pas de modif.
		else
			mkdir "$PROJET-$VERSION"
			cd "$PROJET-$VERSION"
			darcs init
		fi
		machin=base
		;;
	*)
		machin=patch
		;;
esac

### Filtrage des erreurs arch ###

if [ ! -x /tmp/avd.filtre ] ; then
	cd /tmp
	cat > avd.filtre.lex << TERMINE
%s HOP

%%

\n\*{32}\nNO\ CHECKSUMS\ FOUND\ FOR\ REVISION\n { BEGIN(HOP); }
<HOP>\n.*\*{32}\n\n { BEGIN(0); }
<HOP>^..*$ {}
<HOP>\n {}

%%

int yywrap() { return 1; }
int main(int argc, char ** argv) { yylex(); return 0; }
TERMINE
	lex avd.filtre.lex && cc -o avd.filtre lex.yy.c && rm avd.filtre.lex lex.yy.c
fi

### Récupération sous arch des données ###

cd /tmp
rep=`ls -tdr $PROJET--dev--$VERSION--* 2> /dev/null | tail -1` || true # Le || true pour éviter qu'on ne termine sur un échec (le shell est en -e).
statut=0
if [ "$rep" = "" ] ; then
	tla get "$PROJET--dev--$VERSION--$machin-$RUSTINE" > /dev/null
	[ $init_uniquement = oui ] && RUSTINE=$(($RUSTINE + 1)) && continue
	cd "$PROJET--dev--$VERSION--$machin-$RUSTINE"
	# Petite magouille pour comprendre depuis l'inventory, de ce qui a été
	# ajouté, ce qui est du fichier et ce qui est du dossier.
	tla inventory --both > /tmp/$$/infos.2
	tla inventory > /tmp/$$/infos.1
	diff /tmp/$$/infos.[12] | sed -e '/^> /!d' -e 's=^> [^ ]*[ ]*=A/ ='
	sed -e 's=^[^ ]*[ ]*=A =' < /tmp/$$/infos.1
else
	cd "$rep"
	tla replay "$PROJET--dev--$VERSION--$machin-$RUSTINE" | egrep -v '{arch}|.arch-ids'
fi 2> /tmp/$$/infos.erreurs > /tmp/$$/infos || statut=$? # Si on a une erreur, on ne veut pas sortir tout de suite, il faut qu'on puisse afficher le message.
/tmp/avd.filtre < /tmp/$$/infos.erreurs >&2 # On ne peut le coller directement à la suite de la ligne précédente, sans quoi le cd qui y est fait tombe dans un sous-shell.
[ "$statut" -ne 0 ] && return $statut
sed -e '/^[AM][^/]/!d' -e 's/^[^ ]* *//' < /tmp/$$/infos | tr '\012' '\000' | xargs -0 tar pcf /tmp/$$/fichiers.tar

### Enregistrement des changements ###

cd "$SILO/$PROJET-$VERSION"

cat /tmp/$$/infos | while read ligne ; do # Le bon vieux coup de while read < … qui part en boucle infinie.
	contenu=`echo "$ligne" | sed -e 's/^[^ ]*[ ]*//'`
	if echo "$ligne" | grep -q '^A' ; then # Ajouts
		if echo "$ligne" | grep -q '^A/' ; then # Dossier
			mkdir -p "$contenu"
		else # Dans le .tar
			tar xpf /tmp/$$/fichiers.tar "$contenu"
		fi
		darcs add "$contenu"
	elif echo "$ligne" | grep -q '^.>' ; then # Déplacements
		# À FAIRE: blinder. Enfin vu qu'arch n'accepte pas d'espace dans les
		# noms, ça n'est sans doute pas la peine…
		echo "$contenu" | sed -e 's/[	 ][ 	]*/\
/' -e 's=^./==' | tee /tmp/$$/deplacements.temp | ( read origine ; read destination ; darcs mv `cheminReel "$origine"` "$destination" )
		cat /tmp/$$/deplacements.temp >> /tmp/$$/deplacements
	elif echo "$ligne" | grep -q '^D' ; then # Suppressions
		# À FAIRE: regarder comment ça se passe pour les répertoires (le
		# répertoire est-il retiré avant ou après ses fichiers?
		darcs remove "$contenu"
		rm -R "$contenu"
	fi
done

# Modifications

[ -f /tmp/$$/fichiers.tar ] && tar xpf /tmp/$$/fichiers.tar # On a parfois des rustines vides, qui ne génèrent pas de tar.

### Constitution du commentaire ###

commentaire "$VERSION" "$RUSTINE" > /tmp/$$/descr.darcs
[ $EDITER = oui ] && $EDITOR /tmp/$$/descr.darcs

### Enregistrement ###

#darcs record --logfile=/tmp/$$/descr.darcs -a --author="$AUTEUR"
( commentaire -d "$VERSION" "$RUSTINE" ; echo "$AUTEUR" ; cat /tmp/$$/descr.darcs ) | darcs record --pipe -a | grep -v 'What is the date' || true

### Suivante, si nécessaire ###

RUSTINE=$(($RUSTINE + 1))

done

return 0

}

# À FAIRE: un mode où avd1 peut être appelé à la main

if false ; then

### Ménage ###

#mv /tmp/$$ /tmp/traces.$PROJET--dev--$VERSION--$machin-$RUSTINE
rm -Rf /tmp/$$

fi
