Cheat sheet Git [PDF]
(wizardzines.com)- Một PDF tổng hợp để bạn nhanh chóng tra cứu các thao tác thường hay vướng trong Git, gồm tạo commit, chuyển nhánh, so sánh thay đổi, đồng bộ với remote và các lệnh hủy thay đổi
- Luồng làm việc cục bộ xoay quanh
git status,git add,git commit,git diff, dùng để kiểm tra trạng thái trước và sau khi staging rồi tạo commit - Thao tác với nhánh dùng
git switch,git checkout,git branch,git merge,git rebase,git cherry-pickđể xử lý tạo·chuyển·merge·rebase - Tìm trong lịch sử và khôi phục gồm
git log,git blame,git show,git reflog,git reset,git restore; nhánh, tag,HEADvà nhánh remote cũng có thể dùng làm tham chiếu commit - Thao tác với kho remote bao gồm quy trình lấy và gửi thay đổi bằng
git remote add,git fetch,git pull,git push,git push --force-with-lease,git push --tags
Khởi tạo kho và kiểm tra trạng thái hiện tại
- Khởi tạo kho mới:
git init
- Clone kho có sẵn:
git clone $URL
- Kiểm tra vị trí và trạng thái hiện tại:
git status
Chuẩn bị và tạo commit
- Thêm file chưa được theo dõi:
git add $FILE
- Thêm tất cả file chưa được theo dõi và các thay đổi chưa staging:
git add .
- Chọn staging chỉ một phần thay đổi của file:
git add -p
- Xóa hoặc di chuyển file:
git rm $FILEgit mv $OLD $NEW
- Loại file khỏi theo dõi của Git mà không xóa file:
git rm --cached $FILE
- Hủy staging toàn bộ:
git reset HEAD
- Tạo commit:
git commit- Mở trình soạn thảo văn bản để viết message
- Tạo commit và chỉ định message ngay:
git commit -m 'message'
- Tạo commit kèm message, bao gồm cả các thay đổi chưa staging:
git commit -am 'message'
Chuyển và quản lý nhánh
- Chuyển nhánh:
git checkout $NAMEgit switch $NAME
- Tạo nhánh:
git checkout -b $NAMEgit switch -c $NAME
- Xem danh sách nhánh:
git branch
- Sắp xếp nhánh theo commit gần đây:
git branch --sort=-committerdate
- Xóa nhánh:
git branch -d $NAME
- Xóa nhánh cưỡng bức:
git branch -D $NAME
Tìm trong lịch sử và truy vết code
- Xem lịch sử nhánh:
git log main
- Xem quan hệ giữa hai nhánh bằng biểu đồ:
git log --graph a b
- Xem log một dòng:
git log --oneline
- Kiểm tra ai là người sửa cuối cùng từng dòng trong file:
git blame $FILENAME
- Xem tất cả commit đã chỉnh sửa file:
git log $FILENAME
- Tìm commit đã thêm hoặc xóa một đoạn text cụ thể:
git log -S banana
So sánh thay đổi
- Xem khác biệt giữa commit và commit cha:
git show $COMMIT_ID
- Xem khác biệt giữa merge commit và các commit cha đã được merge:
git show --remerge-diff $COMMIT_ID
- Xem tóm tắt diff:
git diff $COMMIT_ID --statgit show $COMMIT_ID --stat
- So sánh hai commit:
git diff $COMMIT_ID $COMMIT_ID
- Chỉ so sánh một file cụ thể với commit:
git diff $COMMIT_ID $FILENAME
- So sánh toàn bộ thay đổi đã staging và chưa staging:
git diff HEAD
- Chỉ so sánh các thay đổi đã staging:
git diff --staged
- Chỉ so sánh các thay đổi chưa staging:
git diff
File cấu hình và tùy chọn
- File cấu hình Git cục bộ:
.git/config
- File cấu hình Git toàn cục:
~/.gitconfig
- Danh sách file cần bỏ qua:
.gitignore
- Chỉ định tùy chọn cấu hình:
git config user.name 'Julia'
- Chỉ định tùy chọn toàn cục:
git config --global ...
- Thêm alias:
git config alias.st status
- Kiểm tra các tùy chọn cấu hình có thể dùng:
man git-config
Sửa lịch sử và khôi phục file
- Hoàn tác commit gần nhất, giữ nguyên thư mục làm việc:
git reset HEAD^
- Hoàn tác rebase thất bại:
git reflog BRANCHNAMEgit reset --hard $COMMIT_ID
- Gộp 5 commit cuối thành một:
git rebase -i HEAD^^^^^^- Đổi
pickthànhfixupđối với các commit cần gộp
- Lấy phiên bản file từ nhánh hoặc commit khác:
git checkout $COMMIT_ID $FILEgit restore $FILE --source $COMMIT_ID
- Đổi commit message hoặc thêm file bị bỏ sót:
git commit --amend
Gộp nhánh
- Fast-forward merge để cập nhật nhánh hiện tại bằng nhánh khác:
git switch maingit merge banana
- Sao chép một commit sang nhánh khác:
git cherry-pick $COMMIT_ID
- Gộp các nhánh đã tách bằng rebase:
git switch bananagit rebase main
- Gộp các nhánh đã tách bằng merge:
git switch maingit merge bananagit commit
- Gộp các nhánh đã tách bằng squash merge:
git switch maingit merge --squash bananagit commit
Kho remote và đồng bộ
- Thêm kho remote:
git remote add $NAME $URL
- Push nhánh
mainlên remoteorigin:git push origin main
- Đưa nhánh push lần đầu lên
originvà thiết lập nhánh theo dõi:git push -u origin $NAME
- Push nhánh hiện tại lên nhánh theo dõi remote:
git push
- Force push:
git push --force-with-lease
- Push tag:
git push --tags
- Lấy thay đổi về, không thay đổi nhánh cục bộ:
git fetch origin main
- Fetch tất cả nhánh:
git fetch --all
- Sau khi fetch, rebase vào nhánh hiện tại:
git pull --rebase
- Sau khi fetch, merge vào nhánh hiện tại:
git pull origin maingit pull
Cách trỏ tới commit
- Có thể dùng nhiều tham chiếu ở vị trí
$COMMIT_ID:- Nhánh:
main - Tag:
v0.1 - ID commit:
3e887ab - Nhánh remote:
origin/main - Commit hiện tại:
HEAD - Commit cách 3 commit trước:
HEAD^^^ - Commit cách 3 commit trước:
HEAD~3
- Nhánh:
Loại bỏ thay đổi
- Xóa cả thay đổi đã staging và chưa staging của một file:
git checkout HEAD $FILE
- Xóa thay đổi chưa staging của một file:
git checkout $FILE
- Xóa tất cả thay đổi đã staging và chưa staging:
git reset --hard
- Stash các thay đổi đã staging và chưa staging:
git stash
- Xóa file chưa được theo dõi:
git clean
1 bình luận
Ý kiến Hacker News
Có vài lệnh như viên ngọc ẩn:
git diff --stagedcho thấy sự khác biệt giữa commit cuối cùng và nội dung hiện đang staging, nên rất hữu ích để kiểm tra xem khi add file hoặc hunk một cách chi tiết thì có đúng là chỉ những phần định commit mới được đưa vào hay khônggit log <file>chỉ hiển thị các commit đã thay đổi file đó, nên đỡ phải đào bới toàn bộgit log, đặc biệt hữu ích với những file không thay đổi thường xuyênKhi dùng
git add --patch, nếu muốn chọn chi tiết hơn cả các hunk mà Git đã chia sẵn thì nên thử tùy chọne. Nó sẽ hướng dẫn cách bao gồm/loại trừ từng dòng riêng lẻ, nên không cần phải học thuộcgit diff --stagedlà bí danh củagit diff --cached. Tôi đã dùngcachedsuốt mấy năm nay, nhưng đúng làstagedtrực quan hơn nhiều. Chỉ là không biết có xóa nổi trí nhớ cơ bắp này khôngAi quan tâm thì có thể xem hoặc đề xuất thêm bí danh ở đây: https://gitlab.com/Falimonda/gitrc
git config --global commit.verbose truethì Git sẽ tự động chèn diff đã staging vào phần chú thích trong trình soạn thảo message commitMột trong những lệnh tôi thích dù không có trong danh sách là
git log -LstartLine,endLine:fileNameNó giống như một phiên bản
git blamemở rộng, cho thấy các dòng được chỉ định đã thay đổi theo thời gian ra sao và ai đã thay đổi chúng. Nó vẫn theo dõi được ngay cả khi số dòng thay đổi hoặc file bị di chuyểnNó trả lời được những câu hỏi như “Ai đã làm thế này, khi nào, và vì sao?” hoặc “Từ khi nào nó thành ra thế này?”
Tôi chợt nhớ đến câu nói của Phil Karlton: “Trong khoa học máy tính chỉ có hai việc khó: vô hiệu hóa cache và đặt tên.”
Theo góc nhìn đó, việc tên lệnh Git là
blameluôn khiến tôi thấy khá kỳ, và hóa ra không chỉ mình tôi nghĩ vậyWhat does 'git blame' do? https://stackoverflow.com/questions/31203001/what-does-git-blame-do
Blame someone else for your bad code https://news.ycombinator.com/item?id=27963868
Git blame should be called git credit https://dev.to/damcosset/git-blame-should-be-called-git-credit-27h5
Does Git Blame sound too negative? https://www.reddit.com/r/ProgrammerHumor/comments/r5lzyo/does_git_blame_sound_too_negative_just_look_up/
gitthìblamelại có vẻ là một cái tên hợp với thương hiệublamethànhwhotrong cấu hình của mình. Thật ra vì là người dùng Vim hardcore nên tôi cứ dùng luônwTôi thích cách mình đã học Git. Ban đầu tôi bắt đầu với Sourcetree, một Git GUI của bên thứ ba, và nó cực kỳ dễ dùng, dễ hiểu
Sau đó tôi chuyển sang
git gui, nhưng có thể bỏ qua bước này. Rồi đến lúc thấy việc bật UI lên quá phiền nên tôi học các lệnh terminalTôi khuyên người mới học Git đi theo cách này, dù đây chỉ là trải nghiệm n=1 của riêng tôi
clone,pull,commit,pushvề Git, và đến giờ như vậy là đủ rồi. Git cũng có thể đơn giản đến thế. Tất nhiên nếu cần thì vẫn có thể học cách dùng phức tạp hơnKhoảnh khắc tôi bắt đầu dùng Magit là lúc Git bắt đầu trở nên dễ hiểu
Tôi chỉ có lời khen cho Sublime Merge
Có người thắc mắc liệu Git phức tạp một cách không cần thiết có phải vì lý do lịch sử hay không
Nhìn tờ cheat sheet này thì càng cảm nhận rõ sự phức tạp đó
Việc nó trở nên phổ biến đến vậy có thể là một “tai nạn may mắn”, hoặc cũng có thể là điều không may. GitHub có công rất lớn trong việc phổ cập Git bằng “dịch vụ hosting Git miễn phí”, nhưng tôi không cho rằng Git là hệ thống quản lý phiên bản lý tưởng cho nhiều dự án
Vì được tạo ra cho Linus và Linux nên thuật ngữ và quy trình làm việc đôi khi cho cảm giác ngược với các hệ thống quản lý phiên bản thông thường khác. Cái tên
Pull Requestcũng xuất phát từ việc trong quy trình phát triển Linux, Linus hoặc maintainer sẽ kéo thay đổi vào cây kernel. Ở các hệ thống khác, người ta thường gọi làMerge Request, và cách gọi đó hợp lý hơn nhiều vì về bản chất là hợp nhất thay đổi từ đâu đó vào cây mãGit gần như là hệ thống quản lý phiên bản phân tán mã nguồn mở duy nhất có thể xử lý nhanh các codebase khổng lồ, nên ở các tập đoàn lớn cũng hình thành quán tính sử dụng
Với dự án cá nhân, tôi thích và thực sự dùng Mercurial hơn nhiều, nhưng nếu thử dùng Hg với những codebase lớn như Android AOSP thì nó giật lag rất nặng. Mercurial có giao diện người dùng và hệ thống lệnh tốt hơn, thậm chí còn có tính năng bật ngay một web server để xem cây thay đổi, nhưng vì được viết bằng Python nên hiệu năng và khả năng mở rộng không thật sự lý tưởng
git checkoutcứ liên tục bị nhồi thêm chức năng, khiến trải nghiệm người dùng càng rối hơnỞ Git hiện đại, chi phí thêm subcommand mới đã giảm, và họ bắt đầu bổ sung những lệnh cụ thể hơn như
git switchlog,add,commit,pull,pushlà ổn. Cũng có cả công cụ GUIThành thật mà nói, trong công việc thông thường tôi không thấy Git phức tạp đến thế. Chỉ là nó có rất nhiều tính năng mạnh mẽ để dùng khi cần
Các lập trình viên kernel phải giao tiếp theo cách phi tập trung và tiếp tục làm việc trên phần việc của nhau. Làm sao để phối hợp trong một dự án có 15.000 người cùng viết mã? Git được tạo ra cho mục đích đó
Nó phức tạp đúng mức cần thiết để hỗ trợ workflow mà nó được thiết kế cho. Tạo một công cụ đơn giản hơn thì dễ, nhưng khi đó sẽ không thể hỗ trợ những workflow phức tạp như phát triển nhân Linux. Vì vậy, không phải là nó phức tạp một cách không cần thiết, nhưng với các nhu cầu đơn giản thì đúng là nó phức tạp hơn mức cần có
Nếu cần một frontend ít phức tạp hơn thì có rất nhiều công cụ giúp dùng Git dễ dàng hơn. Nếu không phải là lập trình viên phần mềm chuyên nghiệp, tôi không khuyến nghị dùng Git trực tiếp. Nhưng nếu là lập trình viên chuyên nghiệp, việc dành thời gian để hiểu các khái niệm bên trong là rất đáng giá, và một khi đã hiểu thì Git khá thanh nhã so với những gì nó làm được
Tôi sẽ không dùng việc biết Git hay không để đánh giá tư cách của một lập trình viên phần mềm, nhưng nếu việc tự tìm hiểu các hệ thống nền tảng như thế này hoạt động ra sao không đem lại chút hứng thú nào cho bạn, thì có thể lĩnh vực này không hợp gu của bạn. Điều đó cũng không sao, vì không phải thứ gì cũng phù hợp với tất cả mọi người
Để học cách di chuyển trong Git, tôi khuyên dùng https://learngitbranching.js.org. Tất nhiên https://xkcd.com/1597/ cũng rất đúng
Trước giờ tôi không biết
git add -p, nhưng nó thực sự hữu ích-p,--patchcho phép chọn tương tác từng hunk trong patch giữa index và working tree để thêm vào index. Nó cho bạn cơ hội xem lại khác biệt trước khi thêm thay đổi vào indexĐiều tôi muốn bổ sung ở đây là tổ hợp
git commit --fixup $COMMIT_IDvàgit rebase -i upstream/master --autosquashNó đã trở thành thành phần thiết yếu trong workflow gần đây của tôi. Ví dụ của Julia dùng
HEAD^^^^^để rebase 5 commit trước đó, còn tôi đến tận gần đây vẫn dùngHEAD~5. Nhưng giờ tôi mới biết rằng có thể đơn giản rebase tất cả commit cho tớiHEADcủa upstreamgit commit --fixup $COMMIT_IDvào dùng ngay. Tôi lúc nào cũng tạo những commit sửa vặt kiểu “oops GET users/me”, rồi trong lần interactive rebase tiếp theo lại tự tay chuyển chúng đi để fixup, nhưng cách này tốt hơn nhiều“Gộp 5 commit cuối thành một:
git rebase -i HEAD^^^^^^” thì có lẽ phải làgit rebase -i HEAD~5mới đúngVới điều kiện là bạn chỉ muốn rebase một lịch sử tuyến tính
https://stackoverflow.com/questions/2221658/whats-the-difference-between-head-and-head-in-git
Điều ngộ ra lớn nhất là khoảnh khắc hiểu rằng mọi commit vẫn còn nằm ở bên trong. Tag và branch chỉ đơn giản là trỏ tới commit, và cả những commit mà branch từng trỏ tới trước đây cũng không hề biến mất
Nhiều lệnh, có lẽ gần như tất cả các lệnh, đều không phá hủy dữ liệu, nên có thể dễ dàng tham chiếu lại trạng thái cũ của branch
Tôi từng thấy
git reflog, nhưng chủ yếu chỉ dùng nó để nhớ lại kiểu “cái branch mình từng làm trước đó là gì nhỉ?”Tôi từng có một nỗi sợ vừa phải đối với những thao tác có thể đẩy mình vào giữa một mớ giải quyết xung đột ngoài ý muốn. Thường là vì tham chiếu nhầm commit hoặc branch nên mới phát sinh xung đột
Nỗi sợ đó chưa biến mất hoàn toàn, nhưng ít nhất tôi đã thấy khá thoải mái hơn trong việc khôi phục lại công việc cũ giữa các thao tác như rebase
Nếu không biết sau một thao tác nào đó có phát sinh xung đột hay không, tôi cứ làm rồi kiểm tra. Nếu có xung đột thì giải quyết, hoặc hoàn tác thao tác đã gây ra xung đột, hoặc tạo một commit mới ở chỗ khác và để việc xử lý xung đột lại sau
Nếu biết trước sẽ có xung đột, tôi sẽ sao chép branch liên quan rồi thử căn chỉnh trước, hoặc chạy thao tác gây xung đột và đi qua quy trình giải quyết. Nếu việc giải quyết trở nên lộn xộn, tôi có thể hoàn tác một phần phần đã giải quyết rồi tiếp tục thử, hoặc tạm hoãn đó lại để làm việc khác, hoặc bỏ cả cây làm việc
Nói thẳng ra thì nên tạm gác Git sang một bên và xem JJ
https://martinvonz.github.io/jj/v0.17.1/
Có lẽ ngay cả Linus cũng sẽ nghĩ UI của Git rất tệ. JJ có quy trình làm việc rất tuyệt
Tuy vậy, ngoài việc thử nghịch qua một chút thì tôi chưa thực sự dùng nó sâu trong công việc thực tế, nên cũng khó khẳng định chắc chắn