Version Control với Git - Quản lý source code Java

Tổng quan về Git

Git là distributed version control system được sử dụng rộng rãi để quản lý source code trong các dự án Java. Git giúp track changes, collaboration, và maintain history của codebase.

Git Workflow cho Java Projects

Gitflow Workflow

# Initialize git repository
git init
git remote add origin https://github.com/username/java-project.git

# Create develop branch from main
git checkout -b develop
git push -u origin develop

# Feature development
git checkout -b feature/user-authentication develop
# Work on feature...
git add .
git commit -m "Add user authentication service"
git push origin feature/user-authentication

# Merge feature to develop
git checkout develop
git merge --no-ff feature/user-authentication
git branch -d feature/user-authentication
git push origin develop

# Release preparation
git checkout -b release/1.0.0 develop
# Bug fixes and final preparations...
git commit -m "Bump version to 1.0.0"

# Merge to main and tag
git checkout main
git merge --no-ff release/1.0.0
git tag -a v1.0.0 -m "Release version 1.0.0"
git push origin main --tags

# Merge back to develop
git checkout develop
git merge --no-ff release/1.0.0
git branch -d release/1.0.0

GitHub Flow (Simplified)

# Create feature branch
git checkout -b feature/add-user-service

# Make changes
echo "UserService implementation" > src/main/java/UserService.java
git add .
git commit -m "feat: add UserService with CRUD operations"

# Push and create Pull Request
git push origin feature/add-user-service
# Open Pull Request on GitHub

# After review and approval
git checkout main
git pull origin main
git branch -d feature/add-user-service

Git Configuration cho Java Projects

Global Configuration

# User configuration
git config --global user.name "John Doe"
git config --global user.email "john.doe@company.com"

# Editor configuration
git config --global core.editor "code --wait"

# Default branch
git config --global init.defaultBranch main

# Line ending handling
git config --global core.autocrlf input  # Linux/Mac
git config --global core.autocrlf true   # Windows

# Merge tool
git config --global merge.tool vimdiff

.gitignore cho Java Projects

# Compiled class files
*.class

# Log files
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# Virtual machine crash logs
hs_err_pid*

# Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar

# Gradle
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

# IntelliJ IDEA
.idea/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/

# Eclipse
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

# NetBeans
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

# VS Code
.vscode/

# Mac
.DS_Store

# Windows
Thumbs.db
ehthumbs.db
Desktop.ini

# Application specific
application-*.properties
!application.properties
!application-example.properties

# Database
*.db
*.sqlite

# Logs
logs/
*.log

# Temporary files
*.tmp
*.swp
*~

# Security
*.key
*.pem
secrets.yml

Git Hooks cho Java Projects

Pre-commit Hook

#!/bin/sh
# .git/hooks/pre-commit

echo "Running pre-commit checks..."

# Check if this is the initial commit
if git rev-parse --verify HEAD >/dev/null 2>&1
then
    against=HEAD
else
    # Initial commit: diff against an empty tree object
    against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

# Redirect output to stderr
exec 1>&2

# Check Java code formatting
echo "Checking Java code formatting..."
if command -v google-java-format >/dev/null 2>&1; then
    java_files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.java$')
    if [ -n "$java_files" ]; then
        for file in $java_files; do
            if ! google-java-format --dry-run --set-exit-if-changed "$file" >/dev/null 2>&1; then
                echo "❌ Code formatting issues found in $file"
                echo "Please run: google-java-format --replace $file"
                exit 1
            fi
        done
    fi
fi

# Run tests
echo "Running tests..."
if [ -f "mvnw" ]; then
    ./mvnw test -q
elif [ -f "gradlew" ]; then
    ./gradlew test --quiet
elif command -v mvn >/dev/null 2>&1; then
    mvn test -q
else
    echo "No build tool found (Maven/Gradle)"
    exit 1
fi

if [ $? -ne 0 ]; then
    echo "❌ Tests failed. Please fix before committing."
    exit 1
fi

