
Git được viết bởi ông Linus Torvalds, cũng là người đã viết ra phiên bản đầu tiên của nhân hệ điều hành Linux. Linus nổi tiếng là người nóng tính. Có lần ngay trên sóng truyền hình, không biết vì lý do gì ông nhìn thẳng vào camera, giơ ngón giữa lên và nói: “Nvidia, Fuck You!”. Hay có lần khác, trong lúc review code ông comment rằng: “This piece of code is pure and utter shit. It makes me go to bed wanting to kill myself”, mời bạn đọc tự dịch.
Git ra đời năm 2005, vậy trước đó người ta dùng gì?
Phần mềm đầu tiên dùng để quản lý code được ghi nhận tên là Source Code Control System (SCCS). Phần mềm này được phát triển ở Bell Labs cuối năm 1972 để phục vụ cho quá trình phát triển hệ điều hành Unix.
Những phần mềm tiếp theo ra đời để cải tiến những nhược điểm của SCCS có thể kể đến như Revision Control System (1982), Concurrent Versions System (1986).
Tiếp đến là một cái tên quen mặt hơn đó là Subversion (SVN), được phát triển vào năm 2000, và hiện giờ thì đang được quản lý bởi Apache Foundation.
SVN cũng huy hoàng được khoảng 5 năm trước khi git của Linus Torvalds ra đời. Và kể từ ấy trở đi không thấy có phần mềm nào nổi lên để thay thế git nữa.
Git có nhiều tính năng, nhưng nếu mà nói sử dụng thường xuyên chắc chỉ loanh quanh cỡ 5 câu lệnh:
# khởi tạo
git init
# chọn files để commit
git add <file>
# commit
git commit -m "<commit message>"
# đẩy code đi đâu đó
git push origin <branch>
# lấy code ở đâu đó về
git pull origin <branch>
Có người làm biếng đặt cả alias, để mỗi khi cần push, pull, commit, hay checkout thì chỉ cần gõ vài chữ là xong.
alias gck='git checkout'
alias gpo='git push origin HEAD'
alias gpl='git pull --rebase'
alias gs='git status'
alias gbr='git branch'
Dĩ nhiên sẽ có những vẫn đề phức tạp hơn mà mấy câu lệnh phổ biến bên trên không giải quyết được. Một trong số đó là nhu cầu về việc dọn dẹp commit.
Giả sử có hai nhánh: main và new-feature. Nhánh new-feature được tạo ra từ nhánh main. Trong nhánh new-feature có 3 commits theo thứ tự như sau:
Init new feature code
Add some configs
Finish new feature
Giờ muốn merge nhánh new-feature vào nhánh main thì dễ rồi.
$ git checkout main
$ git merge new-feature
$ git log
6ba01da Finish new feature
f67273e Add some configs
dc1f2dd Init new feature code
12783dd Finish MVP
c6fc165 Init commit
Sau khi merge xong thì nhánh main giờ sẽ có đủ cả 5 commits, 2 có từ trước, và 3 được merge từ new-feature.
Có một nhu cầu ở đoạn merge này, đấy là làm sao để gom tất cả các commit trong nhánh new-feature thành 1 commit duy nhất trong nhánh main. Ở nhánh new-feature có thể có rất nhiều commit do lập trình viên thử nghiệm đủ thứ, sửa đủ thứ. Khi merge lại vào nhánh main, thì nhìn lịch sử commit sẽ rất hỗn độn.
Trong trường hợp này người ta dùng một tính năng gọi là squash, tức sẽ gom nhiều commits lại thành một commit duy nhất.
$ git checkout main
$ git merge --squash new-feature
$ git commit -m "Add new feature"
$ git log
3d33fd3 Add new feature
12783dd Finish MVP
c6fc165 Init commit
Lúc này mấy file bên nhánh new-feature sẽ được “git add” vào nhánh main, và sau git add, dĩ nhiên là “git commit” những file đó lại, kết thúc quá trình squash. Sau quá trình này, nhánh main chỉ còn 3 commits, trông gọn hơn nhiều.
Tuy nhiên dùng squash đồng nghĩa với việc xoá toàn bộ lịch sử commit ra khỏi nhánh main. Điều này đôi khi lại không được khuyến khích. Bây giờ giả sử bên nhánh new-commit, ta muốn bỏ đi một số commit, nhưng muốn giữ lại một số commit thì phải làm sao?
Câu trả lời là dùng “git rebase”. Nếu dịch từng từ thì có thể hiểu như sau: “re” nghĩa là làm gì đó lại, “base” là gốc, “rebase” là “làm lại gốc”, tức có thể hiểu là chỉnh lại các commits.
$ git checkout new-feature
$ git rebase -i <commit_hash>
$ git rebase --continue
Để bắt đầu rebase, thì ta cần chọn một cái “base” để có thể “re”, tức là chọn một cái commit trong quá khứ để thực hiện lại việc tinh chỉnh commit. Sau khi chạy lệnh “rebase -i”, danh sách các commit sẽ được hiện ra với các lựa chọn mà ta có thể làm với một commit cụ thể. Các lựa chọn đó rất đa dạng, từ việc đơn giản như sửa lỗi typo của một commit message (reword), đến việc sửa lại code (edit), từ việc xoá đi một commit (drop), cho đến việc squash nhiều commit lại với nhau (squash), vv.
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
# commit's log message, unless -C is used, in which case keep only #. # this commit's message; -c is same as -C but opens the editor
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --
# continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# create a merge commit using the original merge commit's
# message (or the oneline, if no original merge commit was
# specified); use -c <commit> to reword the commit message
# u, update-ref <ref> = track a placeholder for the <ref> to be updated
# to this position in the new commits. The <ref> is
# updated at the end of the rebase