0Pricing
AI Agents · Lekcja

Historia commitów i analiza diffów

Przeglądanie historii commitów, wyodrębnianie zmienionych plików i podsumowywanie diffów.

Historia commitów i analiza diffów to bezpłatna lekcja AI Agents na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Agents, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Agents zawiera 4 lekcji w sumie.

Dlaczego analizować historię commitów?

Historia commitów to prawdziwa kopalnia wiedzy dla agentów: ujawnia, co się zmieniło, kiedy i przez kogo. Agenci wykorzystują historię commitów do generowania changelogów, podsumowań przeglądów kodu, raportów aktywności deweloperów, wykrywania ryzyka (duże zmiany tuż przed wydaniem) oraz zrozumienia ewolucji bazy kodu.

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')

# Get recent commits on the default branch
commits = repo.get_commits()
print(f'Total commits: {commits.totalCount}')

# Preview the last 5
for commit in commits[:5]:
    print(f'{commit.sha[:8]} | {commit.commit.author.name}')
    print(f'  {commit.commit.message.split(chr(10))[0][:60]}')
    print(f'  {commit.commit.author.date.date()}')

Filtrowanie commitów według zakresu czasu

Należy użyć parametrów since i until, aby pobrać commity z określonego zakresu dat. Oba przyjmują obiekty datetime języka Python. To podstawowe narzędzie do generowania tygodniowych lub miesięcznych raportów aktywności.

import datetime
from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')

# Last 7 days of commits
now = datetime.datetime.utcnow()
last_week = now - datetime.timedelta(days=7)

recent_commits = repo.get_commits(
    since=last_week,
    until=now
)

print(f'Commits in last 7 days: {recent_commits.totalCount}')

# By specific author
author_commits = repo.get_commits(
    since=last_week,
    author='alice'  # GitHub username
)
print(f'Alice\'s commits last week: {author_commits.totalCount}')

# On a specific branch
branch_commits = repo.get_commits(
    sha='feature/new-api',
    since=last_week
)

Statystyki commita: dodane i usunięte wiersze

Każdy obiekt commita ma właściwość stats zawierającą liczbę zmian additions, deletions i total we wszystkich zmienionych plikach. Pozwala to szybko ocenić rozmiar zmiany.

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')

commit = repo.get_commit('abc12345sha')

# Overall stats
print(f'SHA: {commit.sha[:8]}')
print(f'Message: {commit.commit.message[:80]}')
print(f'Author: {commit.commit.author.name}')
print(f'Date: {commit.commit.author.date}')
print(f'Additions: +{commit.stats.additions}')
print(f'Deletions: -{commit.stats.deletions}')
print(f'Total changes: {commit.stats.total}')

# Large commit warning
if commit.stats.total > 500:
    print('WARNING: Large commit — careful code review needed')

Wyświetlanie plików zmienionych w commicie

commit.files to lista obiektów File — po jednym dla każdego pliku zmienionego w commicie. Właściwości: filename, status (added/modified/removed/renamed), additions, deletions, patch (tekst różnic).

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')
commit = repo.get_commit('abc12345sha')

print(f'Files changed: {len(commit.files)}')

for f in commit.files:
    print(f'  [{f.status.upper():<8}] {f.filename}')
    print(f'    +{f.additions} -{f.deletions}')
    if f.previous_filename:  # for renamed files
        print(f'    Renamed from: {f.previous_filename}')

# Find changed Python files
python_changes = [
    f for f in commit.files
    if f.filename.endswith('.py')
]
print(f'\nPython files changed: {len(python_changes)}')

Analizowanie tekstu diffu (patcha)

file.patch zawiera ujednolicony diff. Wiersze zaczynające się od + oznaczają dodane elementy, te zaczynające się od - — usunięte, a wiersze kontekstu nie mają prefiksu. Należy go przeanalizować, aby wyodrębnić dodane wiersze, na przykład w celu znalezienia nowych sekretów, komentarzy TODO lub antywzorców.

def extract_added_lines(patch):
    if not patch:
        return []
    added = []
    for line in patch.split('\n'):
        if line.startswith('+') and not line.startswith('+++'): 
            added.append(line[1:])  # strip the '+' prefix
    return added

def scan_for_secrets(added_lines):
    import re
    PATTERNS = [
        (r'api[_-]?key\s*=\s*["\'][^"\']{20,}', 'potential API key'),
        (r'password\s*=\s*["\'][^"\']{6,}', 'potential hardcoded password'),
        (r'sk-[a-zA-Z0-9]{20,}', 'potential OpenAI key'),
        (r'ghp_[a-zA-Z0-9]{36}', 'potential GitHub token'),
    ]
    findings = []
    for i, line in enumerate(added_lines, 1):
        for pattern, label in PATTERNS:
            if re.search(pattern, line, re.IGNORECASE):
                findings.append({'line': i, 'type': label, 'preview': line[:80]})
    return findings

