Jump to content
View in the app

A better way to browse. Learn more.

T.M.I IThub

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

Git: это не просто "сохранить файл"

Git изобрёл Линус Торвальдс в 2005 году за две недели — потому что существующие системы контроля версий его раздражали. Результат стал стандартом де-факто для всей современной разработки.

Но большинство разработчиков используют только 10% возможностей Git: git add, git commit, git push. И потом удивляются, почему в команде хаос, история проекта нечитаема, а деплой — это страшный ритуал.

Правильное использование Git — это не набор команд, это культура разработки. Сегодня разберём, как устроена эта культура в реальных командах.


Анатомия правильного коммита

Коммит — это единица изменений. Плохой коммит: "исправил баги и добавил фичи". Хороший коммит: одно логическое изменение, понятное описание.

Conventional Commits — стандарт сообщений

<type>(<scope>): <description>

[optional body]

[optional footer(s)]

Типы:

  • feat — новая функциональность

  • fix — исправление бага

  • docs — только документация

  • style — форматирование, точки с запятой (нет изменений логики)

  • refactor — рефакторинг (нет новой функциональности, нет фикса)

  • perf — оптимизация производительности

  • test — добавление тестов

  • chore — обслуживание: обновление зависимостей, конфигурации CI

  • ci — изменения CI/CD конфигурации

  • revert — откат предыдущего коммита

Примеры:

# Плохо:
git commit -m "fix"
git commit -m "wip"
git commit -m "changes"
git commit -m "поправил немного"

# Хорошо:
git commit -m "fix(auth): исправлена утечка токена при logout"
git commit -m "feat(modbus): добавлена поддержка FC15 (write multiple coils)"
git commit -m "perf(historian): оптимизирован batch-insert, +340% throughput"
git commit -m "docs(api): добавлены примеры для /api/v1/devices endpoint"

# С телом для сложных изменений:
git commit -m "fix(plc): исправлено переполнение счётчика при rollover

Счётчик типа UINT использовался для значений >65535.
Изменён на DINT (32-бит). Затронутые устройства: все узлы с FC03.

Closes #247"

Почему это важно?

  1. Автоматический CHANGELOG — инструменты как conventional-changelog генерируют его автоматически

  2. Семантическое версионированиеfeat → minor, fix → patch, feat! или BREAKING CHANGE → major

  3. Читаемая история — через год понятно что и зачем было сделано

  4. Быстрый поискgit log --grep="fix(modbus)" найдёт все фиксы Modbus


Стратегии ветвления

Git Flow — классика для релизного цикла

main ────────────────────────────────────── (production-ready, теги версий)
   \                                      /
release/1.2.0 ───────────────────────────  (только bagfixes перед релизом)
         \                               /
develop ──────────────────────────────── (интеграция фич)
          \          \          /
           feat/A     feat/B  feat/C

