6 điểm bởi GN⁺ 2023-11-03 | 1 bình luận | Chia sẻ qua WhatsApp
  • Git có nhiều thuật ngữ bị trùng lặp và tái sử dụng dễ gây nhầm lẫn ngay cả khi đã dùng lâu, nên chỉ học thuộc lệnh thôi thì khó dự đoán hành vi thực tế
  • Cùng một từ hoặc ký hiệu như HEAD, heads, detached HEAD, ours/theirs, ../... có thể đổi nghĩa tùy theo ngữ cảnh
  • index, staged, cached đều liên quan đến .git/index, nhưng reset, revert, restore dù tên nghe giống nhau lại có tác động khác nhau đến lịch sử commit, working tree và con trỏ branch
  • Việc main track remote và remote-tracking branch như origin/main là hai chuyện riêng, còn checkout gộp cả chuyển branch lẫn khôi phục file vào cùng một lệnh
  • Để hiểu Git, cần nhìn vào mô hình nội bộ nơi branch, reference, commit, index và trạng thái theo dõi remote liên kết với nhau, thay vì chỉ nhìn từng lệnh riêng lẻ

HEAD, branch, detached HEAD

  • Trong Git, heads chỉ các branch; về mặt nội bộ, branch được lưu trong thư mục .git/refs/heads
    • Bảng thuật ngữ chính thức của Git phân biệt branch là toàn bộ các commit phía trên nó, còn head là commit mới nhất, nhưng có thể xem đây là hai cách nhìn cùng một đối tượng
  • HEAD trỏ đến branch hiện tại và được lưu trong .git/HEAD
    • Cách diễn đạt “head là branch, còn HEAD là branch hiện tại” tự nó đã cho thấy sự rối rắm trong thuật ngữ Git
  • detached HEAD state là trạng thái trong đó HEAD không trỏ đến branch mà trỏ trực tiếp đến một commit ID
    • Khi checkout một tag, vì tag không phải branch nên có thể rơi vào trạng thái detached HEAD
    • Ở trạng thái này, git pull không hoạt động, và git push cũng không hoạt động trừ khi dùng cách đặc biệt
    • Có thể dùng git commit, git merge, git rebase, git cherry-pick, nhưng các commit mới có thể không được nối với branch nào nên sau này khó tìm lại
    • Có thể thoát khỏi trạng thái detached HEAD bằng cách tạo branch mới hoặc chuyển sang branch hiện có

ourstheirs thay đổi giữa merge và rebase

  • Khi giải quyết conflict, git checkout --ours file.txt chọn file phía “ours”, nhưng điểm quy chiếu của merge và rebase lại khác nhau
  • Trong merge, branch hiện tại là ours, branch được merge vào là theirs
    • Ví dụ: sau git checkout merge-into-ours, chạy git merge from-theirs thì branch hiện tại trở thành ours
  • Trong rebase, branch hiện tại là theirs, branch đích của rebase là ours
    • Ví dụ: sau git checkout theirs, chạy git rebase ours thì branch đích trở thành ours
  • Khác biệt này liên quan đến cách git rebase main bên trong lặp lại việc merge các commit của branch hiện tại vào bản sao của branch main
  • ourstheirs giải thích ngắn gọn các thuật ngữ ourstheirs
    • “current change” và “incoming change” trong VSCode cũng có thể gây nhầm lẫn theo cách tương tự

Thông báo trạng thái remote và fast-forward

  • Your branch is up to date with 'origin/main' không có nghĩa là main hiện tại đang mới nhất so với remote
    • Nó có nghĩa là branch local giống với origin/main tại thời điểm git fetch hoặc git pull gần nhất
    • Nếu lần fetch gần nhất là 5 ngày trước, thì nó chỉ “mới nhất theo mốc 5 ngày trước”
  • Git lưu thông tin thời điểm fetch gần đây trong reflog, nhưng thông báo trạng thái không hiển thị thời gian “theo lần fetch gần nhất”
  • can be fast-forwarded có nghĩa là branch remote đang đi trước branch local, local không có commit riêng, nên có thể chỉ cần di chuyển con trỏ về phía trước
    • Ví dụ: nếu mainA-B-C, còn origin/mainA-B-C-D-E, thì sau git pull cả hai đều thành A-B-C-D-E
    • Trong trường hợp này không có khả năng xảy ra merge conflict
  • Khi không thể fast-forward, nghĩa là hai branch đã tách nhánh
    • Ví dụ: nếu main có commit X không có trong origin/main, còn origin/mainD, E không có trong main, thì không thể fast-forward
    • Khi đó Git hiển thị rằng hai branch đang ở trạng thái diverged và mỗi bên có số commit khác nhau