# --- demo ---
patch = (
    '@@ -1,2 +1,3 @@\n'
    ' def connect():\n'
    '+    api_key = "sk-abcdefghijklmnopqrstuvwx"\n'
    '+    password = "hunter2222"\n'
)
added = extract_added_lines(patch)
print('Added lines:', added)
for finding in scan_for_secrets(added):
    print(finding)

Agregowanie aktywności według autora

Należy grupować commity według autora, aby tworzyć raporty aktywności deweloperów. W wybranym przedziale czasu zliczaj commity, dodane i usunięte wiersze oraz pliki zmodyfikowane przez każdego autora. Jest to przydatne podczas retrospektyw zespołu i podejmowania decyzji o przydzielaniu PR-ów.

import datetime
from collections import defaultdict
from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')

last_month = datetime.datetime.utcnow() - datetime.timedelta(days=30)

author_stats = defaultdict(lambda: {'commits': 0, 'additions': 0, 'deletions': 0, 'files': set()})

for commit in repo.get_commits(since=last_month):
    author = commit.commit.author.name
    author_stats[author]['commits'] += 1
    author_stats[author]['additions'] += commit.stats.additions
    author_stats[author]['deletions'] += commit.stats.deletions
    for f in commit.files:
        author_stats[author]['files'].add(f.filename)

print('Author Activity (last 30 days):')
for author, stats in sorted(author_stats.items(), key=lambda x: -x[1]['commits']):
    print(f'{author}: {stats["commits"]} commits, +{stats["additions"]} -{stats["deletions"]}, {len(stats["files"])} files')

Generowanie changelogu z commitów

Agent generujący changelog odczytuje commity między dwiema etykietami lub datami, grupuje je według typu (feat/fix/chore zgodnie z formatem conventional commits) i tworzy sformatowany changelog. Do poprawy jakości opisu każdego commita należy użyć LLM-a.

import re
import datetime
from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')

def get_commits_since_tag(repo, tag_name):
    try:
        ref = repo.get_git_ref(f'tags/{tag_name}')
        tag_sha = ref.object.sha
        tag_commit = repo.get_commit(tag_sha)
        since = tag_commit.commit.author.date
        return list(repo.get_commits(since=since))[:-1]  # exclude the tag commit
    except Exception:
        return []

def parse_conventional_commit(message):
    match = re.match(r'^(feat|fix|chore|docs|test|refactor|perf|style)(\(.+?\))?: (.+)', message)
    if match:
        return match.group(1), match.group(3)
    return 'other', message.split('\n')[0][:60]

commits = get_commits_since_tag(repo, 'v1.2.0')
by_type = {'feat': [], 'fix': [], 'other': []}
for c in commits:
    msg = c.commit.message
    ctype, desc = parse_conventional_commit(msg)
    by_type.get(ctype, by_type['other']).append(desc)
print(f'Parsed {len(commits)} commits')

Identyfikowanie commitów wysokiego ryzyka

Commity wysokiego ryzyka to takie, które są duże, dotyczą krytycznych plików (uwierzytelnianie, płatności, bezpieczeństwo) lub zostały utworzone tuż przed wydaniem. Agent może oznaczyć je do dodatkowej kontroli przed scaleniem z gałęzią main.

import datetime
from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')

CRITICAL_PATHS = [
    'src/auth', 'src/payment', 'src/security',
    'config/', '.env', 'Dockerfile', 'requirements.txt'
]

def is_high_risk(commit):
    reasons = []

    # Large change
    if commit.stats.total > 300:
        reasons.append(f'Large change: {commit.stats.total} lines')

    # Touches critical files
    for f in commit.files:
        for path in CRITICAL_PATHS:
            if f.filename.startswith(path):
                reasons.append(f'Touches critical: {f.filename}')
                break

    return reasons

now = datetime.datetime.utcnow()
for commit in repo.get_commits(since=now - datetime.timedelta(days=1)):
    risks = is_high_risk(commit)
    if risks:
        print(f'HIGH RISK: {commit.sha[:8]} - {commit.commit.message[:50]}')
        for r in risks:
            print(f'  - {r}')

Porównywanie dwóch commitów

Należy użyć repo.compare(base, head), aby uzyskać różnice między dwoma odwołaniami (gałęziami, tagami lub identyfikatorami SHA commitów). Zwracany jest obiekt Comparison zawierający łączne statystyki, listę commitów oraz listę plików zmienionych między tymi punktami.

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')

# Compare two branches
comparison = repo.compare('main', 'feature/new-api')

print(f'Commits ahead: {comparison.ahead_by}')
print(f'Commits behind: {comparison.behind_by}')
print(f'Status: {comparison.status}')  # ahead/behind/identical/diverged
print(f'Total commits in diff: {len(comparison.commits)}')
print(f'Files changed: {len(comparison.files)}')

