You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

493 lines
14 KiB

<?php
// ---------------------------------------- INTERFACE VCS ------------------------------------------
function vcs_read_current_rev($nom_vcs, $dir_repo) {
switch ($nom_vcs) {
case 'git':
case 'gitsvn':
$vcs = "vcs_exec_git";
if ($out = $vcs('rev-parse', 'HEAD', $dir_repo)){
$rev = substr(trim(end($out)), 0, 9);
return $rev;
}
break;
case 'svn':
$vcs = "vcs_exec_svn";
if ($out = $vcs('info', $dir_repo)){
$out = implode("\n",$out);
// Revision
// Revision: 18763
if (preg_match(",^R..?vision[^:\w]*:\s+(\d+)$,Uims",$out,$m)) {
$rev = $m[1];
return $rev;
}
}
break;
default:
break;
}
return false;
}
function vcs_clean_dirs_changed_list($dir_repo, $files_changed) {
$dirs_changed = array_map("trim",$files_changed);
$dirs_changed = array_map("dirname",$dirs_changed);
$dirs_changed = array_unique($dirs_changed);
$dirs_changed = array_filter($dirs_changed);
$d = rtrim($dir_repo,'/')."/";
foreach($dirs_changed as $k=>$v){
$dirs_changed[$k] = $d . ($v=="."?"":$v);
}
return $dirs_changed;
}
function vcs_info_last_commit($nom_vcs, $dir_repo, $sub_dir) {
static $origins = [];
if (!is_dir($dir_repo)) {
return false;
}
$dir_repo = rtrim($dir_repo,'/') . "/";
$props = array(
'revision' => '',
'origin' => '',
'date' => '',
);
$infos = array();
switch ($nom_vcs) {
case 'git':
$vcs = "vcs_exec_git";
if ($out = $vcs('rev-parse','HEAD', $dir_repo)) {
$rev = $props['revision'] = substr(trim(end($out)), 0, 9);
if ($out = $vcs('show', "$rev -s --format='%ci'", $dir_repo)) {
$props['date'] = trim(end($out));
}
}
if (empty($origins[$dir_repo])) {
$out = $vcs('remote','-v', $dir_repo);
foreach ($out as $line) {
if (strpos($line, "origin ") === 0) {
$url = trim(substr($line,7));
$url = explode(' ', $url);
$origins[$dir_repo] = reset($url);
break;
}
}
}
$props['origin'] = $origins[$dir_repo];
break;
case 'gitsvn':
$vcs = "vcs_exec_git";
$infos = $vcs("svn", "info $sub_dir", $dir_repo);
// et on continue comme en svn pour parser les infos
case 'svn':
default:
if (!$infos) {
// Test pour savoir si le gestionnaire de version est bien installe sur le serveur
if (!function_exists($vcs = 'vcs_exec_' . $nom_vcs)) {
echo_trace("VCS non disponible: '$nom_vcs'");
$vcs = 'explode'; // i.e. ne fait rien de ses arguments.
}
$dsource = $dir_repo . $sub_dir;
$infos = $vcs("info", "$dsource");
}
foreach($infos as $line) {
$line = str_replace(chr(160)," ",$line);
if (preg_match('/^(Last Changed Rev|R.vision de la derni.re modification)\s*: (?<revision>\d*)$/',$line,$matches))
$props['revision'] = $matches['revision'];
if (preg_match('/^URL\s*: (?<url>.*)$/',$line,$matches))
$props['origin'] = $matches['url'];
if (preg_match('/^(Last Changed Date|Date de la derni.re modification\s*): (?<date_commit>[^(]*)($|\()/',$line,$matches))
$props['date'] = $matches['date_commit'];
}
break;
}
return $props;
}
function vcs_smart_update($nom_vcs, $dir_repo, $url, $force_update = false) {
if (!is_dir($dir_repo)) {
return false;
}
$rev_before = vcs_read_current_rev($nom_vcs, $dir_repo);
if (!$rev_before) {
echo_trace("Impossible de lire la revision courante de $dir_repo");
return false;
}
switch ($nom_vcs) {
case 'git':
case 'gitsvn':
$vcs = "vcs_exec_git";
if ($nom_vcs === 'git') {
$vcs("pull", "--rebase", $dir_repo);
}
else {
$vcs("svn", "fetch", $dir_repo);
$vcs("svn", "rebase", $dir_repo);
}
$rev_after = vcs_read_current_rev($nom_vcs, $dir_repo);
// recuperer la liste des fichiers modifies
$changed = $vcs("diff-tree", "--no-commit-id --name-only -r {$rev_before}..{$rev_after}", $dir_repo);
$dirs_changed = vcs_clean_dirs_changed_list($dir_repo, $changed);
dirschanged_append($dir_repo, $dirs_changed);
return true;
break;
case 'svn':
default:
// Test pour savoir si le gestionnaire de version est bien installe sur le serveur
if (!function_exists($vcs = 'vcs_exec_' . $nom_vcs)) {
echo_trace("VCS non disponible: '$nom_vcs'");
$vcs = 'explode'; // i.e. ne fait rien de ses arguments.
}
// Si le repo est en file:// on fait un svnsync dessus,
// le up est fait par un hook post-commit sur ce qui a change uniquement
// sauf une fois par jour
if (preg_match(',^file://,',$url)) {
// supprimer le fichier qui trace les dossiers modifies avant sync ou up
$fc = rtrim($dir_repo,'/')."/dirs-changed.txt";
$vcs("sync", $url, $dir_repo);
if ($force_update){
$vcs("up",'--ignore-externals ' . rtrim($dir_repo,'/'), $dir_repo);
}
if (file_exists($fc)) {
$changed = file($fc);
$dirs_changed = vcs_clean_dirs_changed_list($dir_repo, $changed);
dirschanged_append($dir_repo, $dirs_changed);
// renommer le fichier dirs-changed.txt car on a memorise son contenu
rename($fc, $fc . '.last');
}
return true;
}
else {
// le up est fait a chaque fois, sauf si un jeton demande de up uniquement sur le force
if ($force_update or !file_exists($dir_repo . 'forced-up-only.txt')) {
$ignore = '--ignore-externals ';
if (file_exists($dir_repo . 'forced-externals.txt')) {
$ignore = '';
}
$vcs("up", $ignore . rtrim($dir_repo,'/'), $dir_repo);
$rev_after = vcs_read_current_rev($nom_vcs, $dir_repo);
// recuperer la liste des fichiers modifies
$changed = $vcs("diff", "--summarize -r {$rev_before}:{$rev_after} $dir_repo", $dir_repo);
$dirs_changed = array();
foreach ($changed as $line) {
$line = explode($dir_repo, $line, 2);
if (count($line)>1) {
$dirs_changed[] = trim(dirname($dir_repo . end($line)));
}
}
$dirs_changed = array_unique($dirs_changed);
$dirs_changed = array_filter($dirs_changed);
dirschanged_append($dir_repo, $dirs_changed);
return true;
}
return false;
}
break;
}
return false;
}
function vcs_externals_smart_update($nom_vcs, $dir_repo, $sub_dir) {
switch ($nom_vcs) {
case 'svn':
$dir_repo = rtrim($dir_repo, '/') . '/';
$sub_dir = rtrim($sub_dir, '/');
// lire si il y a des externals dans le sous-dossier
$externals = vcs_exec_svn("propget", "svn:externals -R " . $dir_repo . $sub_dir);
$externals = implode("\n", $externals);
if (strpos($externals, "://") !== false) {
vcs_exec_svn('up', $dir_repo . $sub_dir);
return true;
}
break;
default:
// rien a faire
break;
}
return false;
}
// Interface de commande du gestionnaire de version Subversion (GIT)
//
// $cmd : commande git
// $args : liste des arguments (ligne de commande)
//
// return : le resultat de la commande
function vcs_exec_git($cmd, $args, $dir_repo)
{
$dir = getcwd();
chdir($dir_repo);
$bin = 'git';
$out = exec_trace("$bin $cmd $args", true);
chdir ($dir);
return $out;
}
// Interface de commande du gestionnaire de version Subversion (SVN)
//
// $cmd : commande svn
// $args : liste des arguments (ligne de commande)
//
// return : le resultat de la commande
function vcs_exec_svn($cmd, $args)
{
$bin = ($cmd == 'sync') ? 'svnsync' : 'svn';
return exec_trace("$bin $cmd $args", true);
}
// Fonction generique de lancement d'une commande
// La commande et son resultat sont traces
//
// $commande : commande a executer
//
// return : le resultat de la commande
function exec_trace($commande, $full = false){
$output = array();
echo_trace($commande);
$out = exec("$commande 2>&1", $output);
if ($output!==null) {
$out = end($output);
}
if (strlen(trim($out)))
array_map('echo_trace',$output);
return ($full ? $output : $out);
}
// -------------------------------------- dirs-changed.txt -----------------------------------------
function dirschanged_filename($dir_repo) {
$file_changed = rtrim($dir_repo,'/')."/last-changed.json";
return $file_changed;
}
function dirschanged_read($dir_repo, $since = null) {
$file = dirschanged_filename($dir_repo);
if (!file_exists($file)
or !$changed = file_get_contents($file)
or !$dirs_changed = json_decode($changed, true)) {
$dirs_changed = array();
}
if (!is_null($since) and $dirs_changed) {
foreach ($dirs_changed as $dir => $time) {
if ($time < $since) {
unset($dirs_changed[$dir]);
}
}
}
return $dirs_changed;
}
function dirschanged_append($dir_repo, $new_dirs_changed) {
$keep_duration = 24 * 3600; // on garde les modifs des dernieres 24h
$now = time();
$dirs_changed = dirschanged_read($dir_repo, $now - $keep_duration);
foreach ($new_dirs_changed as $dir) {
$dirs_changed[$dir] = $now;
}
$file = dirschanged_filename($dir_repo);
file_put_contents($file, json_encode($dirs_changed));
return $dirs_changed;
}
// -------------------------------------- TRACE ET MAIL --------------------------------------------
// Fonction de trace avec horodatage
//
// $out : chaine a tracer
//
// return : la trace horodatee
function echo_trace($out){
$outh = @date("[Y-m-d H:i:s] ",time()).$out."\n";
if (_TRACE) echo $outh;
if (strncmp($out,"Erreur :",8)==0)
$GLOBALS['erreurs'][]=$outh;
return $outh;
}
// Fonction d'envoi d'un mail sur les erreurs collectees durant la session d'empaquetage
//
// $erreurs : tableau des erreurs collectees
// $mail_to : destinataire des mails d'information
// $mail_from : emetteur des mails d'information
//
// return : aucun
function envoyer_mail($erreurs, $mail_to, $mail_from)
{
$headers =
"From: ".($mail_from ?$mail_from : $mail_to)."\r\n"
."Reply-To: ".($mail_from ?$mail_from : $mail_to)."\r\n";
mail($mail_to,"empaqueteur",implode("",$erreurs),$headers);
}
// -------------------------------- TRAITEMENT DES ARBORESCENCES -----------------------------------
// Fonction de creation d'une arborescence/vide/vers/un/nom/de/dossier
//
// $dir : arborescence a creer
//
// return : aucun
function preparer_chemin($dir){
if (strlen($dir)){
if ($parent = dirname($dir) AND !is_dir($parent))
preparer_chemin($parent);
if (!is_dir($dir))
mkdir($dir);
}
}
// Fonction de suppression supprimer une arborescence/vide/vers/un/nom/de/dossier
// a partir d'un repertoire $base que l'on conserve
//
// $base : repertoire de base a partir duquel commence la suppression
// $dir : arborescence a detruire
//
// return : aucun
function supprimer_chemin($base, $dir){
if (strlen($dir) AND $dir!='.'){
if (file_exists($base .$dir))
rmdir($base .$dir);
supprimer_chemin($base, dirname($dir));
}
}
// ----------------------------------- TRAITEMENT DES PAQUETS --------------------------------------
// Fonction de mise a jour d'un paquet
// Si le paquet a change on le recopie dans le repertoire de destination
//
// $source : paquet venant d'etre cree dans le repertoire temporaire
// $dest : paquet dans le repertoire final de depot (existe deja ou pas)
//
// return : aucun
function copie_update($source, $dest){
if (file_exists($source)){
$ts = filemtime($source);
if (file_exists($dest))
unlink($dest);
else {
// si l'archive doit etre mise dans un sous repertoire
// creer l'arbo la premiere fois
preparer_chemin(dirname($dest));
}
rename($source,$dest);
touch($dest,$ts);
}
else
echo_trace("Erreur : fichier $source non trouve");
}
// Fonction de lecture des informations du fichier revision.svn
// Ce fichier est utilise par SPIP pour afficher son numero de version dans l'interface privee
// a l'aide de la fonction version_svn_courante()
// http://doc.spip.org/@version_svn_courante
//
// $nom_vcs : methode VCS utilisee
// $dir_repo : repertoire directory
// $sub_dir : repertoire paquet
// $file_rev : fichier dans lequel on export les infos revision/origin et date de dernier commit
//
// return : tableau des infos lues dans revision.svn
// 0 : numero de revision
// 1 : date du dernier commit
function renseigner_revision_paquet($nom_vcs, $dir_repo, $sub_dir, $file_rev){
$props = vcs_info_last_commit($nom_vcs, $dir_repo, $sub_dir);
if (!$props) return array('', 0);
$xml_props = [
'revision' => $props['revision'],
'origine' => $props['origin'],
'commit' => $props['date'],
];
$txt_props = [
'Revision' => $props['revision'],
'Origine' => $props['origin'],
'Dernier commit' => $props['date'],
];
$svn_revision = "<svn_revision>\n<text_version>";
foreach($txt_props as $prop => $val) $svn_revision .= "\n$prop: $val";
$svn_revision .= "\n</text_version>";
foreach($xml_props as $prop => $val) $svn_revision .= "\n<$prop>$val</$prop>";
$svn_revision .= "\n</svn_revision>";
file_put_contents($file_rev, $svn_revision);
// mettre la date du fichier a celle du dernier commit
// pour ne pas fausser la date du paquet (qui est celle du plus recent fichier)
touch($file_rev,strtotime($xml_props['commit']));
$date_commit = date('Y-m-d H:i:s',strtotime($props['date']));
return array($props['revision'], $date_commit);
}
// Fonction de suppression des paquets et logos obsoletes du repertoire de depot final
//
// $paquets_a_jour : tableau des paquets venant d'etre mis a jour
// $dir_paq : repertoire de depot des paquets crees
// $dir_tmp : repertoire temporaire de depot des archives crees
//
// return : aucun
function nettoyer_vieux_fichiers($paquets_a_jour, $dir_paq, $dir_tmp, $prepend='') {
$maxfiles = 10000; // securite
#echo_trace("Nettoyer vieux paquets:".$dir_paq);
$nbfiles = 0;
if (@is_dir($dir_paq) AND is_readable($dir_paq) AND $d = @opendir($dir_paq)) {
while (($f = readdir($d)) !== false && ($nbfiles<$maxfiles)) {
#echo_trace("fichier $f lu");
if ($f[0] != '.' # ignorer . .. .svn etc
AND $f != 'CVS'
AND $f != 'remove.txt'
AND is_readable($g = $dir_paq.$f)) {
#echo_trace("fichier $g a supprimer ?");
if (is_dir($g)){
nettoyer_vieux_fichiers($paquets_a_jour, "$g/", $dir_tmp, "$prepend$f/");
}
elseif (is_file($g)) {
#echo_trace("fichier $g est un zip");
if (!in_array($prepend.$f,$paquets_a_jour)){
echo_trace("Suppression du vieux paquet $f");
unlink($g);
@unlink($dir_tmp.$f); // securite, on vire aussi le vieux paquet tmp eventuel
}
}
}
$nbfiles++;
}
closedir($d);
}
}
?>