Cú pháp chọn commit: ^, ~, .., ...

  • HEAD^HEAD~ đều trỏ đến commit liền trước 1 bước
    • HEAD^^^, HEAD~~~, HEAD~3 đều trỏ đến commit trước đó 3 bước
  • Với commit có nhiều parent như merge commit, ý nghĩa của HEAD^2, HEAD^3 sẽ khác
    • HEAD^ là parent thứ nhất
    • HEAD^2 là parent thứ hai
    • HEAD^3 là parent thứ ba
    • HEAD~3 nghĩa là parent của parent của parent, nên khác với HEAD^3
  • git log main..testgit log main...test cũng hoạt động khác nhau
    • main..test hiển thị các commit chỉ có trong test
    • test..main hiển thị các commit chỉ có trong main
    • main...test hiển thị các commit chỉ có ở một trong hai phía
  • Trong git diff, ..... được diễn giải khác với git log
    • git diff test..main so sánh bao gồm cả thay đổi phía test lẫn thay đổi phía main
    • git diff test...main so sánh tổ tiên chung với đầu mút phía main, chỉ hiển thị diff của một phía
  • Sự rối rắm của ký hiệu dấu chấm cũng được bàn thêm trong pain in dots

reference, refspec, tree-ish

  • Trong Git, reference được dùng với nhiều nghĩa
    • Branch và tag như main, v0.2
    • HEAD, thứ trỏ đến branch hiện tại
    • Biểu thức được diễn giải thành commit ID như HEAD^^^
  • Tài liệu Git gọi các biểu thức như HEAD^^^ một cách chính xác là “revision parameters”, nhưng trong sử dụng hằng ngày chúng có thể được đối xử như reference
  • symbolic reference là reference trỏ đến một reference khác, như HEAD
    • HEAD là khái niệm trung tâm có ảnh hưởng lớn đến hành vi của các lệnh cốt lõi trong Git
  • Cấu hình remote trong .git/config chứa refspec
    • Ví dụ: +refs/heads/main:refs/remotes/origin/main
    • Nhiều trường hợp chỉ dùng nguyên giá trị mà git clone hoặc git remote add mặc định tạo ra
  • tree-ish chỉ những đối tượng có thể làm THING trong git checkout THING .
    • Commit ID
    • Reference đến commit như main, HEAD^^, v0.3.2
    • Thư mục con bên trong commit như main:./docs
    • Trong thực tế, nhiều khi có thể hiểu đơn giản là “commit hoặc reference đến commit”

index, staged, cached

  • index, staged, cached đều liên quan đến .git/index
    • .git/index là file được dùng khi stage thay đổi bằng git add
  • Nhiều cách gọi cùng một đối tượng nằm rải rác trong nhiều lệnh
    • git diff --cached
    • git rm --cached
    • git diff --staged
    • .git/index
  • Tuy nhiên các flag --index--cached không phải lúc nào cũng có cùng nghĩa
    • Bài viết của Junio Hamano, maintainer chính của Git, giải thích khác biệt chi tiết
  • Vì lý do hiệu năng, index cũng liệt kê cả untracked files, nhưng thông thường staging area không được xem là bao gồm untracked files

reset, revert, restore

  • reset, revert, restore là những từ nghe giống nhau nhưng trong Git chúng tác động đến các đối tượng khác nhau
  • git revert COMMIT tạo một commit mới trên branch hiện tại để thực hiện thao tác ngược lại với COMMIT
    • Nếu COMMIT đã thêm 3 dòng, commit mới sẽ xóa 3 dòng đó
  • git reset --hard COMMIT cưỡng bức đưa branch hiện tại quay về thời điểm COMMIT
    • Đây là thao tác rất nguy hiểm vì xóa các thay đổi mới sau COMMIT
  • git restore --source=COMMIT PATH đưa các file trong PATH về trạng thái tại thời điểm COMMIT
    • Nó không thay đổi file khác hay lịch sử commit
  • Khi dùng riêng, git reset --hardgit restore . thường hoạt động khá giống nhau, nhưng git reset --hard COMMITgit restore --source COMMIT . hoạt động hoàn toàn khác nhau