# Check for debug statements
echo "Checking for debug statements..."
if git diff --cached --name-only | xargs grep -l "System.out.println\|printStackTrace\|//TODO\|//FIXME" 2>/dev/null; then
    echo "❌ Found debug statements or TODO/FIXME comments."
    echo "Please remove or replace them before committing."
    exit 1
fi

echo "✅ All pre-commit checks passed!"

Pre-push Hook

#!/bin/sh
# .git/hooks/pre-push

protected_branch='main'
current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,')

echo "Pre-push hook: Checking branch $current_branch"

# Check if we're pushing to protected branch
if [ $current_branch = $protected_branch ]; then
    echo "Direct push to $protected_branch is not allowed"
    echo "Please create a pull request instead"
    exit 1
fi

# Run integration tests
echo "Running integration tests..."
if [ -f "mvnw" ]; then
    ./mvnw verify -P integration-tests
elif [ -f "gradlew" ]; then
    ./gradlew integrationTest
fi

if [ $? -ne 0 ]; then
    echo "❌ Integration tests failed"
    exit 1
fi

echo "✅ Pre-push checks passed!"

Conventional Commits

Commit Message Format

# Format: <type>[optional scope]: <description>
# [optional body]
# [optional footer(s)]

# Examples:
git commit -m "feat(auth): add JWT token authentication"
git commit -m "fix(user): resolve null pointer exception in getUserById"
git commit -m "docs(api): update swagger documentation"
git commit -m "test(service): add unit tests for UserService"
git commit -m "refactor(controller): extract validation logic"
git commit -m "perf(db): optimize user query with index"

# Breaking change
git commit -m "feat(api): change user endpoint response format

BREAKING CHANGE: User endpoint now returns user object instead of array"

Types và Scopes

# Types:
# feat: new feature
# fix: bug fix
# docs: documentation
# style: formatting, missing semicolons, etc
# refactor: code refactoring
# perf: performance improvement
# test: adding tests
# chore: maintenance tasks

# Scopes (examples for Java project):
# api: REST API changes
# auth: authentication/authorization
# db: database related
# service: business logic
# controller: web controllers
# config: configuration changes
# security: security related
# cache: caching logic

Git Branching Strategies

Feature Branch Strategy

// UserService.java - working on feature branch
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    // New feature: user validation
    public User createUser(CreateUserRequest request) {
        validateUserRequest(request);

        User user = new User();
        user.setUsername(request.getUsername());
        user.setEmail(request.getEmail());

        return userRepository.save(user);
    }

    private void validateUserRequest(CreateUserRequest request) {
        if (StringUtils.isBlank(request.getUsername())) {
            throw new ValidationException("Username is required");
        }

        if (!EmailValidator.isValid(request.getEmail())) {
            throw new ValidationException("Invalid email format");
        }

        if (userRepository.existsByUsername(request.getUsername())) {
            throw new ValidationException("Username already exists");
        }
    }
}

Merge vs Rebase Strategy

# Merge strategy (preserves branch history)
git checkout main
git merge feature/user-validation
# Creates merge commit

# Rebase strategy (linear history)
git checkout feature/user-validation
git rebase main
git checkout main
git merge feature/user-validation
# Fast-forward merge, no merge commit

# Interactive rebase to clean up commits
git rebase -i HEAD~3
# Allows to squash, reorder, or edit commits

Git Tagging cho Java Releases

Semantic Versioning

# Create annotated tags for releases
git tag -a v1.0.0 -m "Release version 1.0.0 - Initial stable release"
git tag -a v1.1.0 -m "Release version 1.1.0 - Add user authentication"
git tag -a v1.1.1 -m "Release version 1.1.1 - Fix authentication bug"
git tag -a v2.0.0 -m "Release version 2.0.0 - Major API changes"

# Push tags
git push origin --tags

# List tags
git tag -l

# Checkout specific version
git checkout v1.1.0

# Delete tag
git tag -d v1.1.0
git push origin :refs/tags/v1.1.0

Maven Version Management

<!-- pom.xml -->
<version>1.1.0-SNAPSHOT</version>

<!-- Release plugin configuration -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-release-plugin</artifactId>
    <version>3.0.0-M7</version>
    <configuration>
        <tagNameFormat>v@{project.version}</tagNameFormat>
        <autoVersionSubmodules>true</autoVersionSubmodules>
    </configuration>
