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
- Atomic Commits: Mỗi commit chỉ chứa một logical change
- Clear Messages: Viết commit message rõ ràng và có ý nghĩa
- Test Before Commit: Đảm bảo code compile và test pass
- Review Changes: Sử dụng
git difftrước khi commit
Branch Management
- Meaningful Names: Sử dụng tên branch có ý nghĩa (feature/user-auth, bugfix/null-pointer)
- Short-lived Branches: Giữ feature branches ngắn gọn
- Regular Updates: Thường xuyên rebase/merge từ main branch
- Clean History: Sử dụng interactive rebase để clean up commits
Security Considerations
- Never Commit Secrets: Không commit passwords, API keys, certificates
- Use .gitignore: Properly configure gitignore for Java projects
- Signed Commits: Sử dụng GPG signing cho important commits
- 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.