track, remote-tracking branch, untracked files

  • Git dùng từ track theo ba cách
    • Untracked files: trong git status chỉ các file Git không quản lý và không được đưa vào commit
    • Remote-tracking branch như origin/main là reference local chứa commit ID mà main trên remote origin trỏ tới tại thời điểm git pull hoặc git fetch gần nhất
    • Một branch local có thể được cấu hình để track một branch remote, như trong “branch foo set up to track remote branch bar from origin”
  • Nói main là branch track remote và nói origin/main là remote-tracking branch là hai chuyện khác nhau
  • main là branch thật nên có thể tạo commit và merge trên đó
    • Nếu được cấu hình trong .git/config để track main trên remote, có thể dùng git pullgit push thuận tiện hơn
  • origin/main dù có tên là “remote-tracking branch” nhưng không phải branch thông thường
    • Không thể commit trực tiếp lên đó
    • Nó chỉ được cập nhật khi lấy trạng thái main mới nhất từ remote bằng git pull hoặc git fetch

checkout, reflog, merge/rebase/cherry-pick

  • git checkout gộp hai thao tác khác nhau vào cùng một lệnh
    • git checkout BRANCH chuyển branch
    • git checkout file.txt bỏ các thay đổi unstaged của file.txt
  • Để giảm nhầm lẫn này, Git đã tách chức năng thành git switchgit restore, nhưng checkout cũ vẫn có thể dùng
  • reflogreference log, tức bản ghi một reference từng trỏ đến đâu trong quá khứ
    • Reference là thuật ngữ bao quát bao gồm branch, tag và HEAD
    • Reflog có thể giúp thoát khỏi những tình huống Git rất tệ, chẳng hạn lỡ xóa một branch quan trọng
  • merge, rebase, cherry-pick khác nhau ở cách hợp nhất commit
    • merge tạo một commit mới để hợp nhất hai branch
    • rebase sao chép từng commit của branch hiện tại lên trên branch đích
    • cherry-pick giống rebase nhưng cú pháp khác, và sao chép commit vào branch hiện tại
  • git rebase --onto main otherbranch mybranch có thể được dùng khi chỉ muốn chuyển một phần commit của mybranch lên trên main
    • Trong cấu trúc ví dụ, có thể dùng khi muốn rebase chỉ F, G lên trên main
    • Đây là cú pháp khó nhớ vì có tới ba tên branch

commit và các thuật ngữ dễ nhầm lẫn khác

  • commit trong Git được dùng vừa là động từ vừa là danh từ
    • Động từ: “hãy commit thường xuyên”
    • Danh từ: commit mới nhất của main
  • Có thể nhìn Git commit theo ba cách
    • Snapshot trạng thái hiện tại của toàn bộ file
    • Diff so với commit cha
    • History của tất cả commit trước đó
  • Mỗi lệnh xử lý commit theo cách khác nhau
    • git show xem commit như diff
    • git log xem commit như history
    • git restore xem commit như snapshot
  • blob, tree, origin, upstream, downstream, quan hệ giữa fetchpull, stash, worktree, subtree, submodule cũng nằm trong các thuật ngữ Git dễ gây nhầm lẫn
  • Trong thuật ngữ GitHub, “pull request”, “squash and merge”, “rebase and merge” được xem là những ví dụ gây nhầm lẫn

Ba trục nhầm lẫn lặp đi lặp lại

  • Những thuật ngữ Git đặc biệt gây nhầm lẫn có thể rút gọn thành ba điểm
    • head là branch, còn HEAD là branch hiện tại
    • “remote tracking branch” và “branch that tracks a remote” là hai thứ khác nhau
    • index, staged, cached đều chỉ cùng một đối tượng
  • Bài viết năm 2012 the most confusing git terminology cũng bàn về cách thuật ngữ Git liên hệ với thuật ngữ CVS và Subversion
  • Sự rối rắm trong thuật ngữ Git không chỉ nằm ở vài tính năng đặc biệt mà lặp lại trên toàn bộ các chức năng cốt lõi
  • Đặc biệt khi nói về branch, “tracking” được dùng theo nhiều cách nên lúc mới nhìn rất khó nhận ra khác biệt
  • Một số giải thích có nhắc đến các ngóc ngách ít gặp của Git, nên vẫn có lưu ý rằng có thể có sai sót

