Ở phần 1 và phần 2 mình giải thích về workflow làm việc với git một mình trên máy cá nhân, ở phần 3 này mình sẽ nói về mảnh ghép cuối cùng: làm việc với remote repository (tưởng tượng nó là Google Drive trong thế giới lập trình)
Giống như Google Drive và Dropbox có lẽ một trong những dịch vụ nổi tiếng nhất để lưu trữ file trên cloud, ở thế giới lập trình Github và Gitlab là 2 dịch vụ nổi tiếng nhất để host project của bạn.
Ở bài viết này mình sẽ dùng Github. Vì tất cả những dịch vụ "remote repository" này đều hoạt động rất giống nhau, bạn có thể áp dụng những gì bạn học được ở bài viết này cho hầu như bất kỳ một dịch vụ nào khác.
Tạo một repository trên Github
Đầu tiên, nếu chưa có tài khoản Github bạn hãy tạo một cái. Nó không mất tiền hay đòi thẻ tín dụng đâu. Bạn chỉ cần email và hình như là số điện thoại nữa là được.
Nếu bạn... chưa có hứng để signup vào Github, bạn vẫn có thể đọc tiếp. Mình sẽ vẽ một đống hình minh họa để bạn hiểu mình đang làm gì.
Sau khi có tài khoản Github, hãy tạo một repository mới.
Bạn có thể click vào cái nút màu xanh xanh có chữ "New" ở bên tay trái hay click vào dấu + ở menu phía trên rồi chọn "New Repository" đều được.
Sau đó bạn sẽ được chuyển sang màn hình này:
Mình sẽ đặt tên repository của mình là simple-git-tutorial
, đặt nó là public, rồi click vào nút "Create repository" bên dưới.
Sau khi click vào nút đó bạn sẽ thấy màn hình này.
Điều này có nghĩa là mình đã tạo repository thành công trên Github và Github hướng dẫn mình upload (chính xác hơn là push) project của mình lên đó. Nó giống như bạn tạo một folder mới trên Google Drive rồi Google Drive bảo bạn upload file lên đó vậy.
Phần hướng dẫn đầu tiên mà bạn thấy trên màn hình "...or create a new repository on the command line" là khi bạn tạo repository trên Github trước khi bạn tạo project trên máy tính của bạn.
Phần hướng dẫn thứ hai bạn thấy trên màn hình "...or push an existing repository from the command line" là khi bạn đã có project trên máy tính rồi và muốn push lên repository trên Github này. Nó giống như bạn có một thư mục sẵn trên máy tính và muốn upload toàn bộ những file trong đó lên trên Google Drive vậy
Vì mình đã có sẵn project trên máy tính, mình sẽ đọc theo phần hướng dẫn thứ hai. Nhưng mà lệnh mà Github hướng dẫn này
git remote add origin https://github.com/**************
git branch -M main
git push -u origin main
...là gì?
Mình sẽ bắt đầu với lệnh đầu tiên:
git remote add origin https://github.com/**************
git remote add origin
Lệnh git remote add origin
này là để thông báo cho git về một remote repository mà mình muốn push / pull (giống như upload / download) nó. Mình gọi nó là "origin" và copy url của remote repository đó.
Ví dụ bạn muốn đặt tên remote repository là "asdf" chẳng hạn bạn sẽ viết thế này:
git remote add asdf https://github.com/**************
Nhưng bình thường thì mọi người vẫn đặt là "origin" nên mình cũng sẽ làm như thế:
git remote add origin https://github.com/**************
Bạn copy và chạy lệnh này trên Github chứ đừng dùng một đống dấu ** như mình nhé
Cũng giống như bạn có thể upload cùng một thư mục trên máy tính bạn lên nhiều dịch vụ lưu trữ file khác nhau như Google Drive, Dropbox, OneDrive,..., với một project bạn cũng có thể có nhiều remote repository.
Nếu bạn chạy 3 lệnh này:
git remote add origin https://github.com/**************
git remote add asdf https://gitlab.com/***************
git remote add qwer https://bitbucket.com/***************
...project của bạn sẽ 3 remote repository tên là: origin
, asdf
and qwer
.
Để xem tất cả những remote repository trong project của bạn, bạn có thể chạy lệnh sau:
git remote -v
Đây là ví dụ của mình:
origin https://github.com/************** (fetch)
origin https://github.com/************** (push)
Điều này có nghĩa là git biết rằng mình có một remote repository đặt tên là "origin" có url là https://github.com/**************
và mình có thể fetch hay push lên đó (giống như là upload hay download vậy).
git branch -M
Lệnh git branch -M
này là để đổi tên branch hiện tại bạn đang ở. Ví dụ khi bạn vô tình tạo nhánh có tên sai chính tả thế này:
git checkout -b feature/printtt_1
...và bây giờ bạn muốn chỉnh nó thành đúng chính tả feature/print_1
, bạn có thể dùng lệnh git branch -M
như sau:
git branch -M feature/print_1
Nhưng tại sao Github lại muốn mình đổi tên nhánh của mình thành main
?
Quay lại một chút về ví dụ file gốc ở project nhóm ở phần 1 của mình.
Theo mặc định thì "file gốc" đó tương ứng với branch master
trong git. Nhưng vài năm về trước Github muốn mình dùng branch main
là branch chính thay vì master
.
Bạn có thể đổi tên nhánh master
của bạn thành main
nhưng mình sẽ không làm thế. Sau cùng thì master
với main
chỉ khác nhau ở cái tên thôi.
git push -u origin
Lệnh này là để tạo trên remote repository "origin" một nhánh mới cùng tên và giống hệt nhánh ở trên project ở trong máy tính bạn.
Lệnh mà trên Github hướng dẫn mình;
git push origin main
nghĩa là Github muốn mình push nhánh main
ở trên máy mình lên remote repository trên Github (mà mình gọi là "origin").
Chẳng hạn mình muốn push nhánh feature/print_1
lên remote repository của mình trên Github (mà mình gọi là "origin"), thì mình sẽ dùng lệnh sau:
git push -u origin feature/print_1
Ngoài ra thì lệnh git push -u origin [branch-name]
cũng giống hệt như lệnh git push --set-upstream origin [branch-name]
. 2 lệnh này hoàn toàn giống nhau và có thể dùng thay thế cho nhau được.
Nhưng trong trường hợp của mình thì mình push tất cả các nhánh lên Github nên mình sẽ dùng lệnh sau:
git push origin --all
Sau khi chạy xong lệnh trên nếu bạn refresh (F5) lại trang Github, bạn sẽ thấy thế này:
Điều này có nghĩa là bạn đã push thành công tất cả các nhánh lên remote repository của bạn trên Github thành công rồi!
Cuối cùng về chủ để setup repository này mình sẽ nói về lệnh git clone
.
git clone
Tưởng tượng bạn là thành viên mới join một project và bạn cần download project đó về máy mình. Khi đó bạn sẽ dùng lệnh git clone [url-of-project]
như thế này:
git clone https://github.com/***********
Với Github, bạn có thể tìm url của một project bằng cách click vào button xanh xanh có chữ "<> Code" rồi copy cái url hiển thị bên dưới đó.
Full workflow
Bây giờ cuối cùng mình sẽ đi vào phần chính của series 3 bài viết này: full workflow bạn cần làm gì khi nhận một task mới.
Đây là tất cả các bước:
- Chuyển về nhánh chính (hãy tưởng tượng nhánh này giống như bản copy của file gốc của cả nhóm vậy)
- Download bản mới nhất về
- Tạo một nhánh mới từ nhánh chính này
- Hoàn thành task được giao
git add
vàgit commit -m
- Push nhánh này lên remote repository
- Tạo Pull Request (hay Merge Request nếu bạn dùng Gitlab)
Bây giờ tưởng tượng bạn có một task mới: thêm console.log(3)
vào main.js
.
Đây sẽ là các bước bạn cần làm:
- Chuyển về nhánh chính (
git checkout master
) - Download update mới nhất về
- Tạo một nhánh mới (
git checkout -b feature/print_3
) - Làm task
- Add và commit (
git add main.js
andgit commit -m "add console log 3"
) - Đẩy nhánh này lên remote repository (
git push -u origin feature/print_3
haygit push --set-upstream origin feature/print_3
) - Tạo Pull Request
Đầu tiên, hãy chuyển về branch master
:
git checkout master
Để lấy những update mới nhất của branch master
từ remote repository, bạn có thể dùng lệnh git pull origin [branch-name]
như thế này:
git pull origin master
Hãy nhớ luôn luôn lấy bản update mới nhất về để tránh conflict!
Tiếp theo hãy tạo nhánh mới tên là feature/print_3
:
git checkout -b feature/print_3
Bây giờ hãy làm task được giao. Ở file main.js
, thêm dòng này:
console.log(3);
Tiếp theo hãy add và commit file này:
git add main.js
rồi
git commit -m "[feature] add console log 3"
Tiếp theo, hãy push nhánh này lên remote repository trên Github:
git push --set-upstream origin feature/print_3
Nếu bạn refresh (F5) lại remote repository của bạn trên Github, bạn sẽ thấy thế này:
Điều này có nghĩa là mình push nhánh của mình lên Github thành công! Bước tiếp theo là tạo Pull Request.
Cách nhất để tạo một Pull Request trong trường hợp này là bấm vào nút "Compare & pull request" màu xanh xanh. Tuy nhiên tại vì không phải lúc nào button này cũng xuất hiện nên mình sẽ hướng dẫn cách dài hơn dưới đây:
Đầu tiên, hãy bấm vào tab "Pull Request" mà mình khoanh đỏ ở phía bên trên. Sau đó bạn sẽ thấy màn hình này:
Tiếp theo, hãy click vào nút xanh xanh "New pull request" mà mình khoanh đỏ. Sau đấy bạn sẽ được chuyển sang màn hình này:
Hãy chọn base branch là master
và compare branch là feature/print_3
(mình muốn tạo một Pull Request để merge những thay đổi từ nhánh feature/print_3
vào master
).
Bây giờ hãy bấm vào nút "Create pull request". Bạn sẽ được chuyển sang màn hình này:
Sau khi thêm title và description, hãy bấm vào nút "Create pull request" xanh xanh ở phía dưới. Cuối cùng bạn sẽ thấy màn hình này:
Nếu bạn thấy màn hình này nghĩa là bạn đã tạo một Pull Request thành công rồi!
Optional: nhánh develop
Ở dự án thật thì thường có nhiều nhánh cho nhiều mục đích khác nhau, chẳng hạn như master
(hay main
) là cho deploy môi trường production thật sự, staging
để deploy lên môi trường staging, còn develop
là nhánh "chính" mà các lập trình viên bọn mình merge code vào trong đó. Nếu bạn đi làm thì khả năng cao là bạn sẽ merge vào nhánh develop
thay vì merge trực tiếp vào master
.
Review và merge Pull Request (hay Merge Request)
Vì mình là leader của... chính mình, hãy cùng review và merge Pull Request mà mình vừa tạo ra.
Để review một Pull Request, bạn có thể click vào tab Files changed để xem xem có những thay đổi gì muốn merge vào nhánh master
.
Như bạn thấy ở đây thì ở line 3 ở file main.js
mình thêm một dòng mới là console.log(3)
. Đây chính xác là những gì mình được giao và mình đã hoàn thành nó rất tốt. Vì vậy mình sẽ merge Pull Request này.
Để merge Pull Request trong Github, đầu tiên hãy click vào tab Conservation rồi click vào nút xanh xanh "Merge pull request" ở dưới.
Sau khi làm vậy bạn sẽ được chuyển sang màn hình này:
Điều này có nghĩa là bạn đã merge thành công Pull Request! Nếu bạn bấm vào tab "Code", rồi chọn nhánh master
, rồi vào file main.js
để xem xem file này có những gì, bạn sẽ thấy console.log(3)
ở line 3:
Bây giờ bạn hiểu một member và leader trong dự án phải làm những gì, đây là hình vẽ giải thích tổng thế cả quy trình hoạt động như thế nào:
Task mà phụ thuộc vào nhau
Một trong những điểm quan trọng nữa mà mình nghĩ cần được nhắn đến là khi bạn được giao những task mà phụ thuộc vào nhau (nghĩa là chỉ khi bạn hoàn thành task này rồi mới sang được task tiếp theo).
Nếu như tất cả những task mà bạn được giao đều độc lập với nhau thì mọi việc khá đơn giản. Bạn chỉ cần làm theo workflow trên lặp đi lặp lại là được. Chuyển về nhánh master
(hay develop
), lấy những update mới nhất, tạo một nhánh mới, hoàn thành task, push lên Github, tạo Pull Request, đợi được merge, trong lúc đó thì làm task mới, chuyển về nhánh master
,...
Nhưng còn những task mà phụ thuộc vào nhau thì phải làm thế nào? Nếu bạn phải hoàn thành task #1 rồi mới làm task #2 được thì sao?
Nếu bạn làm task #1, nhưng leader của bạn chưa review rồi merge Pull Request, thì khi chuyển sang nhánh master
rồi pull về, nhánh master
đó sẽ không có code của task #1 để làm task #2.
Những giải pháp "không đúng"
Mới đầu, bạn có thể nghĩ đến việc làm task #2 luôn trên nhánh của task #1 cho nhanh
Nhưng nếu bạn làm task #2 luôn trên nhánh của task #1 rồi push lên, Pull Request của bạn sẽ chứa những thay đổi của cả task #1 lẫn task #2. Pull Request chỉ quan tâm đến việc merge branch này vào branch kia chứ không quan tâm đến từng commit của từng branch.
Để giải thích tại sao một Pull Request chứa code của cả 2 task là không tốt hãy tưởng tượng tình huống này.
Bạn tạo một Pull Request cho cả 5 task một lúc. Bây giờ tưởng tượng bạn là leader review Pull Request này. Bạn muốn review một Pull Request to chứa 1000+ thay đổi của cả 5 task cùng một lúc hay 5 Pull Request riêng biệt mỗi cái chỉ chứa 200+ thay đổi cho từng task cụ thể?
Đôi khi task của bạn phức tạp đến mức một Pull Request chứa hàng nghìn thay đổi trong nhiều file khác nhau, nhưng hãy cố tạo những Pull Request nhỏ và đơn giản nhất có thể (ví dụ như mỗi Pull Request cho mỗi task hay bug một lúc).
Quay trở lại vấn đề, bạn cũng có thể định đợi leader merge Pull Request của bạn trước khi làm task tiếp theo như thế này:
Nhưng hiển nhiên việc này khá... lãng phí thời gian. Một leader có thể phải review nhiều Pull Request khác trước khi review đến Pull Request của bạn.
Giải pháp
Thực ra giải pháp cho vấn đề này khá đơn giản: bạn hãy merge nhánh của task #1 vào nhánh của task #2 như thế này.
Làm như vậy thì khi bạn push task #2 lên rồi tạo Pull Request, nó sẽ là 2 Pull Request riêng biệt nhau, mỗi Pull Request cho chỉ riêng một task.
Nhưng bình tĩnh! Chẳng phải nếu mình làm vậy thì Pull Request #2 sẽ chứa code của cả task #1 lẫn task #2 hay sao?
Chính xác là như thế. Nhưng khi review thì leader của bạn sẽ review và merge Pull Request #1 trước Pull Request #2. Sau khi Pull Request #1 được merge vào, thì khi mở tab "File changed" của Pull Request #2 ra, leader của bạn sẽ chỉ thấy những thay đổi của task #2 và nhánh master
thôi. Vì task #1 đã được merge vào master
rồi, những thay đổi này là những thay đổi chỉ thực sự cần thiết để hoàn thành task #2.
Kết luận
Chúc mừng bạn đã đọc đến cuối series này! Mình hy vọng bạn thấy những bài viết này giúp ích cho bạn.
Ngày xưa thời mình còn học Đại học mỗi lần có project nhóm mình toàn gửi file zip cho nhau chứ không dùng git. Bọn mình biết từng phần: git và Github, nhưng không ai biết toàn bộ cả workflow một nhóm sẽ phối hợp với nhau như thế nào. Mình hy vọng qua series này bạn hiểu về quy trình cơ bản cả một team dùng git hoạt động như thế nào và có thể áp dụng nó cho project của bạn.
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/.