# Summary of what changed
for f in comparison.files:
    print(f'  {f.status}: {f.filename} +{f.additions} -{f.deletions}')

# Compare two tags
tag_comparison = repo.compare('v1.0.0', 'v1.1.0')
print(f'Changes between v1.0.0 and v1.1.0: {len(tag_comparison.commits)} commits')

Podsumowanie commitów generowane przez LLM

Przekaż partię komunikatów commitów i nazw plików do LLM-a, aby wygenerować zrozumiałe dla człowieka podsumowanie zmian w danym okresie. To podstawa cotygodniowego bota tworzącego podsumowania prac zespołu inżynieryjnego.

import anthropic
import os

client = anthropic.Anthropic(api_key=os.environ['ANTHROPIC_API_KEY'])

def summarize_commits(commits, time_period='last week'):
    commit_data = []
    for commit in commits[:30]:  # limit to avoid context overflow
        commit_data.append(
            f'- {commit.commit.message.split(chr(10))[0][:80]} '
            f'(+{commit.stats.additions}/-{commit.stats.deletions})'
        )

    commit_list = '\n'.join(commit_data)
    prompt = (
        f'Summarize what happened in this codebase {time_period} '
        f'based on these commit messages. Be concise (3-5 bullet points). '
        f'Focus on user-facing changes and significant technical work.\n\n'
        f'Commits:\n{commit_list}'
    )

    response = client.messages.create(
        model='claude-opus-4-5',
        max_tokens=400,
        messages=[{'role': 'user', 'content': prompt}]
    )
    return response.content[0].text

Wykrywanie wrażliwych plików w commitach

Agent analizujący commity pod kątem bezpieczeństwa skanuje commity, które przypadkowo zawierają wrażliwe pliki: klucze prywatne, pliki .env, konfiguracje z danymi uwierzytelniającymi lub zrzuty baz danych. Należy sprawdzić commit.files pod kątem nazw plików pasujących do znanych wzorców wrażliwych danych.

import re
from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')

SENSITIVE_PATTERNS = [
    r'\.env$', r'\.pem$', r'\.key$', r'id_rsa', r'credentials\.json',
    r'secret', r'password', r'\.p12$', r'keystore', r'\btoken\b'
]

def is_sensitive_filename(filename):
    for pattern in SENSITIVE_PATTERNS:
        if re.search(pattern, filename, re.IGNORECASE):
            return pattern
    return None

import datetime
last_day = datetime.datetime.utcnow() - datetime.timedelta(days=1)

for commit in repo.get_commits(since=last_day):
    for f in commit.files:
        matched = is_sensitive_filename(f.filename)
        if matched and f.status in ('added', 'modified'):
            print(f'ALERT: Sensitive file in commit {commit.sha[:8]}')
            print(f'  File: {f.filename} (matched: {matched})')
            print(f'  Author: {commit.commit.author.name}')
            print(f'  Link: {commit.html_url}')

Szybki test: commit.files a pr.get_files()

Proszę sprawdzić swoją wiedzę na temat dostępu do diffów commitów.

Podsumowanie analizy historii commitów

Agent potrafi teraz kompleksowo analizować historię commitów:

  • repo.get_commits(since, until, author, sha) — filtrowanie commitów według czasu, autora lub gałęzi
  • commit.stats — liczba dodanych, usuniętych i wszystkich elementów we wszystkich zmienionych plikach
  • commit.files — szczegóły poszczególnych plików, w tym patch (ujednolicony diff)
  • Analizowanie file.patch w celu skanowania dodanych wierszy pod kątem sekretów, TODO lub antywzorców
  • Grupowanie commitów według autora na potrzeby raportów aktywności
  • repo.compare(base, head) — diff między dwoma odwołaniami (gałęziami, tagami lub identyfikatorami SHA)
  • Używanie LLM-ów do generowania zrozumiałych dla człowieka podsumowań na podstawie list komunikatów commitów
  • Oznaczanie commitów wysokiego ryzyka: duże zmiany + krytyczne pliki + bliskość wydania

Często zadawane pytania

Czy lekcja „Historia commitów i analiza diffów” jest bezpłatna?

Tak — pełny tekst „Historia commitów i analiza diffów” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Agents, przejdź na CoddyKit PRO. Kurs AI Agents zawiera 4 lekcji w sumie.

Co nauczysz się w „Historia commitów i analiza diffów”?

Przeglądanie historii commitów, wyodrębnianie zmienionych plików i podsumowywanie diffów. Ćwiczysz AI Agents z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Agents?

Nie wymagamy żadnego doświadczenia. AI Agents w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Historia commitów i analiza diffów”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Agents?

Tak. Każda lekcja AI Agents zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Przegląd GitHub REST API
  2. Listowanie i zarządzanie zgłoszeniami
  3. Automatyczne komentarze w przeglądach PR
  4. Historia commitów i analiza diffów
← Powrót do AI Agents