1 bình luận

 
GN⁺ 2023-11-03
Ý kiến trên Hacker News
  • Trước đây tôi thấy hầu hết các thuật ngữ này rất khó nhớ, nhưng điều mấu chốt khiến mọi thứ khớp lại là: rất nhiều thứ trong Git rốt cuộc đều là con trỏ trỏ tới commit
    Tên branch, HEAD, tag đều là con trỏ. HEAD trỏ tới commit bạn đang xem hiện tại, còn tên branch như my-feature trỏ tới commit mới nhất của branch đó. Nếu đang ở main và chạy git checkout -b my-feature, thì main, my-feature, HEAD đều sẽ trỏ tới commit mới nhất của main
    Mỗi lần commit trên my-branch, HEADmy-branch sẽ di chuyển để trỏ tới commit mới. detached HEAD nghĩa là không có branch nào trỏ tới commit mà HEAD đang trỏ tới. Khác biệt giữa tag và branch là tag trỏ tới một commit cụ thể và không di chuyển
    Một điều khác từng khiến tôi bối rối là lý do nhiều lệnh trông có vẻ không nhất quán: Git giả định các đối số mặc định. git checkout file.txt tương đương git checkout HEAD -- file.txt, còn khi đang ở my-branch, git rebase main tương đương git rebase main my-branch. Tuy nhiên dạng sau có thể chạy từ một branch khác
    Cuối cùng, khi mọi thứ rối tung, lệnh giúp bạn thoát khỏi bất kỳ trạng thái kỳ lạ nào là git reflog. Nó hiển thị tất cả các commit mà HEAD từng trỏ tới. Dù vậy, thật đáng mừng khi Git thừa nhận sự rối rắm này và đang tạo ra các lệnh có giao diện ít bất ngờ hơn, đơn giản hơn
    • Nếu dùng đúng tên mà Git đặt cho khái niệm này, “con trỏ commit” là refs. Vì thế mới có tên reflog
      Một điểm mà nhiều người dùng Git không hay nghĩ tới là các ref của branch hoặc tag từ remote đã fetch về cũng đều là ref, nên ở bất cứ nơi nào có thể dùng tên ref thì đều có thể tham chiếu tới chúng
      Ví dụ nếu muốn nói “tôi không quan tâm branch này đang có gì, đừng fast-forward, đừng merge, đừng rebase, cứ ghi đè branch local bằng nội dung remote”, bạn có thể làm như sau: git checkout foo, git reset --hard origin/foo
    • Chính xác hơn thì con trỏ HEAD không trực tiếp trỏ tới commit; thông thường nó trỏ tới một con trỏ branch như my-branch, rồi con trỏ branch đó mới trỏ tới commit. Trạng thái detached HEAD là ngoại lệ
      Khi commit trên my-branch, không phải cả HEADmy-branch đều di chuyển sang commit mới; HEAD vẫn tiếp tục trỏ tới con trỏ my-branch, còn chỉ my-branch di chuyển để trỏ tới commit mới. Tất nhiên nếu đi theo HEAD qua my-branch tới commit, bạn sẽ tới commit mới
      detached HEAD là trạng thái HEAD trỏ trực tiếp tới commit thay vì con trỏ branch. Ngay cả trong trạng thái detached HEAD, cả HEAD và con trỏ branch vẫn có thể cùng trỏ tới commit mới nhất. Khi xem git log --decorate, thay vì kiểu thông thường (HEAD -> my-branch), ở commit mới nhất thường sẽ hiện như (HEAD, my-branch)
    • Tôi thích cách giải thích này. Nhưng phần đôi khi còn gây rối hơn là tag có hai loại. Hầu hết tag là lightweight tag như đã nói: chúng trỏ tới đối tượng commit, chỉ vậy thôi
      Ngược lại, annotated tag có thể chứa message, timestamp, SHA, v.v. Chúng là các đối tượng Git đúng nghĩa và hoạt động khá giống đối tượng commit, nhưng thường vẫn tham chiếu tới một đối tượng Git khác, đa số là commit
    • Nếu bạn là người thận trọng quá mức như tôi, trước khi bắt đầu một quy trình khó có nhiều conflict hoặc nhiều bước di chuyển, cũng nên chạy git rev-parse HEAD và sao chép chuỗi đó vào nơi an toàn
      Nó tạo cảm giác yên tâm rằng bạn đã đánh dấu chính xác trạng thái cuối cùng mà mình chắc chắn là ổn. Thường thì bạn có thể quay lại trạng thái tốt cuối cùng, chứ không phải một điểm nào đó sau vài bước đi vào hỗn loạn. Tất nhiên với điều kiện là bạn không bất cẩn dùng những thứ như git gc hay git filter-branch
    • Phần giải thích này cũng cho thấy rõ rằng cấu trúc cây bên trong Git chi phối mọi thứ
      Nghe như đùa nhưng không phải đùa: nói rằng muốn dùng Git thì thật sự cần bằng khoa học máy tính cũng không hẳn sai. Tôi đồng ý rằng các đối số mặc định của Git tạo ra trải nghiệm sử dụng rất thiếu nhất quán
      git checkout ., git reset --hard/soft, git cleanup đều trông cực kỳ giống nhau nhưng thực tế lại rất khác. Dù vậy tôi vẫn thích Git hơn Perforce hay SVN. Trước đây tôi từng thích Mercurial hơn một chút, nhưng không biết bây giờ còn vậy không
      git reflog là vua và cho thấy sự thật
  • Đã khoảng 10 năm kể từ khi Git “chiến thắng” trong cuộc chiến quản lý phiên bản nhờ thêm một làn sóng công nghệ vô căn cứ nữa, vậy mà đến giờ nó vẫn khiến người ta chật vật với những việc lẽ ra phải rất nhỏ nhặt
    Tôi nhớ đã đọc đâu đó rằng Linus Torvalds ngạc nhiên khi mọi người bắt đầu dùng Git trực tiếp thay vì đặt một lớp thân thiện hơn lên trên. Nếu ngành này có khả năng tự nhìn lại, có lẽ chúng ta đã thừa nhận việc chọn Git là sai lầm và chuyển sang hệ thống quản lý phiên bản khác. Đừng lãng phí vô số thời gian vào một công cụ mà về cơ bản chỉ là lưu trữ văn bản
    • Tôi không xem Git là công cụ lưu trữ văn bản. Nó là công cụ điều phối khá chính xác và ổn định các thay đổi mà nhiều người cùng thực hiện trên cùng một “văn bản”, trong khi không chặn tiến độ của bất kỳ ai. Thử làm bằng Word sẽ thấy khác biệt rất lớn
      Tất nhiên nó có thể tốt hơn nữa. Nhưng sự ghét bỏ dành cho Git luôn làm tôi ngạc nhiên. Git là một công cụ tuyệt vời, và đi trước rất xa so với các công cụ dành cho người không phải lập trình viên như Word hay Google Docs
      Có lẽ vì tôi đủ lớn tuổi để nhớ thời Subversion còn là vua
    • Rất đồng ý
      Hằng tuần tôi thấy đồng nghiệp bối rối vì Git, thấy các bài cầu cứu trên mạng, rồi lại thấy những bài cố gắng giải thích lại những thứ lẽ ra phải đơn giản như bài này
      Trong thời gian viết code, tôi không nhớ có ai lãng phí hàng giờ để đảo ngược mớ hỗn độn do chính họ tạo ra trong SVN, TFVC, Perforce, v.v.
      Công cụ tồn tại để làm cuộc sống dễ hơn. Nếu không làm được điều đó thì không đáng để bỏ thời gian
    • Ngay cả vào năm 2013, việc Git sẽ thắng hoàn toàn không rõ ràng. Vẫn có khá nhiều người dùng darcs, Mercurial, svn, thậm chí cvs, và các công cụ thời đó thường có plugin hỗ trợ tất cả những hệ thống này

