Blog PostSeptember 22, 2025

Bí kíp luyện Git cơ bản cho người mới (P3): Cộng tác viên

Bí kíp luyện Git cơ bản cho người mới (P3): Cộng tác viên

Người bạn đồng hành trên con đường trở thành cao thủ Git

Chào mừng bạn đến với cuộc hành trình chinh phục Git. Nếu bạn đang ở đây, có lẽ bạn đã nghe nói về Git như một công cụ "thần thánh" mà mọi lập trình viên đều phải biết, hoặc có thể bạn đã từng trải qua những đêm dài mất ngủ vì lỡ tay xóa mất file quan trọng hay ghi đè lên công sức của đồng đội. Dù lý do là gì, bạn đã đến đúng nơi.

Cuốn "bí kíp" này không phải là một tài liệu tham khảo khô khan liệt kê hàng trăm câu lệnh. Thay vào đó, nó được viết dưới dạng một câu chuyện, một lộ trình tuyến tính sẽ dẫn dắt bạn đi từng bước, từ việc hiểu được "nỗi đau" mà Git sinh ra để giải quyết, cho đến việc sử dụng thành thạo những kỹ thuật phức tạp nhất. Chúng ta sẽ cùng nhau theo chân một lập trình viên tên Bình, chứng kiến những vấn đề anh gặp phải và khám phá cách Git, với triết lý và công cụ của mình, trở thành người hùng giải quyết những vấn đề đó.

Bạn đang đọc phần 3 của cuốn bí kíp này bao gồm nội dung ở chương 7 và 8. Những chương này sẽ cung cấp cho bạn kiến thức về cách ta giải quyết xung đột khi có nhiều người cùng thực hiện công việc trên một vùng dữ liệu, hoặc cách bạn giải quyết một sai lầm mà bạn đã gây ra trên kho chứa dữ liệu.

Nếu bạn đang cảm thấy lạ lẫm, hãy quay lại Phần 1 của cuốn bí kíp tại đây.


Chương VII: Khi các Thế giới Va chạm - Giải quyết Xung đột (Merge Conflicts)

Làm việc nhóm thật tuyệt vời cho đến khi... hai người cùng chỉnh sửa một dòng code. Đây là lúc "xung đột" xảy ra. Phần này sẽ giải thích tại sao merge conflict (xung đột khi hợp nhất) lại xảy ra, cách Git báo cho bạn biết, và quan trọng nhất là quy trình từng bước để giải quyết chúng một cách bình tĩnh và tự tin. Đây là một kỹ năng không thể thiếu để cộng tác hiệu quả.

Vấn đề: Hai Người, Một Dòng Code

Câu chuyện cộng tác giữa Bình và An đang diễn ra tốt đẹp.

  • Bình quyết định thêm một dòng vào cuối file story.txt để mô tả về con rồng: Con rồng thở ra lửa màu xanh biếc.

  • Cùng lúc đó, An cũng chỉnh sửa file story.txt. Cô ấy cũng thêm một dòng vào cuối file để mô tả về nhân vật chính: Chàng lập trình viên có một bộ râu quai nón rất ngầu.

Cả hai đều làm việc trên nhánh riêng của mình, sau đó commit thay đổi.

Bình là người nhanh tay hơn. Anh merge nhánh của mình vào mainpush lên GitHub. Mọi chuyện suôn sẻ.

Sau đó, An pull những thay đổi mới nhất từ main về để cập nhật nhánh của mình trước khi merge. Khi cô thực hiện lệnh merge nhánh main vào nhánh của mình (hoặc ngược lại), Terminal hiện lên một thông báo đáng sợ:

Auto-merging story.txt
CONFLICT (content): Merge conflict in story.txt
Automatic merge failed; fix conflicts and then commit the result.

Git đã dừng lại. Nó không biết phải làm gì. Ở cùng một vị trí (dòng cuối cùng của file), có hai thay đổi khác nhau. Nên giữ lại dòng của Bình? Dòng của An? Hay cả hai? Git không thể tự quyết định được. Nó cần sự can thiệp của con người. Đây chính là một merge conflict.  

Giải pháp của Git: Dừng lại, Đánh dấu và Chờ Lệnh

