6 điểm bởi GN⁺ 2024-05-27 | 1 bình luận | Chia sẻ qua WhatsApp
  • 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, HEAD và 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 $FILE
    • git 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 $NAME
    • git switch $NAME
  • Tạo nhánh:
    • git checkout -b $NAME
    • git 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 --stat
    • git 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 BRANCHNAME
    • git reset --hard $COMMIT_ID
  • Gộp 5 commit cuối thành một:
    • git rebase -i HEAD^^^^^^
    • Đổi pick thành fixup đố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 $FILE
    • git 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 main
    • git 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 banana
    • git rebase main
  • Gộp các nhánh đã tách bằng merge:
    • git switch main
    • git merge banana
    • git commit
  • Gộp các nhánh đã tách bằng squash merge:
    • git switch main
    • git merge --squash banana
    • git commit

Kho remote và đồng bộ

  • Thêm kho remote:
    • git remote add $NAME $URL
  • Push nhánh main lên remote origin:
    • git push origin main
  • Đưa nhánh push lần đầu lên origin và 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 main
    • git 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

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

 
GN⁺ 2024-05-27
Ý kiến Hacker News
  • Có vài lệnh như viên ngọc ẩn: git diff --staged cho 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ông
    git 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ên
    Khi 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ọn e. 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ộc

    • Giờ mới biết git diff --staged là bí danh của git diff --cached. Tôi đã dùng cached suốt mấy năm nay, nhưng đúng là staged trự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ông
    • Tạo nhiều bí danh cho các lệnh như vậy giúp tiết kiệm khá nhiều thời gian, đồng thời hạ thấp rào cản để tổ chức công việc gọn gàng hơn
      Ai quan tâm thì có thể xem hoặc đề xuất thêm bí danh ở đây: https://gitlab.com/Falimonda/gitrc
    • Nếu đặt git config --global commit.verbose true thì Git sẽ tự động chèn diff đã staging vào phần chú thích trong trình soạn thảo message commit
  • Một trong những lệnh tôi thích dù không có trong danh sách là git log -LstartLine,endLine:fileName
    Nó giống như một phiên bản git blame mở 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ển
    Nó 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à blame luôn khiến tôi thấy khá kỳ, và hóa ra không chỉ mình tôi nghĩ vậy
    What 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/

    • Nghĩ đến việc tên công cụ là git thì blame lại có vẻ là một cái tên hợp với thương hiệu
    • Đồng ý. Tôi đã đổi blame thành who trong 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ôn w
  • Tô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 terminal
    Tô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

    • Tôi chỉ biết khoảng clone, pull, commit, push về 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ơn
    • Tôi không muốn quay lại các lệnh terminal. Repository và workflow Git được hiểu rõ hơn nhiều khi được trực quan hóa, nên Git thực chất gần như là một công cụ đòi hỏi phải dùng GUI
      Khoả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 đã chuyển từ Sourcetree sang Fork. Nó rất giống nhau, nhưng hiệu năng tốt hơn nhiều và UI mượt hơn. Trừ khi vài năm gần đây Sourcetree đã cải thiện
    • Tôi cũng đi theo con đường tương tự, nhưng dùng GitKraken và Sublime Merge; Sublime Merge vẫn được giữ lại vì nó giúp cả những tác vụ phức tạp cũng trở nên dễ dàng
      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 đó

    • Đúng vậy. Vì Git là công cụ do Linus Torvalds tạo ra để phục vụ và thúc đẩy việc phát triển nhân Linux
      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 Request cũ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
    • Một phần của sự phức tạp là vì ở các phiên bản đầu, chi phí để thêm subcommand mới khá cao. Kết quả là các subcommand sẵn có như git checkout cứ 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 switch
    • Trong phần lớn workflow, tôi nghĩ chỉ cần biết vài lệnh Git như log, add, commit, pull, push là ổn. Cũng có cả công cụ GUI
      Thà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
    • Git không phức tạp một cách không cần thiết. Chính workflow mà Git được tạo ra để hỗ trợ vốn đã đặc biệt phức tạp, và nhóm người dùng mục tiêu là các lập trình viên kernel rất thiên về kỹ thuật nên độ phức tạp đó không phải vấn đề lớ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
    • Thiết kế UI tốt là một kỹ năng riêng và cần thêm công sức, nên đa phần người ta cứ thế bỏ qua
  • Trước giờ tôi không biết git add -p, nhưng nó thực sự hữu ích
    -p, --patch cho 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

    • Nếu có thể, tôi tự chọn các hunk của phần việc hiện tại để ghép thành các commit nhất quán. Cách này giúp tôi sắp xếp lại trong đầu những gì mình đã làm, và khi cần giải thích cho ai đó, tôi có thể lần theo dòng commit để trình bày
  • Điều tôi muốn bổ sung ở đây là tổ hợp git commit --fixup $COMMIT_IDgit rebase -i upstream/master --autosquash
    Nó đã 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ùng HEAD~5. Nhưng giờ tôi mới biết rằng có thể đơn giản rebase tất cả commit cho tới HEAD của upstream

    • Tôi nhất định sẽ đưa git commit --fixup $COMMIT_ID và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
    • Tôi cũng chỉ mới học được điều này gần đây, và nó cực kỳ hay khi làm việc với các patch xếp chồng trên Gerrit
  • “Gộp 5 commit cuối thành một: git rebase -i HEAD^^^^^^” thì có lẽ phải là git rebase -i HEAD~5 mới đúng
    Vớ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

    • Jujutsu đã xóa bỏ hoàn toàn nỗi sợ đó
      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

    • Tôi khó mà đồng ý với chuyện không có index. Có thể với một số quy trình làm việc thì như vậy đơn giản hơn, nhưng dù tốt hay xấu thì tôi đã quá quen với index của Git, nên cách làm việc của tôi có lẽ sẽ thay đổi khá nhiều
      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