Mình vẫn ước là còn có sự đa dạng như vậy. Độc canh Git cũng có nghĩa là bất cứ thứ gì muốn thay thế Git trước hết phải triển khai lại Git. Vì thế chúng ta gần như bị buộc chặt vào Git mãi mãi, giống như ASCII hay phím Scroll Lock

  • Git thắng là có lý do chính đáng, và rõ ràng tốt hơn những thứ trước đó. Giờ có thể đang là mốt chê Git như chê jQuery, nhưng vào thời điểm xuất hiện thì đó là một cải tiến rõ ràng
  • Gần đây tôi đã viết một bài về một vấn đề tương tự: vì sao chúng ta có thể bị mắc kẹt với Git
    Nỗi lo cốt lõi là GitHub quá tốt, nên trừ khi GitHub tách Git khỏi engine quản lý ký tự và cho phép dùng các engine khác, chúng ta sẽ bị buộc vào Git
    https://trolololo.xyz/github
    https://news.ycombinator.com/item?id=38098109
  • Thuật ngữ Git giống như một ca lâm sàng: nhiều thuật ngữ chỉ có vẻ hợp lý sau khi bạn đã hiểu cách chúng hoạt động, nhưng nếu không biết chi tiết triển khai hoặc đặt cạnh các thuật ngữ khác thì gần như chẳng có nghĩa gì
    Chúng là những thuật ngữ kiểu abstraction bị rò rỉ
    • Điều này đúng một phần, nhưng cũng vì các phần khác nhau của Git được những người khác nhau làm ra và đặt tên riêng. Linus ban đầu gọi nó là cache, đây là thuật ngữ mang tính khoa học máy tính nhất
      Sau đó có vẻ Junio đổi nó thành index, chuyên biệt hơn cho hệ thống quản lý phiên bản phân tán, còn đa số người dùng gọi nó là staging area. Giờ quá trình tiến hóa thuật ngữ này đã hóa thạch trong UI và nội bộ của Git
    • Không phải là nó hợp lý, mà chỉ là đặt tên cẩu thả. Đặt tên hay cần nỗ lực, và các nhà phát triển Git thời kỳ đầu đã không làm điều đó
    • Nếu tưởng tượng rằng để dùng cơ bản mà phải biết nội bộ hệ điều hành, IDE, compiler, thì thực chất đó là thiết kế API tệ
  • “Your branch is up to date with origin/master” là lời nói dối lớn nhất tôi nghe hằng ngày. Vì vậy lần nào tôi cũng cứ pull
    • Nói nghiêm ngặt thì nó đúng. origin/master không phải là master trên origin, mà là commit mà origin/master được biết lần cuối đang trỏ tới. Giá trị này được cập nhật khi fetch, và pull tự động thực hiện fetch
      Nhìn ls -l .git/refs/remotes/origin/master sẽ thấy origin/master chỉ là một file trên hệ thống của bạn. Bạn cũng có thể xem nó thay đổi lúc nào. Nó không tự cập nhật như phép thuật
      Khi chạy git fetch origin, nếu có thay đổi thì timestamp và nội dung sẽ đổi. Có thể kiểm tra bằng cat .git/refs/remotes/origin/master
      Nền tảng của Git rất đơn giản, đến mức các cấu trúc dữ liệu cốt lõi và vài thao tác có thể được triển khai trong một ngày. Biết điều này thực sự rất đáng giá. Không hiểu bằng cách nào mà Git đã tạo ra một giao diện người dùng cực kỳ phức tạp trên một lõi khá đơn giản
    • Thông báo đó chỉ có nghĩa là Git sẽ không truy cập Internet hay remote repository để kiểm tra cập nhật nếu bạn không chỉ thị rõ ràng. Bản thân tính chất này tôi cho là tốt. Chỉ là thông báo có thể rõ ràng hơn
    • Lẽ ra nên là kiểu “Your branch is up to date with the local copy of origin/master, last updated on XXXX-XX-XX:XX:XX:XX”
    • Tôi xóa luôn branch master cục bộ. Thường thì không thể push lên master, hoặc dù sao tôi cũng không muốn làm việc trực tiếp trên master. Vậy thì chẳng có lý do gì để giữ một branch master cục bộ có thể thay đổi
      Vì thế chỉ cần cập nhật origin/master, và fetch là đủ
    • git fetch && git pull
      Đang chờ 3 năm nữa nghỉ hưu để rửa tay khỏi đống rác hỏng hóc này
  • Tôi đã dùng Git gần như từ những ngày đầu, vậy mà mãi đến tuần này mới biết porcelain. Tôi có một dự án parse output của git status, và thêm flag --porcelain vào thì thật sự hữu ích. Nó tạo ra output gọn hơn và dễ parse bằng chương trình hơn
    Tôi bắt đầu tò mò không biết có bao nhiêu lệnh có output dễ đọc hơn cho máy, rồi cuối cùng đọc đến trang “Git Internals - Plumbing and Porcelain” của git-scm. Tóm lại, ngay từ đầu Git được viết giống một bộ công cụ để xử lý quản lý phiên bản hơn là một hệ thống quản lý phiên bản bóng bẩy
    Nhiều người dùng Git từ sớm đã học các tác vụ quản lý phiên bản bằng các lệnh mức thấp này, rồi truyền bá workflow đó cho người khác. Đây là tầng plumbing. Về sau Git tạo ra một tầng được trau chuốt hơn, gọi là porcelain
    Tôi vẫn chưa hoàn toàn biết lệnh nào thuộc tầng nào, nhưng điều này đã giúp tôi hiểu các workflow mới được khuyến nghị trong vài năm gần đây. Nó cũng giúp tôi phán đoán tốt hơn khi thay đổi workflow của mình
    https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Po...
    • Đây là tín hiệu rõ ràng rằng Linus hài lòng với cấu trúc nội bộ, tức plumbing, nhưng cho rằng UI, tức porcelain, cần được chỉnh sửa. Ban đầu toàn bộ porcelain là các script gọi plumbing
      Đáng tiếc là để làm nó nhanh hơn, họ đã hard-code UI đó, nhưng lại không cải thiện nó
  • Giọng văn của Julia thật sự rất hay. Cô ấy viết những nội dung rất có giá trị ngay cả với developer kỳ cựu, đồng thời vẫn giữ giọng điệu thân thiện và không loại trừ những người mới bước vào lĩnh vực này
  • Có vẻ tôi mắc “hội chứng Stockholm Git”. Những thuật ngữ này không khiến tôi bối rối đến vậy, nhưng có lẽ chủ yếu là vì tôi gần như không học hệ thống nào khác và đã gắn bó sâu với Git từ khoảng năm 2011
    Nhìn lại thì lúc mới bắt đầu chắc hẳn tôi đã cực kỳ bối rối, nhưng có lẽ đã giả vờ là không để trông ngầu hơn
    • Sau khi xem CLI của hệ thống quản lý phiên bản Fossil, tôi mới nhận ra mình có hội chứng Stockholm. Nó quá trực quan
    • Trước khi dùng Git tôi đã dùng cvs và svn; so với svn thì Git dễ hiểu hơn nhiều
    • Hệ thống quản lý phiên bản khác duy nhất tôi từng dùng là Plastic SCM, và dù có hội chứng Stockholm hay không thì Git vẫn tốt hơn nhiều
  • Những thứ đáng xem cùng: Git staging area đúng nghĩa là thuật ngữ được tất cả mọi người đồng ý

