#!/usr/bin/env python3 """ Script pour analyser l'historique Git des fichiers livre.org, intrigues.org et personnages.org Génère un JSON détaillé des changements et un CSV récapitulatif """ import subprocess import json import csv from datetime import datetime from collections import defaultdict import os import sys from typing import Dict, List, Tuple, Any def run_git_command(args: List[str]) -> str: """Exécute une commande git et retourne la sortie""" try: result = subprocess.run( ['git'] + args, capture_output=True, text=True, check=True ) return result.stdout except subprocess.CalledProcessError as e: print(f"Erreur lors de l'exécution de la commande git: {e}", file=sys.stderr) sys.exit(1) def get_file_history(filename: str) -> List[Dict[str, str]]: """Récupère l'historique Git d'un fichier""" cmd = ['log', '--follow', '--pretty=format:%H|%an|%ae|%ad|%s', '--date=iso-strict', '--', filename] output = run_git_command(cmd) history = [] for line in output.strip().split('\n'): if not line: continue parts = line.split('|', 4) if len(parts) >= 5: history.append({ 'hash': parts[0], 'author': parts[1], 'email': parts[2], 'date': parts[3], 'message': parts[4] }) return history def get_file_content_at_commit(hash: str, filename: str) -> str: """Récupère le contenu d'un fichier à un commit donné""" try: result = subprocess.run( ['git', 'show', f'{hash}:{filename}'], capture_output=True, text=True, check=True ) return result.stdout except subprocess.CalledProcessError: return "" def parse_diff(diff_output: str) -> Dict[str, Any]: """Parse la sortie de git diff pour extraire les changements""" changes = { 'added_lines': [], 'removed_lines': [], 'modified_sections': [] } current_file = None current_block_start = None in_header = True for line in diff_output.split('\n'): # Sauter la première ligne de diff if line.startswith('diff --git'): in_header = True continue if line.startswith('+++'): in_header = False continue if line.startswith('---'): continue if in_header: continue # Nouveau bloc de changement if line.startswith('@@'): parts = line.split('@@') if len(parts) >= 3: # Extraire les numéros de ligne line_info = parts[1].strip() current_block_start = line_info continue # Ligne ajoutée if line.startswith('+') and not line.startswith('+++'): clean_line = line[1:] # Enlever le + au début line_num = current_block_start if current_block_start else 'unknown' changes['added_lines'].append({ 'line': clean_line, 'context': current_block_start }) # Ligne supprimée elif line.startswith('-') and not line.startswith('---'): clean_line = line[1:] # Enlever le - au début changes['removed_lines'].append({ 'line': clean_line, 'context': current_block_start }) # Ligne inchangée (context) elif line.startswith(' '): continue return changes def get_changes_between_commits(commit_hash: str, prev_hash: str, filename: str) -> Dict[str, Any]: """Compare deux commits et retourne les changements""" # Récupérer le diff diff_cmd = ['diff', prev_hash, commit_hash, '--', filename] diff_output = run_git_command(diff_cmd) if not diff_output.strip(): return None # Parser le diff changes = parse_diff(diff_output) # Ajouter des statistiques changes['stats'] = { 'added_count': len(changes['added_lines']), 'removed_count': len(changes['removed_lines']), 'net_change': len(changes['added_lines']) - len(changes['removed_lines']) } return changes def format_line_for_csv(line_content: str, max_length: int = 100) -> str: """Formate une ligne pour le CSV en limitant sa longueur""" if len(line_content) > max_length: return line_content[:max_length] + '...' return line_content def analyze_git_history(): """Fonction principale pour analyser l'historique Git""" files_to_track = ['livre.org', 'intrigues.org', 'personnages.org'] # Vérifier que nous sommes dans un dépôt Git try: run_git_command(['rev-parse', '--git-dir']) except subprocess.CalledProcessError: print("Erreur: Ce répertoire n'est pas un dépôt Git", file=sys.stderr) sys.exit(1) all_changes = [] # Pour chaque fichier for filename in files_to_track: if not os.path.exists(filename): print(f"Attention: Le fichier {filename} n'existe pas, il sera ignoré", file=sys.stderr) continue print(f"Analyse de {filename}...") # Récupérer l'historique history = get_file_history(filename) if not history: print(f"Aucun historique trouvé pour {filename}") continue # Analyser chaque commit for i, commit in enumerate(history): commit_hash = commit['hash'] commit_date = commit['date'] commit_author = commit['author'] commit_message = commit['message'] # Récupérer les changements par rapport au commit précédent if i < len(history) - 1: prev_hash = history[i + 1]['hash'] changes = get_changes_between_commits(commit_hash, prev_hash, filename) else: # Premier commit, récupérer le contenu initial content = get_file_content_at_commit(commit_hash, filename) changes = { 'added_lines': [{'line': line, 'context': 'initial'} for line in content.split('\n') if line.strip()], 'removed_lines': [], 'modified_sections': [], 'stats': { 'added_count': len(content.split('\n')), 'removed_count': 0, 'net_change': len(content.split('\n')) } } if changes and changes['stats']['added_count'] + changes['stats']['removed_count'] > 0: change_entry = { 'filename': filename, 'date': commit_date, 'commit_hash': commit_hash, 'author': commit_author, 'message': commit_message, 'changes': changes } all_changes.append(change_entry) # Trier par date all_changes.sort(key=lambda x: x['date']) # Générer le JSON print("Génération du fichier JSON...") with open('git_changes_history.json', 'w', encoding='utf-8') as f: json.dump(all_changes, f, ensure_ascii=False, indent=2) # Générer le CSV print("Génération du fichier CSV...") with open('git_changes_history.csv', 'w', newline='', encoding='utf-8') as csvfile: writer = csv.writer(csvfile) # En-têtes writer.writerow([ 'Date', 'Fichier', 'Commit Hash', 'Auteur', 'Message', 'Lignes Ajoutées', 'Lignes Supprimées', 'Changement Net', 'Détails Ajouts', 'Détails Suppressions' ]) # Données for change in all_changes: # Préparer les détails des ajouts et suppressions added_details = "; ".join([ format_line_for_csv(item['line'], 50) for item in change['changes']['added_lines'][:10] # Limiter à 10 exemples ]) if len(change['changes']['added_lines']) > 10: added_details += f"... (+{len(change['changes']['added_lines']) - 10} autres)" removed_details = "; ".join([ format_line_for_csv(item['line'], 50) for item in change['changes']['removed_lines'][:10] # Limiter à 10 exemples ]) if len(change['changes']['removed_lines']) > 10: removed_details += f"... (+{len(change['changes']['removed_lines']) - 10} autres)" stats = change['changes']['stats'] writer.writerow([ change['date'], change['filename'], change['commit_hash'], change['author'], change['message'], stats['added_count'], stats['removed_count'], stats['net_change'], added_details, removed_details ]) print(f"Analyse terminée: {len(all_changes)} changements enregistrés") print("Fichiers générés: git_changes_history.json et git_changes_history.csv") if __name__ == '__main__': analyze_git_history()