Git
Introduction
Git là công cụ mà Dev nào cũng phải biết. Để hiểu về Git thì mình recommend đọc cuốn Pro Git là đủ. Đối với mình thì hiểu đến 80% cuốn đó là ổn.
Dưới đây là kiến thức mình học được từ cuốn sách và từ kinh nghiệm của bản thân mình. Một số phần mình sẽ skip (được liệt kê ở phần Note)
About Git
Git là một Distributed VCS, được tạo bởi cha đẻ của Linux vào năm 2005. Điểm khác biệt lớn nhất của Git so với các hệ thống DVCS cùng thời là ở cách Git làm việc với data. Git coi dữ liệu nó quản lý là stream of snapshots thay vì stream of changes.
“Every time you commit, or save the state of your project in Git, it basically takes a picture of what all your files look like at that moment and stores a reference to that snapshot. To be efficient, if files have not changed, Git doesn’t store the file again, just a link to previous identitcal file it has already stored.”
Điều này đem lại một số lợi ích lớn khi sử dụng Git, chúng ta sẽ tìm hiểu tiếp ở các phần tiếp theo.
Hầu hết các thao tác đều có thể thực hiện offline. -> nhanh, tiện. Git rất đảm bảo, sử dụng SHA-1 để checksum -> ko file nào thay đổi mà Git không biết.
“Everything in Git is check-summed before it is stored and is then referred to by that checksum.”
Git thường chỉ thực hiện thao tác thêm dữ liệu: Một khi đã commit thì khá khó để làm mess up mà không thể revert.
Trong Git, files sẽ có 3 states:
- modified: chỉnh sửa nhưng chưa commit.
- staged: files đã mark là chuẩn bị để commit.
-
commited: files đã nằm an toàn trong local database.
- untracked: chưa có trong git
Git Basics
Setup Git
Git có kèm theo công cụ git config
để thay đổi configuration variables, những biến này có thể ở 3 vị trí:
/etc/gitconfig
: chứa biến áp dụng với toàn bộ user trong hệ thống. ->--system
~/.gitconfig
hoặc~/.config/git/config
: chưa biến dành riêng cho bạn ->--global
config
file: nằm trong folder.git
, dành riêng cho project đó. ->--local
.
Mình có thể lựa chọn tầng config bằng cách sử dụng option --system
, --global
, --local
. Khi cài đặt lần đầu thì cần config user.name và user.email vì Git sẽ cần 2 thông tin này để hoạt động, nên sử dụng option --global
để config.
git config --global user.name "minhdq99hp"
git config --global user.email "minhdq99hp@gmail.com"
git config --global core.editor emacs # mac dinh co the la nano hoac vim
git config --list # xem danh sach cac setting da cai dat
Basic Commands
Phần này thì mình sẽ viết những câu lệnh mà mình hay sử dụng, trên thực tế, còn rất nhiều lệnh, nhiều option có thể hữu ích khác. Nhưng mình thấy rằng các câu lệnh dưới đây là đủ, nếu cần những nhu cầu phức tạp hơn thì có thể sử dụng GUI tool trên Github, Gitlab,…
# basic commands
git init
# discard changes in file
git checkout -- <file> # dangerous
# stage file or track file or marking merge-conflicted as resolved
git add
# unstange file
git reset HEAD <file>
git commit
git commit -a -m "First commit" # skip staging
git commit -amend # undo commit -> go to the staged area
git status
git status -s # short status
# remove file
git rm <file> # delete from git and also delete it from filesystem
git rm --cached <file> # rm from git only
# move file or rename file
git mv <file>
# show log
git log
git log --graph # show log graph
# working with remote
git clone
git remote -v
git remote add <remote_name> <url>
git remote set-url <remote_name> <new_url>
git fetch # pull down all the data from remote that you don't have yet. (all branches)
git pull # fetch and merge.
git push <remote_name> <branch>
.gitignore
The rules for the patterns you can put in the .gitignore file are as follows:
- Standard glob patterns
- ** match nested directory
- [abc] match a or b or c
- You can end patterns with a forward slash (/) to specify a directory.
- You can negate a pattern by starting it with an exclamation point (!).
.gitignore
use glob patterns.
Example;
# ignore all .a files
*.a
# exclude
!lib.a
# only ignore the TODO file in the root directory, not subdir/TODO
/TODO
# ignore all files in any directory named build
build/
# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt
# ignore all .pdf files in the doc/ directory and any of its subdirectories
doc/**/*.pdf
GitHub duy trì một danh sách các .gitignore cho các project và ngôn ngữ ở đây
Viewing your staged and unstaged changes
Use git diff
to view your staged and unstaged changes.
git diff
: see what are changed but not stagedgit diff --staged
(staged
andcached
are synonyms): see what have been staged.
Dùng VSCode thì nó cũng hỗ trợ tính năng này.
Tagging
Git has the ability to tag specific points in a repo’s history as being important (Ex: v1.0, v2.0). Thường thì sẽ sử dụng để mark release points.
Git supports two types of tags:
- lightweight: a lightweight tag là một pointer trỏ đến một commit cụ thể. (thực chất nó là một branch luôn)
- annotated: được store như một object đầy đủ trong database của Git. Chứa nhiều thông tin hơn như: tên người tag, email, date, message. và có thể signed và verifed.
Theo mặc định thì git push
sẽ không share tag lên, cần phải thêm thủ công:
git push <remote_name> <tag_name>
git push <remote_name> --tags # them nhieu tag cung mot luc
# Listing your tags
git tag # list all available tags
git tag -l "v1.0*" # search for tags
# Create annotated tag
git tag -a v1.4 -m "my version 1.4"
# Create a lightweight tag
git tag v1.4-lw
# Tag a specific commit
git tag v1.4-lw <commit_checksum>
Git Alias
Phần này cũng tiện đấy, cơ mà mình không dùng vì hay làm việc với remote server. Sẽ mất công nhớ và phải config trên remote server nữa thì mới sử dụng được. Với lại các câu lệnh của git cũng khá đơn giản rồi, mình không có nhu cầu sử dụng phức tạp hơn.
Git Branching
Đây chính là “killer feature” của Git. Đối với những DVCS khác, việc chia branch rất phức tạp, tốn kém. Nhưng đối với Git, tạo branch là một điều luôn được recommend trong git flow. Tại sao việc tạo branch trong Git lại dễ dàng là bởi cách Git xử lý data (stream of snapshots). Mỗi branch thực chất chỉ là một pointer trỏ đến commit.
HEAD: current branch, symbolic refer tới branch đang làm việc
Có 2 cách để “nhập branch”:
- Merge: tạo một commit mới dựa trên 3 điểm (commit chung gần nhất, commit sẽ merge vào và commit sẽ được merge). Câu lệnh là
git merge <branch_b>
(nếu đang ở branch_a thì có nghĩa là sẽ có một merge commit được tạo ở branch_a). Trong quá trình merge thì sẽ có lúc Git không thể merge tự động, cần mình resolve conflict. - Rebase: git sẽ tách base của branch_b (tạo những commit tương ứng và commit thẳng vào branch_a). Như vậy, có thể thấy rằng lúc đó lịch sử của git sẽ trở thành tuyến tính, thay vì rẽ nhánh như merge. Tuy nhiên, nó cũng kèm theo drawback rất dở là sẽ conflict lịch sử với người khác nếu không cẩn thận (tất nhiên là vẫn work around được). Điều cần ghi nhớ ở đây là chỉ rebase trên những nhánh của mình quản lý.
Undoing Changes
Reference:
Git checkout
Git clean
Git revert
Git revert có thể hiểu là một cách an toàn để undo commit (forward-moving undo). Trên thực tế, nó sẽ tạo ra invert commit, điều này sẽ tránh động đến git history.
Khác với Git checkout hay Git reset, git revert không di chuyển pointer. Ngoài ra, Git revert có thể trỏ đến bất kì commit nào, trong khi git reset chỉ có thể hoạt động backward từ commit hiện tại.
# prevent creating commit, only put inverse changes in staging index and working directory.
git revert -n --no-commit
Git reset
Đây là lệnh dùng để undo change, nó có 3 kiểu: soft, mixed, hard. 3 kiểu này liên quan đến Git’s internal state management mechanism: The commit tree (HEAD), the staging index, and the working directory
Git rm
GitHub
Forking Project
GitHub hỗ trợ fork là để người ngoài có thể contribute kể cả khi không có quyền push branch. Lúc đó, người ngoài sẽ fork project về và tạo pull request.
GitHub Flow
GitHub là nơi chuyên để open-source, thế nên họ cũng xây dựng một flow riêng xoay quanh việc tạo pull request để contribute.
- Fork the project
- Create a topic branch from master.
- Make some commits to improve the project
- Push this branch to your GitHub project
- Open a Pull Request on GitHub
- Discuss, and optionally continue committing
- The project owner merges or closes the Pull Request
- Sync the updated master back to your fork
Khi tạo Pull Request, GitHub cũng sẽ kiểm tra xem việc merge vào có tạo conflict hay không, nếu có thì người contributor nên sửa lại branch của mình, chứ không nên để maintainer phải sửa. Có 2 cách để làm điều này: rebase branch của mình về target branch hoặc là merge target branch về branch của mình. -> Nên chọn cách 2 để giảm thiểu lỗi.
“Don’t push your work until you’re happy with it”
Special Files
README
CONTRIBUTING: GitHub sẽ hiện cho contributor file này nếu họ tạo pull request
Q&A
- Q: Copy a git repo without history
-
A: here
git clone --depth <depth> -b <branch> <repo_url>
- Q: Nên chia branch như thế nào ? Có những trường phái nào ?
Git stash là gì ?
Git clean ?
Git reset ? soft vs hard ?
Squashing Commits
Git submodules ?
Git credential storage ?
Git hook ? server-side and client-side ?
Làm thế nào để xóa 1 file chứa thông tin mật trong toàn bộ Git history ?
Note
Có các phần mình sẽ không liệt kê ở trong bài này dù có trong ở sách bao gồm:
- Các vấn đề về xây dựng Git server: hầu như hiện nay đều sử dụng các full-featured server như GitLab, GitHub, BitBucket nên việc xây dựng Git server không quá cần thiết
- Các kiến thức về modify Git history: Còn rất nhiều lệnh có thể modify history của Git, nhưng mình chỉ học một số cái cơ bản thôi. Ngoài ra, cũng không khuyến khích việc chỉnh sửa history (nhất là khi làm việc với team) nên mình sẽ không đi sâu về phần này.
- Kiến thức về cách Git hoạt động: mình chỉ cần tiếp cận Git ở tầng abstract, thế nên cũng không có ý định đi sâu về phần này.
References
- Book: Pro Git (2014)
- Git tutorials of Atlassian
Contributor
- minhdq99hp $\dagger$
Comments