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 main và push 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:
-
Dừng quá trình
merge: Git sẽ tạm dừng việc hợp nhất. Nó sẽ không tạomerge commit. -
Đá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 đangmergevào (nhánhHEAD, 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ắngmerge(ví dụ:feature/an-description).
-
-
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
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:
-
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". -
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à>>>>>>>. -
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.
-
-
Đá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". -
Hoàn tất việc
merge: Khi bạn đãaddtất cả các file đã giải quyết, hãy chạy lệnhcommitđể hoàn tất quá trìnhmergemà Git đã tạm dừng trước đó.Bash
git commitGit sẽ tự động tạo một thông điệp
commitmặ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
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ặcgit fetchvàgit merge) thường xuyên từ nhánh chính vào nhánhfeaturecủ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ộtconflictkhổng lồ vào cuối cùng. -
Giữ các nhánh nhỏ và ngắn hạn: Các nhánh
featuretồn tại càng lâu, chúng càng khác biệt so với nhánhmain, và nguy cơ xảy raconflictcàng cao. Hãy cố gắng chia nhỏ công việc vàmergethườ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.
-
Chuẩn bị:
-
Đảm bảo bạn đang ở nhánh
mainvà đãpullmọ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.và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.vàgit commit -m "feat: Describe the programmer's beard"
-
-
Bình
mergetrước:Bash
git switch main git merge feature/binh-dragonViệc này sẽ thành công (fast-forward).
-
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àomain.Bash
git switch main git merge feature/an-description -
Xung đột xảy ra! Bạn sẽ thấy thông báo
CONFLICT. Đừng hoảng sợ! -
Giải quyết:
-
Chạy
git status. Nó sẽ báoboth 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.Và 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 commitLư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 reset và revert.
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
commitquan trọng, nhưng ngay sau đó nhận ra mình đã quên thêm một file CSS nhỏ vàocommitđó. Anh không muốn tạo mộtcommitmớ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
commitcuối cùng. -
Tình huống 3 (Commit sai lầm trên nhánh cá nhân): Anh đã tạo ra 3
committrên nhánhfeaturecủ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ể 3commitđó 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 đã
mergemộtcommitgây ra lỗi nghiêm trọng vào nhánhmainvà đãpushnó lên GitHub. Các thành viên khác có thể đãpullphiê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
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:
-
Thực hiện các thay đổi bạn muốn thêm (ví dụ:
git add file_da_quen.css). -
Chạy lệnh:
Bash
git commit --amend --no-edit-
--amend: Báo cho Git biết bạn muốn sửacommittrước đó. -
--no-edit: Giữ nguyên thông điệp củacommitcũ. 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"
2. git restore và git 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ủacommitcuố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 cleanlà 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ểnHEADvềcommitđược chỉ định. Các thay đổi từ nhữngcommitbị "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ềucommitnhỏ thành một. -
git reset --mixed <commit-hash>(mặc định): Di chuyểnHEADvề. 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áccommitgần đây. -
git reset --hard <commit-hash>: Cực kỳ nguy hiểm! Di chuyểnHEADvề 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 saucommitđó. 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:
-
Sửa file
story.txt, thêm một dòng bất kỳ. -
Commit thay đổi:
git commit -am "feat: Add new line"(cờ-alà lối tắt choaddcác file đã được theo dõi vàcommit). -
Nhận ra bạn quên thêm file. Tạo một file mới
notes.txt. -
Đưa file mới vào Staging Area:
git add notes.txt. -
Sửa
committrước đó:git commit --amend --no-edit. -
Dùng
git logđể xem. Bạn sẽ thấy chỉ có mộtcommitnhưng nó đã bao gồm cả hai thay đổi.
Thực hành reset (trên nhánh riêng):
-
Tạo một nhánh mới:
git switch -c experiment. -
Tạo 3
commitliên tiếp trên nhánh này. (Ví dụ: thêm 3 dòng vàostory.txt, mỗi dòng mộtcommit). -
Dùng
git logđể xem lịch sử và sao chép mã hash củacommitđầu tiên trong 3commitbạn vừa tạo. -
Giả sử bạn muốn quay lại trước khi tạo 3
commitnày. Lấy mã hash củacommitngay trước đó (hoặc dùngHEAD~3để lùi về 3commit). -
Chạy lệnh:
git reset HEAD~3. (Đây là chế độ--mixedmặc định). -
Chạy
git log. 3commitmới đã biến mất khỏi lịch sử. -
Chạy
git status. Bạn sẽ thấy các thay đổi từ 3commitđó vẫn còn trong Working Directory.
Thực hành revert (giả lập trên nhánh chung):
-
Chuyển về
main:git switch main. -
Tạo một
commit"lỗi":Bash
echo "DONG CODE GAY LOI" >> story.txt git commit -am "feat: Add a buggy feature" -
Dùng
git logđể lấy mã hash củacommit"lỗi" này. -
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> -
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.
-
Dùng
git logđể xem. Bạn sẽ thấycommitgốc vẫn còn đó, và có thêm mộtcommitmới với thông điệp "Revert 'feat: Add a buggy feature'". Mở filestory.txtra, 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" merge và rebase để đư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