Ветки:

  • main — всегда стабильный, деплоится в прод, только через merge из release/*

  • develop — основная ветка разработки, всегда должна собираться

  • feature/* — новые фичи, создаются из develop, мержатся в develop

  • release/* — подготовка релиза (версия, changelog), только bugfix

  • hotfix/* — срочные фиксы прод, мержатся в main И develop

# Создать фичу:
git checkout develop
git checkout -b feature/modbus-fc15-support

# Завершить фичу:
git checkout develop
git merge --no-ff feature/modbus-fc15-support  # --no-ff сохраняет историю
git branch -d feature/modbus-fc15-support

# Подготовить релиз:
git checkout develop
git checkout -b release/1.2.0
# Обновить версию, CHANGELOG...
git commit -m "chore(release): version 1.2.0"
git checkout main
git merge --no-ff release/1.2.0
git tag -a v1.2.0 -m "Release 1.2.0"
git checkout develop
git merge --no-ff release/1.2.0

Trunk-Based Development — для быстрых команд

Все разработчики работают в одной ветке (main), фичи прячутся за feature-флагами. Деплой несколько раз в день. Подходит для опытных команд с хорошим покрытием тестами.

# Только короткоживущие ветки (1-2 дня максимум)
git checkout -b task/PLC-247-fix-counter-overflow
# ... работа ...
git push origin task/PLC-247-fix-counter-overflow
# Pull Request → Review → Merge в main
# Деплой автоматически

GitHub Flow — для непрерывного деплоя

Упрощённый Git Flow без develop:

  • main = то, что в проде

  • Feature branches — от main, в main через PR

  • Деплой = merge в main


Code Review: как делать правильно

Code review — не поиск ошибок, это обмен знаниями и повышение качества. Хороший review делает команду сильнее.

Для автора PR:

## Описание
Добавлена поддержка записи нескольких coils (FC15) в Modbus slave.

## Мотивация
Клиент запросил управление 16 выходными реле через один Modbus-запрос
вместо 16 отдельных FC05. Уменьшает нагрузку на шину в 16 раз.

## Изменения
- `ModbusSlave::handle_fc15()` — новый обработчик функционального кода 15
- Обновлён маппинг coils на GPIO пины
- Добавлены unit-тесты: 8 тест-кейсов

## Тестирование
- [x] Unit-тесты: все зелёные
- [x] Интеграционный тест с реальным Modbus-мастером (Python pymodbus)
- [x] Проверен на железе: Raspberry Pi + MCP2551

## Breaking Changes
Нет. FC05 продолжает работать.

## Связанные Issues
Closes #247

Для ревьюера:

Проверяйте:

  1. Логику — правильно ли реализовано то, что задумано?

  2. Граничные случаи — что при пустых данных? При переполнении? При сетевой ошибке?

  3. Безопасность — нет ли SQL-инъекций, XSS, незащищённых данных?

  4. Производительность — нет ли N+1 запросов, бесконечных циклов?

  5. Тесты — покрывают ли они описанную функциональность?

  6. Документацию — понятно ли из кода и комментариев что происходит?

Не проверяйте:

  • Стиль форматирования (для этого есть линтеры и форматтеры)

  • Личные предпочтения (если оба подхода корректны)

Тон комментариев:

Это неправильно, так делать нельзя

Здесь возможно переполнение при dlc > 8, как насчёт проверки?

Почему ты использовал цикл вместо map()?

Можно ли тут использовать list comprehension для читаемости?

Нет, переделай.

Мне кажется, паттерн Strategy тут подошёл бы лучше — как думаешь?


GitHub Actions: автоматизация всего

# .github/workflows/ci.yml
name: CI/CD Pipeline

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

jobs:
  # ===== ЛИНТИНГ И СТАТИЧЕСКИЙ АНАЛИЗ =====
  lint:
    name: Lint & Static Analysis
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
          cache: 'pip'
      
      - name: Install dependencies
        run: |
          pip install flake8 pylint mypy black isort
          pip install -r requirements.txt
      
      - name: Check formatting (black)
        run: black --check --diff .
      
      - name: Check imports (isort)
        run: isort --check-only --diff .
      
      - name: Lint (flake8)
        run: flake8 . --max-line-length=100 --exclude=.venv,migrations
      
      - name: Type check (mypy)
        run: mypy src/ --strict --ignore-missing-imports

  # ===== ТЕСТИРОВАНИЕ =====
  test:
    name: Unit & Integration Tests
    runs-on: ubuntu-latest
    needs: lint
    
    services:
      # Поднимаем сервисы для интеграционных тестов
      redis:
        image: redis:7
        ports: ['6379:6379']
      
      mosquitto:
        image: eclipse-mosquitto:2
        ports: ['1883:1883']
    
    strategy:
      matrix:
        python-version: ['3.10', '3.11', '3.12']  # Тестируем на всех версиях
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
          cache: 'pip'
      
      - name: Install dependencies
        run: pip install -r requirements.txt -r requirements-dev.txt
      
      - name: Run tests with coverage
        run: |
          pytest tests/ \
            --cov=src \
            --cov-report=xml \
            --cov-report=term-missing \
            --cov-fail-under=80 \
            -v
        env:
          REDIS_URL: redis://localhost:6379
          MQTT_HOST: localhost
      
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage.xml

  # ===== СБОРКА DOCKER ОБРАЗА =====
  build:
    name: Build & Push Docker Image
    runs-on: ubuntu-latest
    needs: test
    if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: Login to Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix=sha-
      
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64  # Для x86 серверов И Raspberry Pi
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # ===== ДЕПЛОЙ =====
  deploy-staging:
    name: Deploy to Staging
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/develop'
    environment: staging
    
    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.STAGING_HOST }}
          username: deploy
          key: ${{ secrets.STAGING_SSH_KEY }}
          script: |
            cd /opt/gateway
            docker compose pull
            docker compose up -d --remove-orphans
            docker compose ps
  
  deploy-production:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: build
    if: startsWith(github.ref, 'refs/tags/v')
    environment: production  # Требует одобрения в GitHub
    
    steps:
      - name: Deploy to production
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.PROD_HOST }}
          username: deploy
          key: ${{ secrets.PROD_SSH_KEY }}
          script: |
            cd /opt/gateway
            export IMAGE_TAG=${{ github.ref_name }}
            docker compose pull
            docker compose up -d --remove-orphans
            
            # Smoke test
            sleep 10
            curl -f http://localhost:8080/health || (docker compose logs && exit 1)

Git Hooks: автоматизация на уровне репозитория

# .git/hooks/pre-commit (запускается перед каждым коммитом)
#!/bin/bash
set -e

echo "

🔍 Pre-commit проверки..." # Форматирование Python if command -v black &> /dev/null; then

black --check . --quiet

if [ $? -ne 0 ]; then

echo " Форматирование не соответствует black. Запустите: black ."

exit 1

fi fi # Быстрые тесты (только изменённые файлы)

CHANGED_PY=$(git diff --cached --name-only --diff-filter=ACM | grep '\.py$') if [ -n "$CHANGED_PY" ]; then pytest tests/unit/ -x -q --tb=short fi

echo " Все проверки прошли"

Лучше использовать pre-commit framework:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-json
      - id: check-merge-conflict
      - id: detect-private-key    # Не допускаем секреты в коде!
  
  - repo: https://github.com/psf/black
    rev: 23.10.1
    hooks:
      - id: black
  
  - repo: https://github.com/pycqa/isort
    rev: 5.12.0
    hooks:
      - id: isort
  
  - repo: https://github.com/commitizen-tools/commitizen
    rev: v3.12.0
    hooks:
      - id: commitizen  # Проверяет формат сообщения коммита
# Установка:
pip install pre-commit
pre-commit install  # Устанавливает хуки в .git/hooks/
pre-commit run --all-files  # Запуск вручную

Семантическое версионирование и автоматический релиз

Semantic Versioning: MAJOR.MINOR.PATCH

  • PATCH (1.2.3 → 1.2.4): багфиксы, обратно совместимые изменения

  • MINOR (1.2.4 → 1.3.0): новая функциональность, обратно совместимая

  • MAJOR (1.3.0 → 2.0.0): несовместимые изменения API

Автоматический выпуск с commitizen:

# pyproject.toml:
[tool.commitizen]
name = "cz_conventional_commits"
tag_format = "v$version"
version_scheme = "semver"
version_files = [
    "src/__init__.py:__version__",
    "pyproject.toml:version"
]
update_changelog_on_bump = true

# Команды:
cz bump              # Автоматически определяет тип bumpa из коммитов
cz bump --major      # Принудительно major
cz changelog         # Генерирует CHANGELOG.md

Автоматический CHANGELOG.md из conventional commits:

## v1.3.0 (2024-03-15)

### Features
- **modbus**: добавлена поддержка FC15 (write multiple coils) (#247)
- **historian**: реализован deadband-алгоритм сжатия, экономия 78% места

### Bug Fixes
- **uart**: исправлена потеря байт при высоких нагрузках (#251)
- **pid**: устранено интегральное насыщение при длительной работе

### Performance
- **batch-write**: оптимизирован bulk insert в InfluxDB, +340% throughput

Практические советы

.gitignore — не игнорируйте важное

# Python
__pycache__/
*.pyc
*.pyo
.venv/
.env
venv/

# IDE
.idea/
.vscode/
*.swp
*.swo

# Сборка
dist/
build/
*.egg-info/

# Тесты
.coverage
htmlcov/
.pytest_cache/

# Секреты (НИКОГДА не коммитить!)
*.key
*.pem
.env.local
config.secret.yaml

# OS
.DS_Store
Thumbs.db

Работа с секретами — никогда в репозиторий!

# Плохо: секреты в коде
MQTT_PASSWORD = "supersecret123"

# Хорошо: из переменных окружения
MQTT_PASSWORD = os.getenv("MQTT_PASSWORD")  # В .env локально, в CI — secrets

# Для локальной разработки: .env файл (в .gitignore!)
# pip install python-dotenv
from dotenv import load_dotenv
load_dotenv()

# Для проверки что секрет не утёк:
git log --all --full-history -- "*.env"  # Поиск в истории
git grep "supersecret"  # Поиск в текущем состоянии

Интерактивный rebase для чистой истории

# Перед мержем PR привести историю в порядок
git rebase -i origin/main

# Редактор покажет:
# pick a1b2c3 WIP fix something
# pick d4e5f6 another fix  
# pick g7h8i9 добавил логирование

# Меняем на:
# reword a1b2c3 fix(modbus): исправлен CRC при DLC=0
# squash d4e5f6  # Объединить с предыдущим
# pick g7h8i9 feat(logging): добавлено структурированное логирование

# Результат: чистая, осмысленная история

Заключение

Git — это не инструмент, это язык коммуникации в команде. Правильные коммиты рассказывают историю проекта. Грамотное ветвление изолирует работу. CI/CD устраняет ручной труд и человеческие ошибки при деплое.

Начните с малого: установите .pre-commit-config.yaml с black и detect-private-key. Перейдите на conventional commits. Добавьте один GitHub Actions workflow с тестами. Каждый из этих шагов принесёт немедленную пользу.

Инвестиция в культуру работы с кодом возвращается многократно: меньше времени на дебаггинг, меньше страха перед деплоем, больше времени на реальную разработку.

User Feedback

Create an account or sign in to leave a review

There are no reviews to display.

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.