Khi gặp conflict, Git không hoảng loạn, và bạn cũng không nên. Thay vào đó, nó thực hiện một quy trình rất có phương pháp:

  1. Dừng quá trình merge: Git sẽ tạm dừng việc hợp nhất. Nó sẽ không tạo merge commit.  

  2. Đánh dấu file xung đột: Git sẽ không sửa file của bạn. Thay vào đó, nó sẽ mở file có xung đột ra và thêm vào đó những dấu hiệu xung đột (conflict markers) đặc biệt để cho bạn thấy chính xác vấn đề nằm ở đâu. File đó sẽ trông như thế này :  

    Ngày xửa ngày xưa, có một lập trình viên...
    Và họ sống hạnh phúc mãi mãi về sau.
    <<<<<<< HEAD
    Con rồng thở ra lửa màu xanh biếc.
    =======
    Chàng lập trình viên có một bộ râu quai nón rất ngầu.
    >>>>>>> feature/an-description
    
    • <<<<<<< HEAD: Báo hiệu bắt đầu của khối xung đột. Mọi thứ giữa dòng này và ======= là phiên bản của nhánh hiện tại bạn đang merge vào (nhánh HEAD, trong trường hợp này là main).

    • =======: Đường phân cách giữa hai phiên bản.

    • >>>>>>> <tên-nhánh>: Mọi thứ giữa ======= và dòng này là phiên bản đến từ nhánh bạn đang cố gắng merge (ví dụ: feature/an-description).

  3. Chờ bạn giải quyết: Git bây giờ sẽ chờ bạn. Nhiệm vụ của bạn là mở file đó ra, quyết định phiên bản cuối cùng nên trông như thế nào, và sau đó nói với Git rằng "Tôi đã giải quyết xong".

Cách Git hỗ trợ xử lý xung đột

Cách Git hỗ trợ xử lý xung đột

Quy trình giải quyết xung đột

Đây là quy trình 4 bước để giải quyết bất kỳ merge conflict nào:

  1. Xác định các file xung đột: Chạy lệnh git status. Git sẽ cho bạn biết chính xác những file nào đang ở trạng thái "unmerged paths".  

  2. Mở và sửa file: Mở từng file bị xung đột trong trình soạn thảo code của bạn. Tìm đến các khối được đánh dấu bằng <<<<<<<, =======, và >>>>>>>.

  3. Quyết định phiên bản cuối cùng: Đây là lúc bạn cần dùng đến kỹ năng của mình (và có thể là giao tiếp với đồng đội). Bạn có thể:

    • Giữ lại phiên bản của bạn và xóa phiên bản của họ.

    • Giữ lại phiên bản của họ và xóa phiên bản của bạn.

    • Kết hợp cả hai phiên bản.

    • Viết lại một phiên bản hoàn toàn mới. Quan trọng: Sau khi quyết định, bạn phải xóa tất cả các dòng đánh dấu xung đột (<<<<<<<, =======, >>>>>>>). File cuối cùng phải trông giống như code bình thường.

  4. Đánh dấu là đã giải quyết: Sau khi đã sửa xong file và hài lòng với kết quả, bạn cần nói cho Git biết.

    Bash

    git add <tên-file-vừa-sửa>
    

    Lệnh git add ở đây không có nghĩa là thêm file mới, mà là để đánh dấu file xung đột là "đã được giải quyết".

  5. Hoàn tất việc merge: Khi bạn đã add tất cả các file đã giải quyết, hãy chạy lệnh commit để hoàn tất quá trình merge mà Git đã tạm dừng trước đó.

    Bash

    git commit
    

    Git sẽ tự động tạo một thông điệp commit mặc định (ví dụ: "Merge branch 'feature/an-description' into main"). Bạn có thể giữ nguyên nó, lưu và đóng file lại.

Quy trình giải quyết xung đột

Quy trình giải quyết xung đột

Mẹo để giảm thiểu xung đột:

  • Giao tiếp: Nói chuyện với đồng đội về những gì bạn đang làm, đặc biệt nếu các bạn đang làm việc trên cùng một phần của ứng dụng.

  • Cập nhật thường xuyên: Chạy git pull (hoặc git fetchgit merge) thường xuyên từ nhánh chính vào nhánh feature của bạn. Điều này giúp bạn tích hợp các thay đổi nhỏ và liên tục, thay vì đối mặt với một conflict khổng lồ vào cuối cùng.  

  • Giữ các nhánh nhỏ và ngắn hạn: Các nhánh feature tồn tại càng lâu, chúng càng khác biệt so với nhánh main, và nguy cơ xảy ra conflict càng cao. Hãy cố gắng chia nhỏ công việc và merge thường xuyên.  

Thực hành: Giải quyết xung đột đầu tiên

