Git Integration¶
Complete guide to Git workflow integration in CCGO projects, including automated operations, hooks, and best practices.
Overview¶
CCGO provides seamless Git integration for version control workflows:
- Automated Tagging - Create version tags from CCGO.toml
- Git Hooks - Pre-commit validation and automation
- Submodule Management - Handle C++ dependencies as submodules
- CI/CD Integration - Automated builds on git events
- Branch Workflows - Support for GitFlow and trunk-based development
- Commit Metadata - Inject git info into builds
Git Workflow Patterns¶
Trunk-Based Development¶
# Main branch is always deployable
main ─────●─────●─────●─────●─────●──→
\ \ \ \
\─● \─● \─● \─● short-lived
| | | | feature branches
merge merge merge merge
Workflow:
# 1. Create short-lived feature branch
git checkout -b feature/quick-fix
# 2. Make changes and commit
ccgo build --all
git commit -am "feat: add new API endpoint"
# 3. Merge to main (after CI passes)
git checkout main
git merge feature/quick-fix
# 4. Tag release
ccgo tag v1.2.3 --message "Release 1.2.3"
GitFlow¶
main ────●─────────────●─────────────●──→ production releases
\ / /
\ / /
develop ────●───●───●───●───●───●───●──→ integration branch
\ | / \ /
\ | / \ /
feature-a ●●● \ /
feature-b ●●●
Workflow:
# 1. Create feature branch from develop
git checkout develop
git checkout -b feature/new-module
# 2. Develop feature
ccgo build --all
ccgo test
# 3. Merge back to develop
git checkout develop
git merge feature/new-module
# 4. Create release branch
git checkout -b release/1.2.0
# 5. Finalize release
# Update CCGO.toml: version = "1.2.0"
ccgo build --all --release
ccgo test --all
# 6. Merge to main and develop
git checkout main
git merge release/1.2.0
ccgo tag v1.2.0 --message "Release 1.2.0"
git checkout develop
git merge release/1.2.0
Automated Tagging¶
Using ccgo tag¶
# Create tag from CCGO.toml version
ccgo tag
# Create tag with custom version
ccgo tag v2.0.0
# Create annotated tag with message
ccgo tag --annotate --message "Release 2.0.0 with new features"
# Push tag to remote
ccgo tag --push
# Force update existing tag
ccgo tag --force
Tag Naming Convention¶
CCGO follows semantic versioning for tags:
# Release tags
v1.0.0 # Major release
v1.2.3 # Minor/patch release
v2.0.0-rc.1 # Release candidate
v1.5.0-beta.2 # Beta release
# Internal tags
build-20240115 # CI build tag
dev-john-123 # Development tag
Automated Tag Creation in CI¶
# GitHub Actions
name: Tag Release
on:
push:
branches: [main]
jobs:
tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install CCGO
run: pip install ccgo
- name: Create tag if version changed
run: |
# Extract version from CCGO.toml
VERSION=$(grep '^version = ' CCGO.toml | cut -d'"' -f2)
# Check if tag exists
if ! git rev-parse "v$VERSION" >/dev/null 2>&1; then
ccgo tag "v$VERSION" --message "Release $VERSION" --push
fi
Git Hooks¶
Pre-Commit Hook¶
Validates code before commit:
# .git/hooks/pre-commit
#!/bin/bash
# Format check
echo "Running code formatting check..."
if ! ccgo check --format; then
echo "Error: Code formatting issues found"
echo "Run: ccgo format --fix"
exit 1
fi
# Build check
echo "Running build check..."
if ! ccgo build --quick-check; then
echo "Error: Build check failed"
exit 1
fi
# License headers
echo "Checking license headers..."
if ! ccgo check --license; then
echo "Error: Missing license headers"
exit 1
fi
echo "Pre-commit checks passed!"
Pre-Push Hook¶
Runs tests before pushing:
# .git/hooks/pre-push
#!/bin/bash
# Run unit tests
echo "Running unit tests..."
if ! ccgo test; then
echo "Error: Tests failed"
exit 1
fi
# Check for TODO/FIXME in changed files
echo "Checking for unresolved TODOs..."
changed_files=$(git diff --name-only @{u}..HEAD)
if echo "$changed_files" | xargs grep -n "FIXME\|TODO" 2>/dev/null; then
read -p "Found TODOs/FIXMEs. Continue push? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
echo "Pre-push checks passed!"
Commit-Msg Hook¶
Enforces conventional commits:
# .git/hooks/commit-msg
#!/bin/bash
commit_msg=$(cat "$1")
# Check conventional commit format
if ! echo "$commit_msg" | grep -qE "^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .+"; then
echo "Error: Commit message must follow Conventional Commits format"
echo ""
echo "Format: <type>(<scope>): <subject>"
echo ""
echo "Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert"
echo ""
echo "Example: feat(android): add new build configuration"
exit 1
fi
echo "Commit message format validated!"
Installing Hooks¶
# Make hooks executable
chmod +x .git/hooks/pre-commit
chmod +x .git/hooks/pre-push
chmod +x .git/hooks/commit-msg
# Or use CCGO to install hooks
ccgo install-hooks
Submodule Management¶
Adding C++ Dependencies as Submodules¶
# Add third-party library as submodule
git submodule add https://github.com/openssl/openssl.git third_party/openssl
# Initialize and update submodules
git submodule update --init --recursive
# Update submodules to latest
git submodule update --remote --recursive
CCGO.toml with Submodules¶
[dependencies]
# Git submodule dependency
openssl = { path = "third_party/openssl", type = "submodule" }
# Specific version via git tag
boost = {
git = "https://github.com/boostorg/boost.git",
tag = "boost-1.80.0",
type = "submodule"
}
# Branch tracking
nlohmann_json = {
git = "https://github.com/nlohmann/json.git",
branch = "develop",
type = "submodule"
}
Submodule Workflow¶
# Clone project with submodules
git clone --recurse-submodules https://github.com/user/myproject.git
# Update existing clone
git submodule update --init --recursive
# Update specific submodule
cd third_party/openssl
git checkout v3.0.0
cd ../..
git add third_party/openssl
git commit -m "chore: update openssl to v3.0.0"
# Remove submodule
git submodule deinit third_party/oldlib
git rm third_party/oldlib
Commit Metadata Injection¶
Build-Time Git Info¶
CCGO automatically injects git metadata into builds:
// Auto-generated in include/<project>/version.h
#define MYLIB_GIT_SHA "8f3a2b1c"
#define MYLIB_GIT_BRANCH "main"
#define MYLIB_GIT_TAG "v1.2.3"
#define MYLIB_GIT_DIRTY 0 // 1 if uncommitted changes
namespace mylib {
const char* get_git_sha();
const char* get_git_branch();
const char* get_git_tag();
bool is_git_dirty();
}
Using Git Info in Code¶
#include "mylib/version.h"
#include <iostream>
void print_build_info() {
std::cout << "Version: " << mylib::get_version() << "\n";
std::cout << "Git SHA: " << mylib::get_git_sha() << "\n";
std::cout << "Git Branch: " << mylib::get_git_branch() << "\n";
std::cout << "Git Tag: " << mylib::get_git_tag() << "\n";
if (mylib::is_git_dirty()) {
std::cout << "Warning: Built from dirty working tree\n";
}
}
Disabling Git Injection¶
# Build without git metadata
ccgo build android --no-git-inject
# Or configure in CCGO.toml
[version]
inject_git_metadata = false
Branch Protection¶
GitHub Branch Protection Rules¶
# .github/branch-protection.yml
main:
required_status_checks:
strict: true
contexts:
- "build-android"
- "build-ios"
- "test-all"
- "lint"
required_pull_request_reviews:
required_approving_review_count: 2
dismiss_stale_reviews: true
enforce_admins: false
restrictions: null
develop:
required_status_checks:
strict: false
contexts:
- "build-android"
- "test-all"
required_pull_request_reviews:
required_approving_review_count: 1
Pre-Receive Hook (Server-Side)¶
# Prevent direct pushes to main
#!/bin/bash
while read oldrev newrev refname; do
if [ "$refname" = "refs/heads/main" ]; then
echo "Error: Direct pushes to main are not allowed"
echo "Please create a pull request instead"
exit 1
fi
done
CI/CD Integration¶
GitHub Actions¶
name: CI Build
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
create:
tags:
- 'v*'
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
platform: [android, ios]
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0 # Full history for git describe
- name: Install CCGO
run: pip install ccgo
- name: Build
run: ccgo build ${{ matrix.platform }} --release
- name: Test
run: ccgo test ${{ matrix.platform }}
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.platform }}-build
path: target/${{ matrix.platform }}/
GitLab CI¶
stages:
- build
- test
- deploy
variables:
GIT_SUBMODULE_STRATEGY: recursive
build:android:
stage: build
tags: [android]
script:
- ccgo build android --release
artifacts:
paths:
- target/android/
expire_in: 1 week
build:ios:
stage: build
tags: [macos]
script:
- ccgo build ios --release
artifacts:
paths:
- target/ios/
expire_in: 1 week
test:all:
stage: test
script:
- ccgo test --all
dependencies:
- build:android
- build:ios
deploy:release:
stage: deploy
only:
- tags
script:
- ccgo publish --all --registry official
Jenkins Pipeline¶
pipeline {
agent any
environment {
CCGO_VERSION = sh(script: "grep '^version' CCGO.toml | cut -d'\"' -f2", returnStdout: true).trim()
}
stages {
stage('Checkout') {
steps {
checkout scm
sh 'git submodule update --init --recursive'
}
}
stage('Build') {
parallel {
stage('Android') {
steps {
sh 'ccgo build android --release'
}
}
stage('iOS') {
steps {
sh 'ccgo build ios --release'
}
}
}
}
stage('Test') {
steps {
sh 'ccgo test --all'
}
}
stage('Tag') {
when {
branch 'main'
}
steps {
sh "ccgo tag v${CCGO_VERSION} --message 'Release ${CCGO_VERSION}' --push"
}
}
}
}
Monorepo Management¶
Git Subtree for Shared Libraries¶
# Add shared library as subtree
git subtree add --prefix=libs/common \
https://github.com/company/common-lib.git main --squash
# Update subtree
git subtree pull --prefix=libs/common \
https://github.com/company/common-lib.git main --squash
# Push changes back to subtree
git subtree push --prefix=libs/common \
https://github.com/company/common-lib.git feature-branch
Sparse Checkout¶
# Clone only specific directories
git clone --filter=blob:none --sparse https://github.com/user/monorepo.git
cd monorepo
git sparse-checkout set libs/mylib docs/mylib
Git LFS for Binary Assets¶
Setup¶
# Install Git LFS
git lfs install
# Track binary files
git lfs track "*.so"
git lfs track "*.dylib"
git lfs track "*.dll"
git lfs track "*.a"
git lfs track "assets/*.png"
# Commit .gitattributes
git add .gitattributes
git commit -m "chore: add Git LFS tracking"
.gitattributes¶
# Native libraries
*.so filter=lfs diff=lfs merge=lfs -text
*.dylib filter=lfs diff=lfs merge=lfs -text
*.dll filter=lfs diff=lfs merge=lfs -text
*.a filter=lfs diff=lfs merge=lfs -text
# Assets
*.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
Best Practices¶
1. Commit Messages¶
Follow Conventional Commits:
# Good commit messages
git commit -m "feat(android): add ARMv7 support"
git commit -m "fix(ios): resolve memory leak in network module"
git commit -m "docs: update installation guide"
git commit -m "chore: bump version to 1.2.3"
git commit -m "refactor(core): simplify error handling"
git commit -m "test: add unit tests for calculator module"
# Breaking change
git commit -m "feat(api)!: change function signature
BREAKING CHANGE: Calculator.add() now returns Result<int>"
2. Branch Naming¶
# Feature branches
feature/user-authentication
feature/android-arm64-support
# Bug fix branches
fix/memory-leak-ios
fix/crash-on-startup
# Release branches
release/1.2.0
release/2.0.0-rc.1
# Hotfix branches
hotfix/1.2.1
hotfix/security-patch
3. Pull Request Workflow¶
# 1. Create feature branch
git checkout -b feature/new-api
# 2. Make changes and commit frequently
git commit -am "wip: implement new API"
git commit -am "feat: complete new API implementation"
git commit -am "test: add API tests"
# 3. Rebase on main before PR
git fetch origin
git rebase origin/main
# 4. Push and create PR
git push origin feature/new-api
# 5. Address review comments
git commit -am "fix: address review comments"
git push origin feature/new-api
# 6. Squash commits before merge (optional)
git rebase -i origin/main
4. Signing Commits¶
# Generate GPG key
gpg --gen-key
# Configure git to use GPG key
git config --global user.signingkey YOUR_KEY_ID
git config --global commit.gpgsign true
# Sign commits
git commit -S -m "feat: add signed commit"
# Verify signature
git log --show-signature
5. Git Aliases¶
# Add useful aliases to ~/.gitconfig
[alias]
co = checkout
br = branch
ci = commit
st = status
unstage = reset HEAD --
last = log -1 HEAD
visual = log --graph --oneline --decorate --all
# CCGO-specific aliases
ccgo-build = "!f() { ccgo build $1 --release; }; f"
ccgo-tag = "!f() { ccgo tag --push; }; f"
Troubleshooting¶
Submodule Issues¶
Problem:
Solution:
# Re-initialize submodules
git submodule deinit -f third_party/lib
rm -rf .git/modules/third_party/lib
git submodule update --init --recursive
Detached HEAD in Submodule¶
Problem:
Solution:
cd third_party/lib
git checkout main # or appropriate branch
cd ../..
git add third_party/lib
git commit -m "chore: fix submodule detached HEAD"
Large Repository Size¶
Problem: Repository is too large due to binary files in history
Solution:
# Use git filter-repo (safer than filter-branch)
pip install git-filter-repo
# Remove large files from history
git filter-repo --path-glob '*.so' --invert-paths
git filter-repo --path-glob '*.a' --invert-paths
# Migrate to Git LFS
git lfs migrate import --include="*.so,*.a,*.dylib,*.dll"