РефератПрограммированиеГод: 2024РТУ МИРЭА: МИРЭА – Российский технологический университет
👁 15💼 0

Готовая реферат: Визуализация зависимостей Git-коммитов

Загружена: 20.02.2026 11:07

Инструмент на Python для анализа истории Git и визуализации зависимостей между коммитами через Mermaid. Описаны парсинг коммитов, построение графа по истории изменений файлов, генерация и сохранение .mmd, а также юнит-тесты. Полезно для анализа истории проекта и аудита изменений.

Содержание

План Коммитов
Ниже представлен список коммитов, отражающий основные этапы разработки вашего инструмента:
1.	Инициализация проекта и создание config.ini
2.	Создание базовой структуры скрипта depgraph.py
3.	Реализация функции получения коммитов и файлов
4.	Построение графа зависимостей
5.	Генерация Mermaid-кода
6.	Добавление обработчиков ошибок и отладочных сообщений
7.	Реализация функции сохранения результата в файл
8.	Добавление тестов для основных функций
9.	Улучшение читаемости графа (короткие хеши и сообщения коммитов)
10.	Добавление дополнительных

Заключение

Depgraph предоставляет удобный способ визуализации зависимостей между коммитами в вашем Git-репозитории. Используя простой интерфейс командной строки и интеграцию с Mermaid, вы можете быстро анализировать и понимать историю изменений вашего проекта.
## Установка
1. Клонируйте репозиторий.
2. Создайте виртуальное окружение и активируйте его:
```bash
python3 -m venv .venv
source .venv/bin/activate  # для Linux/macOS
# или
.venv\Scripts\activate     # для Windows
```
## Использование
1. Создайте `config.ini` с путями к репозиторию и файлу вывода.
2. Запустите скрипт:
```bash
python depgraph.py config.ini
```
3. Откройте сгенерированный `output.mmd` в [Mermaid Live Editor](https://mermaid.live) для визуализации.
## Тестирование
Запустите тесты с помощью `unittest`:
```bash
python -m unittest test_depgraph.py
Лицензия
MIT License
**Команды для коммита:**
```bash
echo "# Depgraph" > README.md
# Добавьте остальную информацию, как показано выше
git add README.md
git commit -m "docs: Add README with project description and usage instructions"
Итоговая Структура Проекта
После всех коммитов структура вашего проекта должна выглядеть следующим образом:
depgraph_project/
├── .git/
├── config.ini
├── depgraph.py
├── output.mmd
├── test_depgraph.py
└── README.md
ИТОГОВЫЙ КОД:
depgraph.py:
import configparser
import subprocess
import sys
import os
from collections import defaultdict

def get_commits_and_files(repo_path):
    """
    Возвращает список коммитов и словарь {commit_hash: {'message': commit_message, 'files': set(files_changed)}}.
    Использует:
    git log --pretty=format:%H|%s --name-only --reverse
    """
    print(f"Получение коммитов из репозитория: {repo_path}")
    cmd = ["git", "-C", repo_path, "log", "--pretty=format:%H|%s", "--name-only", "--reverse"]
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, check=True)
    except subprocess.CalledProcessError as e:
        print(f"Ошибка выполнения git: {e.stderr}")
        sys.exit(1)

    lines = result.stdout.strip().split('\n')
    commits = []
    commit_to_files = {}
    current_commit = None
    current_message = ""
    current_files = []

    for line in lines:
        if line.strip() == '':
            # Переход к следующему блоку
            if current_commit is not None:
                commit_to_files[current_commit] = {'message': current_message, 'files': set(current_files)}
                commits.append(current_commit)
            current_commit = None
            current_message = ""
            current_files = []
        elif '|' in line and len(line.split('|')[0]) == 40 and all(c in "0123456789abcdef" for c in line.split('|')[0].lower()):
            # Новая хеш-сумма коммита с сообщением
            if current_commit is not None:
                # Сохранить предыдущий коммит
                commit_to_files[current_commit] = {'message': current_message, 'files': set(current_files)}
                commits.append(current_commit)
            parts = line.split('|', 1)
            current_commit = parts[0].strip()
            current_message = parts[1].strip() if len(parts) > 1 else ""
            current_files = []
        else:
            # Файл
            current_files.append(line.strip())

    # Добавляем последний коммит, если он не пустой
    if current_commit is not None:
        commit_to_files[current_commit] = {'message': current_message, 'files': set(current_files)}
        commits.append(current_commit)

    print(f"Найдено {len(commits)} коммитов.")
    return commits, commit_to_files

