Khi dự án legacy “mắc kẹt” với SVN
Nhận nhiệm vụ maintain một project chạy từ năm 2016, codebase đang nằm trên SVN server của công ty cũ — đó là tình huống mình gặp cách đây không lâu. Toàn bộ lịch sử 7 năm commit, hàng chục branches, mọi thứ đều ở định dạng SVN. Team mới muốn chuyển sang Git để tích hợp CI/CD, nhưng không ai muốn vứt đi lịch sử commit quý giá đó.
Sau khi thử vài phương án, mình chọn git-svn — bridge tool có sẵn trong Git, không cần cài thêm gì phức tạp. Nó không export code rồi import lại từ đầu mà chuyển đổi thực sự, revision từng revision, giữ nguyên từng commit, tác giả, branch structure.
git-svn hoạt động như thế nào?
git-svn có từ Git 1.5.x, đọc từng SVN revision và tạo Git commit tương ứng. Cụ thể, nó xử lý ba việc:
- Map SVN revision number (r1, r2, r3…) thành Git commit SHA
- Chuyển SVN username thành Git author format (name + email)
- Convert cấu trúc thư mục SVN (
trunk/branches/tags) sang Git branches và tags
Sự khác biệt cấu trúc giữa SVN và Git
SVN dùng revision number tuyến tính và lưu branches như thư mục thật trên server:
svn-repo/
├── trunk/ ← tương đương main branch
├── branches/
│ ├── feature-login/
│ └── hotfix-payment/
└── tags/
├── v1.0/
└── v2.1/
Git thì khác: branches và tags chỉ là con trỏ, không phải thư mục thật. git-svn tự động map trunk thành main, branches/* thành Git branches, tags/* thành Git tags. Sau khi clone, bạn cần thêm một bước convert để chúng trở thành local branches và tags thực sự thay vì remote tracking refs.
Thực hành: Migrate SVN sang Git từng bước
Demo dưới đây dùng SVN repo tại https://svn.example.com/myproject với cấu trúc trunk/branches/tags chuẩn.
Bước 1: Cài đặt git-svn
Trên nhiều distro, git-svn không đi kèm mặc định với Git:
# Ubuntu/Debian
sudo apt-get install git-svn
# CentOS/RHEL
sudo yum install git-svn
# macOS (Homebrew)
brew install git-svn
# Xác nhận đã cài
git svn --version
Bước 2: Tạo file mapping authors
SVN chỉ lưu username (ví dụ: john_dev), còn Git cần cả tên đầy đủ và email. Đầu tiên, lấy danh sách toàn bộ SVN authors:
svn log https://svn.example.com/myproject --xml --quiet \
| grep "<author>" \
| sort -u \
| sed 's/.*<author>\(.*\)<\/author>.*/\1/' > /tmp/svn-authors-raw.txt
cat /tmp/svn-authors-raw.txt
Từ danh sách đó, tạo file authors.txt:
john_dev = John Nguyen <[email protected]>
mary_tran = Mary Tran <[email protected]>
admin = Admin Bot <[email protected]>
Format mỗi dòng: svn_username = Full Name <email>. Đừng bỏ sót author nào — git-svn sẽ dừng ngay tại revision đầu tiên gặp username thiếu và báo lỗi, không tiếp tục được.
Bước 3: Clone SVN repo bằng git-svn
Đây là bước ngốn thời gian nhất. Repo 1.000 revisions mất khoảng 10–20 phút; 10.000+ revisions có thể chạy cả buổi tuỳ tốc độ mạng và server. Nên bật screen hoặc tmux trước:
screen -S svn-migration
git svn clone https://svn.example.com/myproject \
--stdlayout \
--authors-file=authors.txt \
--no-metadata \
myproject-git
# Ctrl+A, D để detach screen nếu cần
Các flag quan trọng:
--stdlayout: Tự nhận diện cấu trúc trunk/branches/tags chuẩn (viết tắt là-s)--authors-file: File mapping đã tạo ở Bước 2--no-metadata: Không thêm chuỗigit-svn-id:vào cuối mỗi commit message — commit history sẽ gọn hơn
SVN repo không dùng cấu trúc chuẩn? Bỏ --stdlayout và chỉ định thủ công:
git svn clone https://svn.example.com/myproject \
--trunk=code \
--branches=feature-branches \
--tags=releases \
--authors-file=authors.txt \
myproject-git
Bước 4: Convert branches và tags
Sau khi clone xong, branches và tags SVN xuất hiện dưới dạng remote tracking refs (refs/remotes/*), không phải Git local branches hay tags thực sự. Cần convert:
cd myproject-git
# Convert SVN tags thành Git tags thực sự
git for-each-ref refs/remotes/tags | cut -d / -f 4- | while read tagname; do
git tag "$tagname" "refs/remotes/tags/$tagname"
git branch -r -d "tags/$tagname"
done
# Convert SVN branches thành Git local branches
git for-each-ref refs/remotes | grep -v '@' | grep -v 'tags' | cut -d / -f 3- | while read branchname; do
git branch "$branchname" "refs/remotes/$branchname"
git branch -r -d "$branchname"
done
# Đổi tên trunk thành main
git branch -m trunk main
Kiểm tra kết quả:
git branch -a # Xem tất cả local branches
git tag -l # Xem tất cả tags
git log --oneline -15 # Kiểm tra lịch sử commit
Bước 5: Push lên remote Git repository
Tạo repo trống trên GitHub, GitLab hoặc Gitea, sau đó push toàn bộ lên:
git remote add origin https://github.com/yourorg/myproject.git
# Push tất cả branches
git push origin --all
# Push tất cả tags
git push origin --tags
Một điểm cần nhắc ở bước này: đừng dùng --force. Mình từng mất code quan trọng vì force push nhầm branch vào repo đang có người khác làm việc — từ đó luôn cẩn thận với git push --force. Repo remote ban đầu còn trống, không có lý do gì phải dùng force cả. Gặp lỗi thì tìm nguyên nhân thật sự thay vì dùng --force để qua mặt.
Những vấn đề hay gặp trong thực tế
Author bị thiếu trong file mapping
Clone dừng với thông báo:
Author: old_contractor not defined in authors.txt file
Thêm dòng thiếu vào authors.txt rồi resume — không cần clone lại từ đầu:
echo "old_contractor = Old Contractor <[email protected]>" >> authors.txt
cd myproject-git
git svn fetch
Repo có quá nhiều revision
10.000+ revisions mà lịch sử xa không cần thiết? Giới hạn từ một mốc cụ thể:
# Chỉ lấy từ revision 8000 đến HEAD
git svn clone https://svn.example.com/myproject \
-r 8000:HEAD \
--stdlayout \
--authors-file=authors.txt \
myproject-git
Xác minh kết quả sau migration
Sau khi xong, so sánh số lượng commit để đảm bảo không bị miss:
# Đếm revisions trong SVN (chạy từ máy có svn client)
svn log https://svn.example.com/myproject | grep -c "^r[0-9]"
# Đếm commits trong Git repo vừa migrate
cd myproject-git
git log --all --oneline | wc -l
Hai con số thường không khớp hoàn toàn — SVN có merge revisions và property-only changes không tạo commit riêng trong Git. Chênh lệch dưới 10% là bình thường; chênh lệch lớn hơn 30% thì nên kiểm tra lại quá trình convert.
Sau migration: Những việc cần làm ngay
Code đã lên Git rồi, nhưng đó mới là phần dễ. Việc quan trọng hơn là đảm bảo cả team chuyển hẳn — không ai vẫn commit nhầm lên SVN cũ:
- Thông báo toàn team, gửi link Git repo mới và hướng dẫn clone về
- Đặt SVN repo ở chế độ read-only (hoặc lock hoàn toàn) để tránh ai đó vẫn commit nhầm lên SVN
- Cập nhật CI/CD pipeline từ SVN checkout sang Git clone
- Kiểm tra
.svndirectory không lọt vào repo Git (thêm vào.gitignorenếu cần)
Tuần đầu sẽ có người hỏi “clone về thế nào”, “switch branch kiểu gì” — bình thường, chuẩn bị sẵn một doc ngắn về Git workflow cơ bản là đủ. Qua giai đoạn đó, thứ team thường nhận ra đầu tiên là branching trong Git không còn “đắt” như SVN — tạo branch mới chỉ tốn vài millisecond thay vì copy cả thư mục trên server.

