Ở phần 1 mình nói về Git versioning, ở phần 2 này mình sẽ nói về Git branch.
Git branch giải quyết vấn đề gì?
Hãy quay về ví dụ làm slide thuyết trình cho một project nhóm.
Để giải quyết vấn đề có ai đó vô tình chỉnh sửa file chung của nhóm, mình có thể làm thế này:
- Tạo ra một bản copy của file chung đó
- Chỉnh sửa ở file copy chứ không chỉnh sửa trên file chung
- Leader sẽ review file copy này
- Chỉ khi leader đồng ý thì mới merge những thay đổi của file copy này vào file chung của nhóm
Theo cách trên, ai muốn chỉnh sửa cái gì phải tạo ra một bản copy của file chung. Với Git, điều đó sẽ là mỗi người tạo ra một branch mới.
Mình có chút thắc mắc, bạn hỏi. Giả sử chỉ có 1 người tạo một bản copy, chỉnh sửa trên đó, rồi leader upload file đó lên Google Drive thành file chung thì ok, nhưng nếu 3 người làm việc đó cùng một lúc thì giải quyết thế nào?
Chẳng lẽ leader của nhóm phải xem lần lượt từng file mỗi file thay đổi những gì, copy paste những thay đổi đó thành một file mới, rồi mới upload lên Google Drive chung của nhóm hay sao?
Ở trong lập trình, đừng lo, Git sẽ giải quyết vấn đề đó cho bạn!
Chỉ cần dùng git merge
và Git sẽ tự động merge những thay đổi cho bạn!
Giả sử nếu 2 người vô tình chỉnh sửa cùng một dòng trong cùng một file, đó sẽ là "conflict".
Túm cái váy lại từ ví dụ của mình:
- Tạo một bản copy mới = Tạo một nhánh (branch) mới
- Merge thành 1 file = Merge nhánh
- 2 người chỉnh sửa cùng một chỗ = conflict (rồi tiếp theo sẽ phải resolve conflict)
Hãy cùng học cách dùng Git để làm tất cả những việc trên!
Workflow cơ bản khi bạn nhận task mới
Ở phần này, mình sẽ giải thích workflow cơ bản khi bạn nhận một task mới như thế nào.
Mặc định, khi bạn git init
(tức là bảo Git bắt đầu quản lý version của một thư mục), bạn ở branch master
. Bạn hãy tưởng tượng nhánh master
như kiểu file chung cho cả nhóm vậy.
Nếu bạn có một task mới, bạn cần thực hiện những bước này:
- Tạo một nhánh mới (= tạo một file copy mới)
- Chỉnh sửa trên nhánh đó rồi
git add
vàgit commit -m
(= tạo một version mới) - Merge vào nhánh
master
(= merge vào file chung của nhóm)
Hãy áp dụng nó vào project mà mình đã tạo ra ở phần 1.
View, create, merge branch
Đây là project của mình ở phần 1:
Để xem mình đang ở nhánh nào, bạn có thể sử dụng lệnh sau
git branch
Đây là kết quả của mình
* master
Điều này có nghĩa mình đang ở nhánh master
.
Bây giờ tưởng tượng bạn có một task mới: thêm vào index.js
console.log("bye there")
.
Theo "đúng quy trình", đây là các bước bạn cần phải thực hiện:
- Tạo một nhánh mới (= copy ra một file mới)
- Thêm
console.log("bye there")
vàoindex.js
(thực hiện task đó) git add
rồigit commit -m
(= tạo một version mới)- Merge vào nhánh
master
(= merge vào file chung của nhóm)
Đầu tiên, hãy tạo một nhánh mới. Để tạo một nhánh mới, hãy sử dụng git checkout -b [branch-name]
.
Ví dụ mình tạo nhánh mới tên là feature/bye_there
thì sẽ dùng lệnh sau:
git checkout -b feature/bye_there
Khi mới dùng Git, bạn có thể rất dễ quên mình đang ở nhánh nào. Vì thế hãy tạo thói quen tốt mỗi lần chuyển nhánh hay làm gì, hãy kiểm tra mình ở nhánh nào:
git branch
Đây là kết quả của mình
* feature/bye_there master
Điều này có nghĩa là mình có 2 nhánh master
và feature/bye_there
và mình đang ở nhánh feature/bye_there
.
Ở trong index.js
, hãy thêm console.log("bye there")
:
console.log("hello there");
console.log("bye there");
Sau khi hoàn thành task, hãy tạo một version mới:
git add index.js
rồi
git commit -m "[feature] add bye there"
Đây là hình vẽ minh họa giải thích những gì mình vừa làm:
Tiếp theo, hãy merge nó vào nhánh master
. Có 2 bước để làm việc này:
- Chuyển sang nhánh
master
:git checkout master
- Merge nhánh
feature/bye_there
vàomaster
:git merge feature/bye_there
Đầu tiên, hãy chuyển sang nhánh master
:
git checkout master
Tiếp theo, hãy merge nhánh feature/bye_there
vào master
:
git merge feature/bye_there
Ok vậy là mình đã merge thành công nhánh feature/bye_there
vào master
. Dưới đây mình sẽ giải thích kỹ hơn một chút điều gì vừa xảy ra khi mình gọi git merge
.
Khi bạn bảo git merge nhánh feature/bye_there
vào master
, git sẽ merge tất cả những thay đổi ở nhánh feature/bye_there
vào master
.
Nhánh feature/bye_there
thay đổi những gì so với nhánh master
? Đó là:
- Ở file
index.js
ở line 2 viếtconsole.log("bye there")
Vậy git sẽ làm thế này:
- Apply những thay đổi vào nhánh master: ở file
index.js
ở line 2 viếtconsole.log("bye there")
- Tự động tạo một version mới ở nhánh
master
(add and commit)
Tóm tắt lại, có 5 bước bạn phải làm khi nhận một task mới:
git checkout -b [new branch]
git add [file name]
git commit -m [message]
git checkout master
git merge [new branch]
Conflict và resolve conflict
Bây giờ bạn đã hiểu một workflow đơn giản khi nhận một task mới bạn phải làm thế nào, tiếp đến ở phần này hãy giải thích về conflict. Khi nào thì conflict xảy ra và làm sao để resolve conflict?
Tưởng tượng có 2 người nhận 2 task mới cùng một lúc:
- Task #1: Thêm
console.log(1)
vào filemain.js
- Task #2: Thêm
console.log(2)
vào filemain.js
Cả 2 người này sẽ tạo 2 nhánh mới xuất phát từ cùng một điểm trên nhánh master. Mình có hình minh họa dưới đây giải thích làm các task lần lượt khác với nhiều người làm các task cùng lúc khác nhau như thế nào.
Để giả sử mình là 2 người làm 2 task một lúc, mình sẽ làm thế này. Đầu tiên mình sẽ tạo nhánh feature/print_1
rồi git add
và git commit -m
trên đó, nhưng tiếp theo chưa merge ngay vào nhánh master
, mà từ nhánh master
tạo tiếp nhánh feature/print_2
rồi git add
và git commit -m
xong nhánh đó, rồi mới merge cả 2 nhánh feature/print_1
và feature/print_2
vào master
.
Nếu bạn thấy ở trên mình nói hơi... khó hiểu, dưới đây là hình vẽ minh họa mình sẽ làm gì:
Đầu tiên hãy hoàn thành task #1. Theo "đúng quy trình", mình sẽ phải làm thế này:
- Tạo một nhánh mới
- Tạo file
main.js
mới và thêm vào đóconsole.log(1)
- Tạo một version mới:
git add
rồigit commit -m
Đầu tiên, hãy kiểm tra mình đang ở nhánh nào:
git branch
Đây là kết quả của mình:
feature/bye_there
* master
Điều này có nghĩa mình đang ở nhánh master
. Hãy đảm bảo rằng mình đang ở nhánh master
trước khi tiếp tục.
Tiếp theo, hãy tạo nhánh feature/print_1
.
git checkout -b feature/print_1
Bây giờ hãy cùng kiểm tra xem mình đang ở nhánh nào:
git branch
Đây là kết quả của mình
feature/bye_there
* feature/print_1 master
Điều này có nghĩa là mình có 3 nhánh và mình đang ở nhánh feature/print_1
.
Hãy hoàn thành task #1 bằng cách tạo file main.js
với console.log(1)
:
console.log(1)
Tiếp đến, hãy tạo một version mới trên nhánh này:
git add main.js
rồi
git commit -m "[feature] add console log 1"
Bình thường bước cuối cùng sẽ là merge feature/print_1
vào master
nhưng vì mình đang giả sử 2 người làm 2 task cùng lúc, mình sẽ chưa merge ngay vội.
Tiếp theo, hãy hoàn thành task #2.
Theo "đúng quy trình", mình sẽ làm theo các bước này:
- Chuyển về nhánh
master
- Tạo một nhánh mới là
feature/print_2
- Làm task #2 trên nhánh này: tạo file
main.js
mới vớiconsole.log(2)
Đầu tiên, hãy chuyển về nhánh master
:
git checkout master
Bạn sẽ thấy file main.js
biến mất tại vì ở nhánh master
thì file đó còn chưa tồn tại.
Bây giờ hãy tạo nhánh feature/print_2
:
git checkout -b feature/print_2
Hãy tạo một thói quen tốt kiểm tra mình đang ở trên nhánh nào:
git branch
Đây là kết quả của mình:
feature/bye_there feature/print_1
* feature/print_2 master
Điều này có nghĩa project hiện tại của mình có 4 nhánh và mình đang ở nhánh feature/print_2
Bây giờ hãy làm task #2 bằng cách tạo file main.js
với nội dung như sau:
console.log(2)
Tiếp theo, hãy tạo một version mới trên nhánh feature/print_2
này:
git add main.js
rồi
git commit -m "[feature] add console log 2"
Ok vậy là mình hoàn thành task #2 rồi!
Bây giờ hãy merge nhánh feature/print_1
and feature/print_2
vào master
.
Bước đầu tiên là chuyển về nhánh master
:
git checkout master
Đầu tiên hãy merge nhánh feature/print_1
vào master
:
git merge feature/print_1
Tiếp theo, hãy merge nhánh feature/print_2
vào master
:
git merge feature/print_2
Và... conflict xảy ra! Khi gõ lệnh merge bên trên bạn sẽ nhìn thấy git thông báo conflict:
Auto-merging main.js
CONFLICT (add/add): Merge conflict in main.js
Automatic merge failed; fix conflicts and then commit the result.
Mình có conflict ở file main.js
. Nếu bạn mở file main.js
, bạn sẽ nhìn thấy như sau:
<<<<<<< HEAD
console.log(1);
=======
console.log(2);
>>>>>>> feature/print_2
Nếu bạn sử dụng VSCode, đây sẽ là cái mà bạn nhìn thấy:
Git đánh dấu chỗ nào có conflict bằng cách dùng <<<< HEAD
, ======
, >>>>> feature/print_2
. Bây giờ nhiệm vụ của mình sẽ là resolve conflict.
Nhưng trước tiên hãy giải thích tại sao conflict lại xảy ra ở main.js
, HEAD (current change) và incoming change mà VSCode hiển thị nghĩa là gì.
Một cách ngắn gọn, conflict xảy ra vì ở cả 2 nhánh feature/print_1
và feature/print_2
đều có file main.js
mà ở cùng một dòng lại khác nhau (chính xác hơn là chỉnh sửa một cách khác nhau). Ở feature/print_1
là console.log(1)
. Ở feature/print_2
là console.log(2)
.
HEAD chỉ mình đang ở nhánh nào: master
, trong khi "Incoming change" chỉ đến nhánh mà mình muốn merge vào master
: feature/print_2
.
Nếu bạn muốn mình giải thích kỹ hơn một chút, bạn có thể đọc tiếp dưới đây.
Đầu tiên, khi mình merge nhánh feature/print_1
vào master
, git sẽ merge những thay đổi ở nhánh feature/print_1
vào master
:
- Tạo file
main.js
mới - Ở file
main.js
ở dòng 1 viếtconsole.log(1)
- Add và commit (trên nhánh
master
)
Tiếp theo, khi mình merge nhánh feature/print_2
vào master
, git sẽ merge những thay đổi ở nhánh feature/print_2
vào master
. Vậy git sẽ làm thế này:
- Tạo file
main.js
mới (file đã có!) - Ở file
main.js
ở dòng 1 viếtconsole.log(2)
- Add and commit (trên nhánh
master
)
Tuy nhiên ở bước thứ hai thì file main.js
ở dòng 1 đã là console.log(1)
. Đây là lý do conflict xảy ra.
Vậy làm sao để mình resolve conflict này? Trong trường hợp này, vì mình phải hoàn thành cả 2 task nên mình sẽ giữ cả 2 cái console.log
.
Ở main.js
xóa những cái git thêm vào để đánh dấu conflict xảy ra ở đâu <<<<< HEAD
, =====
, >>>>> feature/print_2
:
console.log(1);
console.log(2);
Bây giờ ở file main.js
có cả 2 dòng console.log
, có nghĩa là mình đã làm cả task #1 và task #2. Sau khi resolve conflict thành công, hãy add và commit file main.js
:
git add main.js
rồi
git commit -m "Merge branch 'feature/print_2'"
Ok vậy là mình resolve conflict xong rồi! Đó là cách mình resolve conflict!
Giải thích các command ở phần 1
Ở phần 1, mình nói là 2 command git checkout master
và git switch -c
mình sẽ giải thích ở phần này sau khi nói về nhánh. Bây giờ mình sẽ giải thích nó.
Có lẽ bây giờ bạn đã biết git checkout master
là để chuyển về nhánh master
. Nhưng ngoài việc bạn có thể dùng nó để chuyển nhánh bạn còn có thể dùng nó để thoát khỏi "preview mode" nữa. Nếu bạn đang ở nhánh feature/print_1
chẳng hạn và dùng git checkout [version hash]
để xem các version trước, bạn có thể dùng git checkout feature/print_1
để thoát khỏi preview mode đó!
Lệnh tiếp theo git switch -c
là để tạo ra một nhánh mới từ commit đó và chuyển sang nhánh mới đó. Với lệnh này, bạn không phải dùng git reset --hard [version-hash]
(bạn sẽ không xem lại được những version sau đó nữa).
A fun thought experiment
Vậy là phần 2 kết thúc ở đây. Chúc mừng bạn đã học được những lệnh cơ bản về version và nhánh của Git! Bây giờ bạn có thể tự thưởng cho mình một ván game sau khi đã học hành vất vả!
Nhưng cho phần kết mình có "bài tập" vui nho nhỏ để giúp bạn quen thuộc hơn về khái niệm về nhánh và những lệnh cơ bản của git.
Bạn đã bao giờ thấy cái ảnh hài này chưa?
Bạn đã bao giờ nghe các bạn làm designer phàn nàn về việc khách hàng suốt ngày thay đổi thiết kế chưa? Kiểu các bạn ấy bỏ 1 tiếng ra chỉnh sửa thiết kế theo ý khách hàng, chỉ để khách hàng bảo hãy làm lại như ban đầu đi. Mà nữa, nhiều khi các bạn ấy phải edit lại về ban đầu vì nó đã quá giới hạn bấm Ctrl + Z.
Bây giờ bạn đã biết dùng Git, bạn sẽ làm gì khi gặp tình huống này:
- Khách hàng muốn bạn chỉnh sửa ảnh cho cô ấy
- Khách hàng muốn bạn thêm chút tương phản và ánh sáng cho bức ảnh
- Rồi khách hàng muốn bạn dùng Liquidfy để nhìn cô ấy cho thon hơn
- Rồi cô ấy hơi... hối hận và bảo bạn revert lại như ban đầu (nhìn trông ảo quá!)
- Rồi cô ấy bảo bạn có cách nào cho tóc cô ấy thành màu hồng không
- Xong cô ấy lại bảo bạn thử chỉnh tóc cô ấy thành màu xanh nước biển xem có xinh hơn không
- Xong cô ấy muốn tóc hồng
- Xong cô ấy muốn làm cho mắt cô ấy to hơn chút
- Tuyệt vời! Cô ấy cảm ơn bạn.
Nếu bạn không biết Liquidfy trong Photoshop làm gì, mình có bức ảnh này giải thích cho bạn:
Ok sau khi bạn đã hiểu về "chi tiết kỹ thuật" Liquidfy là gì, bây giờ mình sẽ giải thích mình có thể dùng git như thế nào trong tình huống này.
Đây là hình vẽ giải thích mình sẽ làm thế nào:
Giả sử mình làm việc trên file cute-photo.psd
:
- Khách hàng muốn bạn chỉnh sửa ảnh cho cô ấy
git init
git add cute-photo.psd
git commit -m "initial commit"
- Khách hàng muốn bạn thêm chút tương phản và ánh sáng cho bức ảnh
git checkout -b feature/dramatic_lighting
git add cute-photo.psd
git commit -m "[feature] add dramatic lighting"
git checkout master
git merge feature/dramatic_lighting
- Rồi khách hàng muốn bạn dùng Liquidfy để nhìn cô ấy cho thon hơn
git checkout -b feature/liquidfy
git add cute-photo.psd
git commit -m "[feature] liquidfy thinner"
git checkout master
git merge feature/liquidfy
- Rồi cô ấy hơi... hối hận và bảo bạn revert lại như ban đầu (nhìn trông ảo quá!)
git reset --hard [commit-hash]
- Rồi cô ấy bảo bạn có cách nào cho tóc cô ấy thành màu hồng không
git checkout -b feature/hair_pink
git add cute-photo.psd
git commit -m "[feature] change hair to pink"
- Xong cô ấy lại bảo bạn thử chỉnh tóc cô ấy thành màu xanh nước biển xem có xinh hơn không
git checkout master
git checkout -b feature/hair_blue
git add cute-photo.psd
git commit -m "[feature] change hair to blue
- Xong cô ấy muốn tóc hồng
git checkout master
git merge feature/hair_pink
- Xong cô ấy muốn làm cho mắt cô ấy to hơn chút
git checkout -b feature/eyes_bigger
git add cute-photo.psd
git commit -m "[feature] make eyes bigger"
git checkout master
git merge feature/eyes_bigger
- Tuyệt vời! Cô ấy cảm ơn bạn.
Bạn đã bao giờ thắc mắc tại sao người ta không sử dụng Git khi làm việc với Photoshop, Illustrator, Blender, Maya,... chưa? Chẳng lẽ Git chỉ dùng được với code thôi hả?
Không phải. Bạn hoàn toàn có thể dùng Git để quản lý version những file .psd hay .ai,... Nếu bạn thử chạy những lệnh git add
, git commit -m
,... nó hoạt động hoàn toàn bình thường! Lý do mà người ta không làm việc này là vì khi conflict xảy ra, bạn không thể fix được. Những file Photoshop, Illustrator, Blender,... là binary file, nghĩa là bên trong nó toàn là 0 với 1, bạn còn không thể đọc được chứ đừng nói là resolve conflict.
Kết luận
Hãy cùng tóm tắt lại những gì mình đã học trong bài viết này:
git branch
: xem mình đang ở branch nàogit checkout [branch name]
: chuyển sang một branch khácgit checkout -b [new branch]
: tạo một branch mớigit merge [branch name]
: merge một branch khác vào branch hiện tại mình đang ở- Conflict và làm sao để resolve conflict
Ở phần 3 mình sẽ nói tiếp đến làm sao để làm việc với remote repository như là Github, Gitlab: git push
, git pull
, git clone
,...
Credits
Nếu bạn thích con cá mà mình sử dụng, hãy xem: https://thenounproject.com/browse/collection-icon/stripe-emotions-106667/.