Hãy mô phỏng lại tình huống của Bình và An.

  1. Chuẩn bị:

    • Đảm bảo bạn đang ở nhánh main và đã pull mọi thứ mới nhất.

    • Tạo và chuyển sang một nhánh cho Bình: git switch -c feature/binh-dragon

    • Sửa story.txt, thêm vào cuối dòng: Con rồng thở ra lửa màu xanh biếc.

    • Commit thay đổi: git add.git commit -m "feat: Describe the dragon's fire"

    • Quay lại main: git switch main

    • Tạo và chuyển sang một nhánh cho An: git switch -c feature/an-description

    • Sửa story.txt, thêm vào cuối dòng: Chàng lập trình viên có một bộ râu quai nón rất ngầu.

    • Commit thay đổi: git add.git commit -m "feat: Describe the programmer's beard"

  2. Bình merge trước:

    Bash

    git switch main
    git merge feature/binh-dragon
    

    Việc này sẽ thành công (fast-forward).

  3. An cố gắng merge: Bây giờ, đến lượt An. Cô ấy cần hợp nhất công việc của mình vào main.

    Bash

    git switch main
    git merge feature/an-description
    
  4. Xung đột xảy ra! Bạn sẽ thấy thông báo CONFLICT. Đừng hoảng sợ!

  5. Giải quyết:

    • Chạy git status. Nó sẽ báo both modified: story.txt.

    • Mở file story.txt. Bạn sẽ thấy các dấu hiệu xung đột.

    • Quyết định: Bình và An nói chuyện với nhau và quyết định giữ lại cả hai mô tả. Hãy sửa nội dung bên trong khối xung đột để nó trông như sau:

      Chàng lập trình viên có một bộ râu quai nón rất ngầu.
      Con rồng thở ra lửa màu xanh biếc.
      

      xóa các dòng <<<<<<< HEAD, =======, >>>>>>> feature/an-description.

    • Đánh dấu đã giải quyết:

      Bash

      git add story.txt
      
    • Hoàn tất merge:

      Bash

      git commit
      

      Lưu và đóng trình soạn thảo văn bản hiện ra.

Chúc mừng! Bạn vừa giải quyết thành công merge conflict đầu tiên của mình. Đây là một trong những kỹ năng đáng sợ nhất đối với người mới, nhưng một khi bạn hiểu quy trình, nó sẽ trở nên rất đơn giản.


Chương VIII: Cỗ máy Thời gian Nâng cao - Hoàn tác các Sai lầm

Sai lầm là một phần không thể tránh khỏi của quá trình sáng tạo. Sức mạnh của một công cụ không chỉ nằm ở việc nó giúp bạn đi về phía trước, mà còn ở việc nó cho phép bạn quay lại một cách an toàn. Phần này sẽ dạy bạn các kỹ thuật khác nhau để "undo" trong Git, từ việc sửa một commit gần nhất, loại bỏ các thay đổi cục bộ, cho đến việc hoàn tác một commit đã được chia sẻ công khai. Chúng ta sẽ tìm hiểu sự khác biệt quan trọng giữa resetrevert.

Vấn đề: "Ối, tôi đã làm sai!"

Trong quá trình làm việc, Bình gặp phải vô số tình huống "ước gì mình có thể làm lại":

  • Tình huống 1 (Quên file): Anh vừa thực hiện một commit quan trọng, nhưng ngay sau đó nhận ra mình đã quên thêm một file CSS nhỏ vào commit đó. Anh không muốn tạo một commit mới chỉ với nội dung "Thêm file CSS đã quên".

  • Tình huống 2 (Thử nghiệm thất bại): Anh đã thử nghiệm một vài ý tưởng trong Working Directory của mình, tạo ra một mớ hỗn độn. Bây giờ anh muốn vứt bỏ tất cả những thay đổi đó và quay lại trạng thái sạch sẽ của commit cuối cùng.

  • Tình huống 3 (Commit sai lầm trên nhánh cá nhân): Anh đã tạo ra 3 commit trên nhánh feature của mình, nhưng sau đó nhận ra toàn bộ hướng đi này là sai lầm. Nhánh này chưa được chia sẻ với ai cả. Anh muốn lịch sử trông như thể 3 commit đó chưa bao giờ tồn tại.

  • Tình huống 4 (Commit lỗi lên nhánh chung): Tệ nhất, anh đã merge một commit gây ra lỗi nghiêm trọng vào nhánh main và đã push nó lên GitHub. Các thành viên khác có thể đã pull phiên bản lỗi này về. Anh không thể "xóa" commit đó khỏi lịch sử chung được nữa. Anh cần một cách để "hoàn tác" nó một cách an toàn.

Những sai lầm mà lập trinh viên thường mắc phải khi làm việc nhóm