https://news.ycombinator.com/item?id=28143078
“Your branch is up to date with ‘origin/main’” thực ra hơi dễ gây hiểu lầm. Có thể hiểu là nhánh main đang mới nhất, nhưng thực tế không phải vậy
Tuy nhiên cũng không cần phải quá “nói cho thật chính xác” ở đây. Chẳng phải bài toán hai vị tướng áp dụng trong trường hợp này sao? Nếu soi thật kỹ thì không thể biết ngay đúng khoảnh khắc này có “mới nhất” hay không. Kể cả có fetch trước status thì cũng vậy
Vậy kỳ vọng hợp lý là gì? Đại khái là ref trong cơ sở dữ liệu object trên máy mình, ở lần kiểm tra cuối cùng, giống với ref trong một cơ sở dữ liệu object khác ở tận đâu đó
Một lệnh gọi status đơn giản không thể thực hiện fetch qua mạng, hay lần nào cũng nhắc lại cho bạn nguyên lý cơ bản của công cụ đang dùng. Cá nhân tôi nghĩ vậy. Nhưng đáng ngạc nhiên là rất nhiều bình luận trong câu trả lời trên StackOverflow lại cho rằng cần đúng hai điều đó
Cách diễn đạt như “is up to date with the origin’s main as of your last fetch 5 days ago” thì hợp lý hơn một chút. Vì nó chỉ cho biết lần fetch cuối cùng cách đây bao lâu
[1] https://stackoverflow.com/questions/27828404/why-does-git-st...
Một điều nữa: tôi từng nghĩ ORIG_HEAD có liên quan đến “head của origin” như FETCH_HEAD. Nhưng không phải. “pseudoref” này có vẻ được dùng làm điểm lưu trước các thao tác ghi lại lịch sử phức tạp hơn như rebase, và được triển khai trước khi có reflog. Có lẽ nó có nghĩa là “original head”

  • Tôi khá có thiện cảm với Git, nhưng về câu chữ của thông báo đó thì tôi đồng ý với tác giả. Tuy nhiên tôi không đồng ý với phương án thay thế được đề xuất
    “Your branch is up to date with ‘origin/main’” về mặt kỹ thuật là đúng, nhưng cụm up to date nếu đọc lướt thì gợi ra hai điều: main giống origin/main, và origin/main cũng đang mới nhất, tức là có cảm giác nó đã được cập nhật trong lúc chạy lệnh hoặc được tự động giữ ở trạng thái mới nhất và lần đồng bộ gần đây đã thành công
    Đây không phải là tín hiệu máy đọc cần đúng trong một chu kỳ CPU, mà là thông báo trạng thái hiển thị cho người dùng. Không cần lôi lý thuyết mạng vào thì cách hiểu như vậy vẫn hợp lý
    “Up to date” gần với nghĩa đã bắt kịp theo thời gian hơn là theo không gian. Vị trí của nhánh cục bộ gần với “nhánh này đang trỏ tới đâu”, tức không gian; còn trạng thái ref từ xa gần với “lần cuối tôi cập nhật ref từ xa là khi nào”, tức thời gian. Tất nhiên điều này rất chủ quan và cả hai đều có thể mang cả hai nghĩa
    Câu chữ tốt hơn có thể là “Your branch matches origin/main”, “Your branch’s head is the same as origin/main”, “Your branch is pointing to the same commit as origin/main”. Đó là sự đánh đổi giữa rõ ràng và dài dòng. Cũng có thể thêm trong ngoặc “remote ref last updated 5 days ago” như đề xuất của tác giả
  • Tôi không đồng ý rằng việc lệnh gọi status thực hiện fetch qua mạng là điều khó chịu. Tôi cho rằng tác vụ mạng phải là tường minh, không nên là ngầm định. Khi chạy git status, tôi muốn biết trạng thái kho lưu trữ của mình trên hệ thống tệp
    Nếu muốn biết chuyện gì đang xảy ra ở remote khác, tôi có thể tự fetch rồi so sánh
  • Điều tôi kỳ vọng là nhận được thông tin mới nhất tính tại thời điểm bắt đầu chạy lệnh status. Có thể có race condition trong lúc thông tin được in ra terminal, nhưng đó là một khoảng thời gian rất nhỏ
    Nhìn chung điều này có nghĩa là muốn có fetch. Bài toán hai vị tướng chỉ áp dụng cho phía remote. Nếu remote không nhận được xác nhận rằng client đã nhận dữ liệu, nó có thể tiếp tục truyền lại một thời gian. Đó không phải vấn đề của client
    Nếu client không nhận được dữ liệu từ server, hành vi đúng có lẽ là đứng chờ rồi sau khi timeout thì in lỗi; hiện tại fetch có lẽ cũng làm như vậy
  • Bước giá trị nhất khi học Git là có được mô hình tư duy đúng. Tất cả đều là đồ thị
    Công cụ này thường được chia sẻ, và ở đây cũng rất phù hợp. Nó trực quan hóa mô hình nội bộ của Git và tác động của nhiều lệnh
    https://learngitbranching.js.org/
    Tôi học được nhiều hơn trong 10 phút dùng công cụ này so với khoảng 10 năm kinh nghiệm Git trước đó. Rất khuyến nghị
  • Không muốn nói thêm nhiều, nhưng việc người của Bitbucket tạo ra thuật ngữ pull request cũng là một vấn đề
    “pull” là thao tác hợp nhất thay đổi từ remote vào kho lưu trữ cục bộ. Thứ người dùng thực sự yêu cầu là server hãy hợp nhất thay đổi từ remote của họ vào một nhánh nào đó. “merge request” của GitLab mới là cách gọi đúng
    Tôi từng thấy có người fetch xong thì thấy commit biến mất. Đó là vì kho lưu trữ đã đi lùi theo thời gian nên chúng bị ẩn đi. Git giấu các chi tiết như vậy trong status; nếu không dùng git-prompt hay powerline-shell thì chẳng khác nào làm việc trong bóng tối
    • Ý tưởng ban đầu còn sót lại trong git-request-pull(1) là mỗi chúng ta sẽ có một kho Git riêng ở những nơi như kernel.org/git hoặc redhat.com/git, rồi gửi email yêu cầu nhau pull từ các kho nằm trên những server, tổ chức, domain khác nhau
      GitHub lấy cảm hứng từ lệnh request-pull của Git, nhưng diễn giải lại thành việc hợp nhất từ một kho GitHub này sang một kho GitHub khác
    • Nhìn từ góc độ server, người dùng là remote còn server là local. Vì vậy người dùng đang yêu cầu server pull từ phía của mình
      Ít nhất tôi luôn đọc theo cách đó
    • Sách Git giải thích nguồn gốc thuật ngữ này và vì sao nó có lý
      Tóm lại, đó không phải là yêu cầu gửi tới server, mà là yêu cầu gửi tới một người khác. Bạn yêu cầu người đó pull nhánh của mình để xem xét các thay đổi mà bạn muốn đóng góp cho dự án

[0] https://git-scm.com/book/en/v2/Distributed-Git-Contributing-...

  • “pull request” được GitHub phổ biến rộng rãi, và tôi nghĩ đây là một thuật ngữ ổn. Việc thực tế là pull từ repository khác hay thực hiện “merge request” trong cùng một repository thì cũng không thú vị lắm. “merge request” có cảm giác như thuật ngữ riêng của GitLab
  • Đây luôn là điều khó hiểu nhất. Rõ ràng đáng lẽ phải là push request hoặc merge request chứ không phải pull request mới đúng