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.

441 lines
12 KiB

#!/usr/bin/php
<?php
// quelques constantes pour faire marcher les fonctions copie-colle de SPIP ou du plugin debardeur
define('_SPIP_CHMOD', 0777);
define('_DIR_DEBARDEUR_TMP', getcwd());
define('_DIR_RESTREINT', 'ecrire/');
define('_DIR_RACINE', '');
define('_LOG_ERREUR', 3);
$url_orgs = [
'https://git.spip.net/spip-contrib-squelettes',
'https://git.spip.net/spip-contrib-extensions',
'https://git.spip.net/spip-contrib-themes',
'https://git.spip.net/spip-contrib-outils',
'https://git.spip.net/spip-contrib-graphismes',
'https://git.spip.net/spip-galaxie',
'https://git.spip.net/spip'
];
$errors = [];
$updated = [];
$empty = [];
$nb_repositories = 0;
// tester si checkout est bien disponible dans le path
$output = [];
exec('which checkout', $output);
$output = trim(implode("\n", $output));
if(empty($output)){
echo "Le script checkout doit être installé d'abord : https://git.spip.net/spip-contrib-outils/checkout\n";
die();
}
// Identifier le mode de clonage : ssh ou https, défaut https
$args = $argv;
array_shift($args); // inutile : le nom du script
$method = reset($args);
foreach ($url_orgs as $url_org) {
$repos = debardeur_connecteur_gitea_lister_repositories($url_org, time() - 10 * 60);
$total = count($repos);
$nb_repositories += $total;
echo "\n# $total repositories dans $url_org\n";
$nb = 0;
foreach ($repos as $repo) {
$nb++;
$dir = $repo['full_name'];
$url = $method == '--ssh' ? $repo['url_ssh'] : $repo['url'];
$last_modified = $repo['last_modified'];
echo "$nb/$total. $url";
if ($repo['empty']) {
$empty[] = $url;
echo " => Vide\n";
}
else {
$gitindex = "$dir/.git/index";
if (!is_dir($dir)
or !file_exists($gitindex)
or !$t = filemtime($gitindex)
or $t < $last_modified) {
$updated[] = $url;
if (file_exists("$dir/.ignore")) {
$errors[] = $url;
}
else {
$cmd = "checkout -bmaster $url $dir";
echo "\n$cmd\n";
passthru($cmd);
echo "\n";
if (!file_exists($gitindex)) {
$errors[] = $url;
}
}
}
else {
echo " => OK\n";
}
}
}
}
echo "\n$nb_repositories Repositories au total\n";
if ($empty) {
$nb_empty = count($empty);
echo "\n$nb_empty Repositories vides\n" . '- ' . implode("\n- ", $empty);
echo "\n";
}
if ($updated) {
$nb_updated = count($updated);
echo "\n$nb_updated Repositories mis a jour\n" . '- ' . implode("\n- ", $updated);
echo "\n";
}
if ($errors) {
$nb_errors = count($errors);
debardeur_fail("$nb_errors Repositories en erreur", '- ' . implode("\n- ", $errors));
}
echo "\n";
/**
* Lister les repositories d'une organisation
* @param string $url_organisation
* @param int $last_modified_time
* @return array
*/
function debardeur_connecteur_gitea_lister_repositories($url_organisation, $last_modified_time = null) {
$endpoint = gitea_endpoint_from_url($url_organisation);
$org = gitea_organisation_from_url($url_organisation);
$method = "orgs/{$org}/repos";
$res = debardeur_json_api_call_pages(0, 'gitea', $endpoint, $method, ['limit' => 50], $last_modified_time);
$repositories = [];
if ($res) {
foreach ($res as $k => $row) {
if ($k === 'message' and is_string($row)) {
debardeur_fail("Echec API Gitea", implode("\n", [$url_organisation, "API Call $endpoint $method", $row, json_encode($res)]));
}
if (is_array($row) and !empty($row['clone_url'])) {
$repositories[$row['clone_url']] = [
'name' => $row['name'],
'full_name' => $row['full_name'],
'url' => $row['clone_url'],
'url_ssh' => $row['ssh_url'],
'empty' => $row['empty'],
'last_modified' => strtotime($row['updated_at'])
];
}
}
}
return $repositories;
}
/**
* Appel d'une l'API JSON en get (type gitea/github)
*
* @param string $type
* @param string $endpoint
* @param string $method
* @param string|array $query
* @param null $last_modified_time
* @return false|mixed|string
*/
function debardeur_json_api_call($type, $endpoint, $method, $query, $last_modified_time = null){
$res = debardeur_json_api_call_raw($type, $endpoint, $method, $query, $last_modified_time);
if ($res and $res['status']){
return $res['content'];
}
return false;
}
/**
* Appels multiples d'une API JSON en get (type gitea/github) pour recuperer toutes les pages de resultats
*
* @param int $nb_pages
* nombre maxi de de pages
* @param string $type
* @param string $endpoint
* @param string $method
* @param string|array $query
* @param null $last_modified_time
* @return false|array
*/
function debardeur_json_api_call_pages($nb_pages, $type, $endpoint, $method, $query, $last_modified_time = null){
$res = debardeur_json_api_call_raw($type, $endpoint, $method, $query, $last_modified_time);
$nb = 1;
if ($res and $res['status']){
$results = $res['content'];
$links = debardeur_json_api_extract_links($res['header']);
while (($nb_pages<=0 or $nb<$nb_pages) and $links and !empty($links['next'])) {
$q_page = explode('?', $links['next'], 2);
$q_page = end($q_page);
// s'assurer que les pages suivantes sont au moins aussi recentes
if (!empty($res['last_modified'])) {
$last_modified_time = $res['last_modified'];
}
$res = debardeur_json_api_call_raw($type, $endpoint, $method, $q_page, $last_modified_time);
$nb++;
$links = false;
if ($res and $res['status']){
while(count($res['content'])) {
$results[] = array_shift($res['content']);
}
$links = debardeur_json_api_extract_links($res['header']);
}
}
return $results;
}
return false;
}
/**
* Extraire les links du header Link: de la pagination des resultats
* @param array $headers
* @return array
*/
function debardeur_json_api_extract_links($headers) {
// "Link":"<https:\/\/git.spip.net\/api\/v1\/orgs\/spip-contrib-squelettes\/repos?limit=50&page=2>; rel=\"next\",<https:\/\/git.spip.net\/api\/v1\/orgs\/spip-contrib-squelettes\/repos?limit=50&page=4>; rel=\"last\""
$links = [];
if (!empty($headers['Link'])) {
$list = explode(',', $headers['Link']);
foreach ($list as $l) {
$l = explode(";", $l, 2);
if (count($l) == 2) {
$url = reset($l);
$url = ltrim($url, '<');
$url = rtrim($url, '>');
$rel = explode("rel=", end($l));
$rel = trim(end($rel), '"');
if ($rel and $url) {
$links[$rel] = $url;
}
}
}
}
return $links;
}
/**
* Appel d'une l'API JSON en get (type gitea/github) avec cache si possible
* (mais par defaut pas de cache)
*
* @param string $endpoint
* @param string $method
* @param string|array $query
* @param null $last_modified_time
* @return array
*/
function debardeur_json_api_call_raw($type, $endpoint, $method, $query, $last_modified_time = null){
$res = '';
$dir_cache = sous_repertoire(_DIR_DEBARDEUR_TMP, 'cache');
$dir_cache = sous_repertoire($dir_cache, $type);
if (is_array($query)){
$query = http_build_query($query);
}
if ($query){
$query = '?' . ltrim($query, '?');
} else {
$query = '';
}
$file_cache = $dir_cache . "api-" . md5("debardeur_json_api_call_raw:$endpoint:$method:$query") . ".json";
if (!is_null($last_modified_time)
and file_exists($file_cache)
and filemtime($file_cache)>=$last_modified_time
and $res = file_get_contents($file_cache)
and ($res = json_decode($res, true))!==false){
return $res;
}
$url = $endpoint . $method . $query;
// var_dump("curl $url");
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_USERAGENT, 'PHP-Debardeur');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('accept: application/json'));
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
curl_setopt($ch, CURLOPT_VERBOSE, 0);
curl_setopt($ch, CURLOPT_AUTOREFERER, true);
$output = curl_exec($ch);
$err = curl_errno($ch);
$errmsg = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($output) {
list($header, $content) = explode("\r\n\r\n", $output, 2);
$headers = [];
$header_lines = explode("\n", $header);
foreach ($header_lines as $header_line){
$header_line = explode(':', $header_line, 2);
if (count($header_line)==2){
list($k, $v) = $header_line;
$headers[$k] = trim($v);
}
}
$json = json_decode($content, true);
$res = [
'status' => false,
'header' => $headers,
'content' => $json,
'last_modified' => time(),
];
if ($httpCode==200 and $json!==false and !is_null($json)){
$res['status'] = true;
file_put_contents($file_cache, json_encode($res));
} else {
spip_log("Echec call API $url:" . json_encode($res), 'debardeur_call_api' . _LOG_ERREUR);
}
} else {
echo "API non accessible (url: $url)";
spip_log("Echec call API $url pas accessible", 'debardeur_call_api' . _LOG_ERREUR);
}
return $res;
}
function gitea_endpoint_from_url($url){
$url = explode('://', $url, 2);
$host = explode("/", end($url), 2);
$endpoint = $url[0] . "://" . $host[0] . "/api/v1/";
return $endpoint;
}
function gitea_repository_from_url($url){
$url = explode('://', $url, 2);
$path = explode("/", end($url), 2);
$path = end($path);
$repository = preg_replace(",\.git$,", "", $path);
return $repository;
}
function gitea_organisation_from_url($url){
$repository = gitea_repository_from_url($url);
$repository = explode('/', $repository);
$organisation = reset($repository);
return $organisation;
}
/**
* Echec sur erreur : on envoie un mail si possible et on echoue en lançant une exception
* @param $sujet
* @param $corps
* @throws Exception
*/
function debardeur_fail($sujet, $corps){
$corps = rtrim($corps) . "\n\n";
//debardeur_envoyer_mail($sujet, $corps);
echo "\n\n$sujet\n$corps\n";
throw new Exception($sujet);
}
/**
* Crée un sous répertoire
*
* Retourne `$base/${subdir}/` si le sous-repertoire peut être crée
*
* @example
* ```
* sous_repertoire(_DIR_CACHE, 'demo');
* sous_repertoire(_DIR_CACHE . '/demo');
* ```
*
* @param string $base
* - Chemin du répertoire parent (avec $subdir)
* - sinon chemin du répertoire à créer
* @param string $subdir
* - Nom du sous répertoire à créer,
* - non transmis, `$subdir` vaut alors ce qui suit le dernier `/` dans `$base`
* @param bool $nobase
* true pour ne pas avoir le chemin du parent `$base/` dans le retour
* @param bool $tantpis
* true pour ne pas raler en cas de non création du répertoire
* @return string
* Chemin du répertoire créé.
**/
function sous_repertoire($base, $subdir = '', $nobase = false, $tantpis = false) {
static $dirs = array();
$base = str_replace("//", "/", $base);
# suppr le dernier caractere si c'est un /
$base = rtrim($base, '/');
if (!strlen($subdir)) {
$n = strrpos($base, "/");
if ($n === false) {
return $nobase ? '' : ($base . '/');
}
$subdir = substr($base, $n + 1);
$base = substr($base, 0, $n + 1);
} else {
$base .= '/';
$subdir = str_replace("/", "", $subdir);
}
$baseaff = $nobase ? '' : $base;
if (isset($dirs[$base . $subdir])) {
return $baseaff . $dirs[$base . $subdir];
}
$path = $base . $subdir; # $path = 'IMG/distant/pdf' ou 'IMG/distant_pdf'
if (file_exists("$path/.ok")) {
return $baseaff . ($dirs[$base . $subdir] = "$subdir/");
}
@mkdir($path, _SPIP_CHMOD);
@chmod($path, _SPIP_CHMOD);
if (is_dir($path) && is_writable($path)) {
@touch("$path/.ok");
spip_log("creation $base$subdir/");
return $baseaff . ($dirs[$base . $subdir] = "$subdir/");
}
// en cas d'echec c'est peut etre tout simplement que le disque est plein :
// l'inode du fichier dir_test existe, mais impossible d'y mettre du contenu
spip_log("echec creation $base${subdir}");
if ($tantpis) {
return '';
}
if (!_DIR_RESTREINT) {
$base = preg_replace(',^' . _DIR_RACINE . ',', '', $base);
}
$base .= $subdir;
raler_fichier($base . '/.ok');
}
function spip_log($message, $logfile='spip') {
}