Những sai lầm mà lập trinh viên thường mắc phải khi làm việc nhóm

Giải pháp của Git: Bộ công cụ Undo

Git cung cấp một bộ công cụ mạnh mẽ để xử lý các tình huống trên, mỗi công cụ có một mục đích và mức độ "phá hủy" khác nhau.

1. git commit --amend: Sửa chữa Commit Gần nhất

Đây là giải pháp cho Tình huống 1. amend (sửa đổi) cho phép bạn thêm các thay đổi mới vào commit gần nhất, thay vì tạo một commit mới.

Cách hoạt động:

  1. Thực hiện các thay đổi bạn muốn thêm (ví dụ: git add file_da_quen.css).

  2. Chạy lệnh:

    Bash

    git commit --amend --no-edit
    
    • --amend: Báo cho Git biết bạn muốn sửa commit trước đó.

    • --no-edit: Giữ nguyên thông điệp của commit cũ. Nếu bạn bỏ cờ này, Git sẽ mở trình soạn thảo để bạn viết lại thông điệp.

Lưu ý: amend thực chất là viết lại commit cũ, tạo ra một commit mới với mã hash khác. Vì vậy, chỉ nên sử dụng nó cho các commit chưa được push lên remote.

Sửa chữa sai lầm với "git commit --amend"

Sửa chữa sai lầm với "git commit --amend"

2. git restoregit clean: Dọn dẹp Working Directory

Đây là giải pháp cho Tình huống 2.

  • git restore <tên-file>: Lệnh này sẽ loại bỏ tất cả các thay đổi bạn đã thực hiện trên một file trong Working Directory và khôi phục nó về trạng thái của commit cuối cùng.

  • git restore.: Áp dụng cho tất cả các file đã thay đổi trong thư mục hiện tại.

  • git clean -fd: Lệnh này mạnh hơn. Nó sẽ xóa tất cả các file và thư mục untracked (file mới mà bạn chưa bao giờ add).

    • -f (force): Bắt buộc xóa.

    • -d (directories): Xóa cả các thư mục. Cảnh báo: git clean là lệnh xóa vĩnh viễn. Hãy sử dụng cẩn thận.

3. git reset: Du hành Thời gian và Viết lại Lịch sử (Dùng cho Local)

Đây là giải pháp cho Tình huống 3. git reset là một lệnh cực kỳ mạnh mẽ, dùng để di chuyển con trỏ HEAD (và con trỏ của nhánh hiện tại) đến một commit cụ thể trong quá khứ, làm cho lịch sử trông như thể các commit sau đó chưa từng tồn tại.  

git reset có ba chế độ chính:

  • git reset --soft <commit-hash>: Di chuyển HEAD về commit được chỉ định. Các thay đổi từ những commit bị "loại bỏ" sẽ được giữ lại và đưa vào Staging Area. Rất hữu ích khi bạn muốn gộp nhiều commit nhỏ thành một.

  • git reset --mixed <commit-hash> (mặc định): Di chuyển HEAD về. Các thay đổi sẽ được giữ lại trong Working Directory, nhưng không nằm trong Staging Area. Hữu ích khi bạn muốn làm lại các commit gần đây.

  • git reset --hard <commit-hash>: Cực kỳ nguy hiểm! Di chuyển HEAD về và xóa sạch tất cả các thay đổi trong cả Staging Area và Working Directory. Bạn sẽ mất toàn bộ công việc đã làm sau commit đó. Chỉ sử dụng khi bạn chắc chắn 100% muốn vứt bỏ mọi thứ.  

Khi nào dùng reset? Chỉ khi bạn đang làm việc trên một nhánh cá nhân, chưa được chia sẻ. Vì reset viết lại lịch sử, nó sẽ gây ra vấn đề lớn nếu áp dụng trên nhánh chung, tương tự như rebase.

4. git revert: Hoàn tác An toàn (Dùng cho Public)

Đây là giải pháp cho Tình huống 4. git revert là cách "undo" an toàn cho các nhánh đã được chia sẻ.  

Thay vì xóa commit cũ khỏi lịch sử, git revert làm một việc thông minh hơn: nó tạo ra một commit mới có nội dung là phủ định của commit mà bạn muốn hoàn tác.

Ví dụ: Nếu commit A thêm một dòng code, thì revert của commit A sẽ tạo ra một commit B mới, và commit B này sẽ xóa chính dòng code đó.

Cách hoạt động:

Bash

git revert <commit-hash-cần-hoàn-tác>