def build_dependency_graph(commits, commit_to_files):
    """
    Строим граф зависимостей.
    Граф: {commit: set(of_commits_that_it_depends_on)}
    Зависимость: commit2 зависит от commit1, если хотя бы один файл из commit2
    был ранее изменен в commit1.
    """
    print("Построение графа зависимостей...")
    file_history = {}  # file -> last commit that changed it
    graph = defaultdict(set)

    for commit in commits:
        files = commit_to_files[commit]['files']
        depends_on = set()
        for f in files:
            if f in file_history:
                depends_on.add(file_history[f])
        # Записать зависимости
        graph[commit] = depends_on
        # Обновить file_history
        for f in files:
            file_history[f] = commit

    print("Граф зависимостей построен.")
    return graph

def generate_mermaid(graph, commit_to_files, commits):
    """
    Генерируем Mermaid диаграмму в виде кода.
    Формат:
    graph LR
      C1["c1: Add fileA and fileB"] --> C2
    """
    print("Генерация Mermaid-кода...")
    mermaid_lines = ["graph LR"]

    # Создадим краткие идентификаторы узлов для Mermaid (C1, C2, ...)
    commit_ids = {c: f"C{i}" for i, c in enumerate(commits, start=1)}

    for commit in commits:
        node_id = commit_ids[commit]
        short_hash = commit[:7]  # Короткий хеш (первые 7 символов)
        message = commit_to_files[commit]['message']
        label = f"{short_hash}: {message}"
        # Экранируем кавычки
        node_def = f'{node_id}["{label}"]'
        mermaid_lines.append(node_def)

    # Рёбра
    for commit in commits:
        from_id = commit_ids[commit]
        for dep in sorted(graph[commit]):
            to_id = commit_ids[dep]
            mermaid_lines.append(f"{to_id} --> {from_id}")

    mermaid_code = "\n".join(mermaid_lines)
    print("Mermaid-код сгенерирован.")
    return mermaid_code

def main():
    if len(sys.argv) != 2:
        print("Использование: python depgraph.py /путь/до/config.ini")
        sys.exit(1)

    config_path = sys.argv[1]
    if not os.path.exists(config_path):
        print(f"Файл конфигурации {config_path} не найден")
        sys.exit(1)

    config = configparser.ConfigParser()
    config.read(config_path)

    try:
        repo_path = config.get("paths", "repository_path")
        output_path = config.get("paths", "output_path")
    except (configparser.NoSectionError, configparser.NoOptionError) as e:
        print(f"Ошибка в конфигурационном файле: {e}")
        sys.exit(1)

    print(f"Репозиторий: {repo_path}")
    print(f"Путь к результату: {output_path}")

    commits, commit_to_files = get_commits_and_files(repo_path)
    if not commits:
        print("Нет коммитов для обработки.")
        sys.exit(0)

    graph = build_dependency_graph(commits, commit_to_files)
    mermaid_code = generate_mermaid(graph, commit_to_files, commits)

    # Выводим результат на экран
    print("\n--- Mermaid Code ---\n")
    print(mermaid_code)
    print("\n--- Конец Mermaid Code ---\n")

    # Сохраним в файл, если требуется
    if output_path:
        try:
            with open(output_path, "w") as f:
                f.write(mermaid_code)
            print(f"Mermaid-код сохранён в {output_path}")
        except IOError as e:
            print(f"Ошибка при записи в файл: {e}")
            sys.exit(1)

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        print(f"Произошла неожиданная ошибка: {e}")
        sys.exit(1)
test_depgraph.py
import unittest
from depgraph import build_dependency_graph, generate_mermaid