</plugin>
# Maven release process
mvn release:prepare
# Updates version, creates tag, commits changes

mvn release:perform
# Builds and deploys the release

Git Aliases cho Java Development

# Add useful aliases to ~/.gitconfig
[alias]
    # Quick status
    st = status

    # Pretty log
    lg = log --oneline --decorate --graph --all

    # Show files in last commit
    dl = "!git ll -1"

    # Delete merged branches
    dm = "!git branch --merged | grep -v '\\*\\|main\\|develop' | xargs -n 1 git branch -d"

    # Undo last commit (keep changes)
    undo = reset HEAD~1 --mixed

    # Show changes in last commit
    last = log -1 HEAD --stat

    # Interactive rebase
    rb = rebase -i

    # Show current branch
    cb = symbolic-ref --short HEAD

    # Java specific: show only java files in diff
    java-diff = diff '*.java'

    # Find java files that have been modified
    java-modified = "!git status --porcelain | grep '\\.java$' | cut -c4-"

Git LFS (Large File Storage)

# Initialize Git LFS
git lfs install

# Track large files (JARs, WARs, etc.)
git lfs track "*.jar"
git lfs track "*.war"
git lfs track "*.zip"
git lfs track "*.pdf"

# Add .gitattributes
git add .gitattributes
git commit -m "Add Git LFS configuration"

# Check LFS status
git lfs status

# List tracked files
git lfs ls-files

CI/CD Integration

GitHub Actions

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

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

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
      with:
        fetch-depth: 0  # Shallow clones should be disabled for better analysis

    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'

    - name: Cache Maven packages
      uses: actions/cache@v3
      with:
        path: ~/.m2
        key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
        restore-keys: ${{ runner.os }}-m2

    - name: Run tests
      run: mvn clean verify

    - name: Generate test report
      uses: dorny/test-reporter@v1
      if: success() || failure()
      with:
        name: Maven Tests
        path: target/surefire-reports/*.xml
        reporter: java-junit

    - name: SonarCloud Scan
      uses: SonarSource/sonarcloud-github-action@master
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

GitLab CI

# .gitlab-ci.yml
stages:
  - validate
  - test
  - build
  - deploy

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"

cache:
  paths:
    - .m2/repository/

validate:
  stage: validate
  image: maven:3.8.6-openjdk-17
  script:
    - mvn validate compile

test:
  stage: test
  image: maven:3.8.6-openjdk-17
  script:
    - mvn test
  artifacts:
    reports:
      junit:
        - target/surefire-reports/TEST-*.xml
    paths:
      - target/

build:
  stage: build
  image: maven:3.8.6-openjdk-17
  script:
    - mvn package -DskipTests
  artifacts:
    paths:
      - target/*.jar
    expire_in: 1 week

deploy:
  stage: deploy
  image: maven:3.8.6-openjdk-17
  script:
    - mvn deploy -DskipTests
  only:
    - main

Best Practices

Commit Best Practices

  1. Atomic Commits: Mỗi commit chỉ chứa một logical change
  2. Clear Messages: Viết commit message rõ ràng và có ý nghĩa
  3. Test Before Commit: Đảm bảo code compile và test pass
  4. Review Changes: Sử dụng git diff trước khi commit

Branch Management

  1. Meaningful Names: Sử dụng tên branch có ý nghĩa (feature/user-auth, bugfix/null-pointer)
  2. Short-lived Branches: Giữ feature branches ngắn gọn
  3. Regular Updates: Thường xuyên rebase/merge từ main branch
  4. Clean History: Sử dụng interactive rebase để clean up commits

Security Considerations

  1. Never Commit Secrets: Không commit passwords, API keys, certificates
  2. Use .gitignore: Properly configure gitignore for Java projects
  3. Signed Commits: Sử dụng GPG signing cho important commits
  4. Branch Protection: Bật branch protection rules trên main branches

Git là công cụ fundamental cho Java development, việc hiểu và sử dụng Git effectively sẽ significantly improve development workflow và team collaboration.