Git sẽ tạo một commit mới và mở trình soạn thảo để bạn xác nhận thông điệp.

Ưu điểm:

  • An toàn cho lịch sử chung: Vì nó không thay đổi lịch sử cũ mà chỉ thêm vào lịch sử mới, nó sẽ không gây ra xung đột cho các thành viên khác trong nhóm.

  • Minh bạch: Lịch sử ghi lại rõ ràng rằng một commit đã được thực hiện và sau đó đã được hoàn tác, giữ lại toàn bộ bối cảnh.

Thực hành: Sửa chữa Sai lầm

Hãy tạo ra một vài sai lầm và sửa chúng.

Thực hành amend:

  1. Sửa file story.txt, thêm một dòng bất kỳ.

  2. Commit thay đổi: git commit -am "feat: Add new line" (cờ -a là lối tắt cho add các file đã được theo dõi và commit).

  3. Nhận ra bạn quên thêm file. Tạo một file mới notes.txt.

  4. Đưa file mới vào Staging Area: git add notes.txt.

  5. Sửa commit trước đó: git commit --amend --no-edit.

  6. Dùng git log để xem. Bạn sẽ thấy chỉ có một commit nhưng nó đã bao gồm cả hai thay đổi.

Thực hành reset (trên nhánh riêng):

  1. Tạo một nhánh mới: git switch -c experiment.

  2. Tạo 3 commit liên tiếp trên nhánh này. (Ví dụ: thêm 3 dòng vào story.txt, mỗi dòng một commit).

  3. Dùng git log để xem lịch sử và sao chép mã hash của commit đầu tiên trong 3 commit bạn vừa tạo.

  4. Giả sử bạn muốn quay lại trước khi tạo 3 commit này. Lấy mã hash của commit ngay trước đó (hoặc dùng HEAD~3 để lùi về 3 commit).

  5. Chạy lệnh: git reset HEAD~3. (Đây là chế độ --mixed mặc định).

  6. Chạy git log. 3 commit mới đã biến mất khỏi lịch sử.

  7. Chạy git status. Bạn sẽ thấy các thay đổi từ 3 commit đó vẫn còn trong Working Directory.

Thực hành revert (giả lập trên nhánh chung):

  1. Chuyển về main: git switch main.

  2. Tạo một commit "lỗi":

    Bash

    echo "DONG CODE GAY LOI" >> story.txt
    git commit -am "feat: Add a buggy feature"
    
  3. Dùng git log để lấy mã hash của commit "lỗi" này.

  4. Bây giờ, hãy hoàn tác nó một cách an toàn:

    Bash

    git revert <mã-hash-của-commit-lỗi>
    
  5. Git sẽ mở trình soạn thảo với một thông điệp mặc định. Lưu và đóng nó lại.

  6. Dùng git log để xem. Bạn sẽ thấy commit gốc vẫn còn đó, và có thêm một commit mới với thông điệp "Revert 'feat: Add a buggy feature'". Mở file story.txt ra, bạn sẽ thấy dòng code lỗi đã biến mất. Lịch sử được bảo toàn!


Lời kết

Thật tuyệt vời! Bạn vừa chinh phục những kỹ năng cốt lõi và cũng là thử thách nhất trong việc cộng tác: hợp nhất các nhánh, làm việc với kho chứa từ xa, và đối mặt với xung đột. Bạn đã học được hai "binh pháp" mergerebase để đưa các dòng thời gian về lại làm một. Bạn đã biết cách đưa dự án của mình lên "mây" với push và cập nhật công việc của đồng đội với pull.

Quan trọng nhất, bạn đã đối mặt với "nỗi sợ" lớn nhất của làm việc nhóm – merge conflict – và đã chiến thắng. Bạn hiểu rằng đó không phải là một thảm họa, mà chỉ là một quy trình có phương pháp mà Git yêu cầu sự can thiệp của con người. Giờ đây, bạn đã chuyển từ một người làm việc độc lập thành một cộng tác viên thực thụ, sẵn sàng tham gia vào bất kỳ dự án nào.

Nhưng khi làm việc nhóm, sai lầm có thể xảy ra và có sức ảnh hưởng lớn hơn. Lỡ push một commit lỗi lên nhánh chung thì sao? Có cách nào để tạm cất công việc đang dang dở mà không cần commit không? Trong phần cuối cùng, chúng ta sẽ mài giũa những kỹ năng của một bậc thầy, học cách du hành thời gian để sửa chữa sai lầm và trang bị những công cụ tinh xảo nhất trong kho vũ khí của Git.

End of Article

Thank you for reading!