class TestDependencyGraph(unittest.TestCase):
    def test_build_dependency_graph(self):
        commits = ["c1", "c2", "c3"]
        commit_to_files = {
            "c1": {'message': "Add fileA and fileB", 'files': {"fileA.txt", "fileB.txt"}},
            "c2": {'message': "Update fileB and add fileC", 'files': {"fileB.txt", "fileC.txt"}},
            "c3": {'message': "Add fileD", 'files': {"fileD.txt"}}
        }

        # Ожидаемые зависимости:
        # c1 не зависит ни от кого
        # c2 зависит от c1 (изменение fileB.txt)
        # c3 не зависит ни от кого
        graph = build_dependency_graph(commits, commit_to_files)
        self.assertEqual(graph["c1"], set())
        self.assertEqual(graph["c2"], {"c1"})
        self.assertEqual(graph["c3"], set())

    def test_generate_mermaid(self):
        graph = {
            "c1": set(),
            "c2": {"c1"},
        }
        commit_to_files = {
            "c1": {'message': "Add fileA and fileB", 'files': {"fileA.txt", "fileB.txt"}},
            "c2": {'message': "Update fileB and add fileC", 'files': {"fileB.txt", "fileC.txt"}}
        }
        commits = ["c1", "c2"]
        mermaid = generate_mermaid(graph, commit_to_files, commits)
        self.assertIn('graph LR', mermaid)
        self.assertIn('C1["c1: Add fileA and fileB"]', mermaid)
        self.assertIn('C2["c2: Update fileB and add fileC"]', mermaid)
        self.assertIn('C1 --> C2', mermaid)
        self.assertNotIn('C2 --> C1', mermaid)

if __name__ == '__main__':
    unittest.main()

Подробное описание

📘 О чем эта работа

Проект Depgraph — командный инструмент на Python, предназначенный для извлечения истории коммитов из локального Git-репозитория, определения зависимостей между коммитами на основе изменений файлов и генерации диаграммы в формате Mermaid. Объект — история Git; предмет — алгоритм построения графа зависимостей и экспорт в .mmd для визуализации.

📚 Что внутри

Документация и код содержат конкретные модули и примеры команд:

  • config.ini с секцией '[paths]' и ключами 'repository_path' и 'output_path' для указания репозитория и файла вывода.
  • Скрипт depgraph.py с основными функциями: get_commits_and_files(repo_path) (парсинг вывода git log --pretty=format:%H|%s --name-only --reverse), build_dependency_graph(commits, commit_to_files) (алгоритм file_history — запись последнего коммита, изменившего файл, и формирование зависимостей) и generate_mermaid(graph, commit_to_files, commits) (создание кода Mermaid с короткими хешами и метками вида 'short_hash: commit_message').
  • Примеры команд для инициализации проекта (mkdir, git init), запуска инструмента (python depgraph.py config.ini) и сохранения результата в output.mmd.
  • Файл test_depgraph.py с юнит-тестами для функций построения графа и генерации Mermaid (unittest), демонстрирующий пример входных данных и ожидаемые рёбра графа.
  • README.md с описанием установки (venv), настройки config.ini, примерами использования и визуализации через Mermaid Live Editor.

📊 Для кого подходит

Полезно студентам и разработчикам по программной инженерии и DevOps: для анализа истории версий, аудита изменений, подготовки демонстраций по управлению версиями и для включения в курсовые/рефератные работы по инструментам разработки.

✨ Особенности

Проект выделяется практической реализацией полного конвейера: извлечение коммитов через git, аккуратное парсирование сообщений и списка файлов, построение зависимостей методом 'последний измени́вший файл = источник зависимости', генерация читабельных меток с короткими хешами, вывод в консоль и сохранение в .mmd. В комплекте — юнит-тесты, пошаговые коммиты и README для быстрой настройки.

❓ Частые вопросы

Подойдет ли для моего ВУЗа?
Структура текста, кода и README соответствует академическим требованиям для реферата/практического сопровождения: есть цель, описание функций, инструкции по запуску и тесты.

Можно адаптировать?
Да. Легко сменить формат вывода, добавить фильтрацию файлов по расширению, расширить парсинг commit message или подключить визуализацию в CI.