🌍 English ∙ Español ∙ Русский ∙ 繁體中文 ∙ 简体中文 ∙ 한국어 ∙ Tiếng Việt ∙ Français ∙ 日本語
Là tài liệu hướng dẫn cho các phi hành gia vũ trụ (và tại đây, cho các lập trình viên sử dụng Git) về những việc cần làm khi có sai lầm xảy ra.
Flight Rules là những kiến thức vất vả kiếm được trong các hướng dẫn sử dụng chỉ ra, từng bước, phải làm gì nếu X xảy ra và tại sao. Về cơ bản, chúng là các chuẩn quy trình thực hiện rất chi tiết cho từng kịch bản cụ thể . [...]
NASA qua thời gian đã ghi lại những sai lầm, thảm hoạ và giải pháp của chúng tôi kể từ đầu những năm 1960, khi các đội mặt đất trong thời kỳ chương trình Mercury bắt đầu thu thập "các bài học kinh nghiệm" thành một bản yếu lược liệt kê hàng nghìn tình huống có vấn đề, từ lỗi động cơ đến các tay cầm bị bẻ cong đến trục trặc máy tính, và các giải pháp của họ.
— Chris Hadfield, Sổ Tay Phi Hành Gia.
Để chuyền tải rõ ràng, tất cả các ví dụ trong tài liệu này sử dụng bash prompt được tuỳ chỉnh để chỉ ra nhánh hiện tại và có hay không thay đổi trong vùng chuyển tiếp (staged changes). Nhánh được đặt trong dấu ngoặc đơn và một ký tự *
bên cạnh tên nhánh cho biết các thay đổi trong vùng chuyển tiếp.
Tất cả các lệnh (command) phải thi hành với phiên bản lâu đời nhất là git 2.13.0. Xem git website để cập nhật phiên bản git trên local của bạn.
Danh mục nội dung generated with DocToc
- Repositories (Kho)
- Chỉnh sửa Commit
- Tôi vừa commit cái gì?
- Tôi đã viết sai vài thứ trong message (thông điệp) của commit
- Tôi đã commit với cấu hình tên và email sai
- Tôi muốn xoá một file từ commit trước
- Tôi muốn xoá hoặc loại bỏ commit mới nhất
- Xoá/loại bỏ bất kỳ commit nào
- Tôi đã cố gắng push commit đã sửa đổi lên remote, nhưng tôi gặp thông báo lỗi
- Tôi đã vô tình thực hiện hard reset và tôi muốn các thay đổi của tôi.
- Tôi vô tình commit và đẩy lên một merge
- Tôi vô tình commit và đẩy các file chứa dữ liệu nhảy cảm
- Tôi muốn xóa file to quá để chưa bao giờ xuất hiện trong lịch sử repository
- Tôi cần thay đổi nội dung của một commit nhưng không phải là cái mới nhất
- Staging (sân chuyển tiếp)
- Tôi muốn nâng lên stage tất cả file đang theo dõi và bỏ qua file không theo dõi
- Tôi cần cho thêm các thay đổi đang trong stage vào commit trước
- Tôi muốn stage một phần của một file mới, nhưng không phải toàn bộ file
- Tôi muốn thêm các thay đổi trong một file vào 2 commit khác nhau
- Tôi cho lên stage quá nhiều thay đổi, và tôi muốn tách ra thành các commit khác nhau
- Tôi muốn cho lên stage các chỉnh sửa chưa được stage và hã khỏi stage các chỉnh sửa đã stage
- Thay đổi chưa lên sân (Unstaged Edits)
- Tôi muốn di chuyển các chỉnh sửa chưa lên stage sang một nhánh mới
- Tôi muốn di chuyển các chỉnh sửa chưa stage của tôi đến một nhánh khác đã tồn tại
- Tôi muốn bỏ các thay đôi chưa trong commit tại local (đã lên hoặc chưa lên stage)
- Tôi muốn loại bỏ các thay đổi cụ thể chưa lên stage
- Tôi muốn loại bỏ các file cụ thể chưa lên stage
- Tôi muốn chỉ loại bỏ các thay đổi chưa lên stage tại local
- Tôi muốn loại bỏ tất cả các file chưa được theo dõi (track)
- Tôi muốn hạ khỏi stage một file cụ thể đã stage
- Nhánh
- Tôi muốn liệt kê tất cả các nhánh
- Tạo một nhánh mới từ một commit
- Tôi đã pull (kéo) từ/vào sai nhánh
- Tôi muốn loại bỏ các commit tại local để nhánh của tôi giống như nhánh trên server
- Tôi đã tạo commit lên main thay vì một nhánh mới
- Tôi muốn giữ toàn bộ file từ một ref-ish khác
- Tôi đã thực hiện một số commit trên một nhánh mặc dù chúng nên ở các nhánh khác nhau
- Tôi muốn xóa các nhánh local đã bị xóa tại luồng trước (upstream)
- Tôi vô tình xóa nhánh của tôi
- Tôi muốn xoá một nhánh
- Tôi muốn xoá nhiều nhánh
- Tôi muốn đổi tên một nhánh
- Tôi muốn checkout đến một nhánh remote mà người khác đang làm việc trên đó
- Tôi muốn tạo một nhánh remote mới từ một nhánh local hiện tại
- Tôi muốn thiết lập một nhánh remote làm upstream (luồng trước) cho một nhánh local
- Tôi muốn để HEAD của tôi dõi theo nhánh mặc định của remote
- Tôi đã thực hiện thay đổi trên sai nhánh
- Tôi muốn tách một nhánh thành hai
- Rebasing và Merging
- Stash (Cất)
- Finding (Tìm)
- Submodules
- Miscellaneous Objects (Những thứ khác)
- Tracking (Theo dõi) các file
- Tôi muốn thay đổi cách viết hoa của tên tệp mà không thay đổi nội dung của tệp
- Tôi muốn ghi đè lên các tệp local khi thực hiện lệnh git pull
- Tôi muốn xóa một tệp khỏi Git nhưng vẫn giữ tệp
- Tôi muốn đảo ngược tệp về bản sửa đổi cụ thể
- Tôi muốn liệt kê các thay đổi của một tệp cụ thể giữa các commit hoặc các nhánh
- Tôi muốn Git bỏ qua những thay đổi đối với một tệp cụ thể
- Debugging (Gỡ lỗi) with Git
- Cấu hình (Configuration)
- Tôi không biết mình đã làm gì sai
- Git Shortcuts (phím tắt)
- Tài nguyên khác
Để tạo một Git repository tại thư mục đã tồn tại:
(thư-mục-của-tôi) $ git init
Để clone (copy) một remote repository, copy đường dẫn url cho repository, và chạy :
$ git clone [url]
Lệnh này sẽ tải xuống một thư mục có tên giống tên của remote repository. Hãy chắc chắn rằng bạn có kết nối đến remote server khi bạn đang clone về (phần lớn thời gian nghĩa là cần đảm bảo bạn kết nối được với internet).
Để clone vào một thư mục với tên khác với tên mặc định của repository:
$ git clone [url] name-of-new-folder
Có thể có vài vấn đề khác nhau:
Nếu bạn clone sai repository, chỉ cần xóa thư mục tạo bởi git clone
và sau đó clone đúng remote repository.
Nếu bạn để nhầm repository là origin của một local repository hiện tại, thay đổi URL của origin với lệnh:
$ git remote set-url origin [url của repo đúng]
Xem thêm tại StackOverflow.
Git không cho bạn thêm sửa code vào repository của người khác nếu không có quyền truy cập. GitHub cũng thế, GitHub khác với Git vì là dịch vụ hosting cho các Git repository. Nhưng bạn có thể thêm sửa code với các patch vá lỗi, hoặc, nếu trên GitHub, với forks và pull requests.
Trước hêt, một vài điều về fork. Một fork là một copy của một repository. Đây không phải là một lệnh git, mà là một hành động thường thấy trên GitHub, Bitbucket, GitLab — hoặc bắt cứ đâu host các Git repository. Bạn có thể fork một repository qua UI của dịch vụ host.
Sau khi đã fork một repository, bạn thường phải clone repository về máy của bạn. Tất nhiên bạn có thể tạo vài chỉnh sửa nhỏ trên GitHub nếu không clone về máy, nhưng văn bản này không phải là github-flight-rules, thế nên hãy xem cách trên máy local.
# nếu bạn dùng ssh
$ git clone git@github.com:k88hudson/git-flight-rules.git
# nếu bạn dùng https
$ git clone https://github.com/k88hudson/git-flight-rules.git
Nếu bạn cd
vào thư mục được tạo, và chạy lệnh git remote
, bạn sẽ thấy danh sách các remote. Thường sẽ có một remote - origin
- trỏ đến k88hudson/git-flight-rules
. Trong trường hợp này, bạn cũng muốn một remote trỏ đến fork của bạn.
Đầu tiên, để theo quy chuẩn dùng Git, chúng ta sẽ dùng remote tên origin
cho repository của bạn và tên upstream
cho repository mà bạn fork. Để đổi tên cho remote origin
sang tên upstream
chạy lệnh:
$ git remote rename origin upstream
Bạn cũng có thể đổi tên với lệnh git remote set-url
, nhưng sẽ mất thêm thời gian và nhiều bước hơn.
Sau đó, tạo remote mới để trỏ về repository của bạn.
$ git remote add origin git@github.com:YourtGitHubUsername/git-flight-rules.git
Lưu ý là bây giờ bạn có hai remote.
origin
trỏ đến repository của bạn.upstream
trỏ đến repository nguyên bản .
Với origin
, bạn có thể đọc và viết. Với upstream
, bạn chỉ có thể đọc.
Sau khi đã chỉnh sửa theo mong muốn, push (đẩy) các thay đổi (thường là ở trong branch) tới remote tên origin
. Nếu bạn ở trên nhánh, bạn có thể dùng --set-upstream
để tránh cần phải ghi rõ dùng brach nào của remote mỗi lần push trong tương lai khi dùng nhánh đấy. Ví dụ:
$ (feature/my-feature) git push --set-upstream origin feature/my-feature
Không có cách nào để tạo pull request trên giao diện lệnh (CLI) với Git (mặc dù có vài công cụ, như hub, có cho bạn lựa chọn này). Nếu bạn sãn sàng tạo Pull Request, trở lại GitHub (hoặc dịch vụ host Git) và tạo pull request mới. Nhớ là dịch vụ host sẽ tự động link repository nguyên bản và repository do fork.
Sau cùng, nhớ đùng quên trả lời những comment phê duyệt code.
Một cách khác để thêm sửa code mà không cần sử dụng dịch vụ bên thứ ba như GitHub là dùng git format-patch
.
format-patch
tạo file (tệp) dạng .patch cho một hoặc nhiều commit. File này là cơ bản là danh sách nhưng thay đổi, giống như những commit diffs bạn xem được trên Github.
Các patch có thể được xem hoặc thậm chí thêm sửa bởi người nhận và áp gắn với lệnh git am
.
Ví dụ, để tạo patch dựa vào commit mới nhât, bạn chạy lệnh git format-patch HEAD^
, lệnh sẽ tạo một tệp .patch với tên như: 0001-My-Commit-Message.patch
.
Để áp gắn tệp patch cho repository, bạn sẽ dùng lệnh git am ./0001-My-Commit-Message.patch
.
Các patch còn có thể gửi qua email với lệnh git send-email
. Để xem thêm thông tin về cách dùng hoặc cấu hình, xem: https://git-send-email.io
Sau một quãng thời gian, kho upstream
có thể có thêm thay đổi, và những thay đổi này cần phải được tải về kho origin
. Nhớ là giống bạn, những người khác cũng đang góp sức của họ. Giả dụ bạn đang ở nhánh cho tính năng mới bạn đang thiết kế, và bạn cần update nhánh với những thay đổi trên repository nguyên bản.
Có khi bạn đã có remote trỏ đến project nguyên bản. Nếu không, hãy tạo nó. Thường chúng ta dùng tên upstream
cho remote này:
$ (main) git remote add upstream <link-tới-repository-nguyên-bản>
# $ (main) git remote add upstream git@github.com:k88hudson/git-flight-rules.git
Bây giờ bạn fetch (lấy) từ upstream
và nhận những update mới nhất.
$ (main) git fetch upstream
$ (main) git merge upstream/main
# hoặc với một lệnh duy nhất
$ (main) git pull upstream main
Giả sử bạn vừa commit những thay đổi một cách mù quáng với lệnh git commit -a
và bạn không chắc chắn nội dung thực sự của commit vừa thực hiện là gì. Bạn có thể hiển thị ra commit gần nhất trên trỏ HEAD hiện tại của bạn với lệnh:
(main)$ git show
Hoặc
$ git log -n1 -p
Nếu bạn muốn xem một file tại một commit cụ thể, bạn cũng có thể làm được điều này (khi <commitid>
là commit mà bạn muốn biết) với lệnh:
$ git show <commitid>:filename
Nếu bạn đã viết sai thứ gì đó và commit chưa được push lên, bạn có thể làm theo cách sau để thay đổi message của commit mà không làm thay đổi commit:
$ git commit --amend --only
Câu lệnh đó sẽ mở trình soạn thảo (text editor) mặc định của bạn, nơi bạn có thể chỉnh sửa message. Ngoài ra, bạn có thể làm tất cả điều này với lệnh sau:
$ git commit --amend --only -m 'xxxxxxx'
Nếu bạn đã đẩy message lên, bạn có thể chỉnh sửa commit và force push (đẩy ép), nhưng cách này không được khuyến khích.
Nếu đó là một commit độc lập, chỉnh sửa nó:
$ git commit --amend --no-edit --author "TênTácGiảMới <authoremail@mydomain.com>"
Một cách khác để cấu hình đúng tác giả là cài đặt lại với lệnh git config --global author.(name|email)
và sau đó chạy lệnh
$ git commit --amend --reset-author --no-edit
Nếu bạn cần thay đổi tất cả lịch sử, hãy xem trang man
của git filter-branch
.
Để xoá các thay đổi đối với một file khỏi commit trước, hãy làm như sau:
$ git checkout HEAD^ myfile
$ git add myfile
$ git commit --amend --no-edit
Trong trường hợp file mới được thêm vào commit và bạn muốn xoá nó (riêng trên Git), hãy thực hiện:
$ git rm --cached myfile
$ git commit --amend --no-edit
Cách này đăc biệt hữu ích khi bạn đang mở một bản patch và bạn đã commit một file không cần thiết và cần force push để cập nhật bản patch trên remote. Dòng --no-edit
được dùng để giữ không thay đổi message cho commit hiện tại.
Nếu bạn đã thực hiện một commit bao gồm các thay đổi phù hợp hơn với một commit khác, bạn có thể di chuyển các thay đổi sang commit khác bằng cách sử dụng rebase tương tác (interactive rebase). Đó là câu trả lời từ stackoverflow.
Ví dụ: bạn có ba commit (a, b, c). Trên b, bạn đã thay đổi file1 và file2 và bạn muốn chuyển thay đổi trên file1 từ commit b sang commit a.
Đầu tiên, rebase interactively:
$ git rebase -i HEAD~3
Editor sẽ hiện lên như bên dưới:
pick a
pick b
pick c
Sửa 2 dòng với a và b thành edit:
edit a
edit b
pick c
Lưu và đóng Editor. Bạn sẽ được đưa tới commit b. Bây giờ, hãy reset các thay đổi của file1:
$ git reset HEAD~1 file1
Thao tác trên sẽ unstash những thay đổi trong file1. Tiếp tục, stash những thay đổi đó và tiếp tục rebase:
$ git stash
$ git rebase --continue
Bây giờ bạn sẽ quay lại chỉnh sửa commit a. Unstash các thay đổi sau đó thêm chúng vào commit hiện tại và tiếp tục rebase:
$ git stash pop
$ git add file1
$ git commit --amend --no-edit
$ git rebase --continue
Bây giờ quá trình rebase của bạn đã hoàn tất, với những thay đổi từ b trên a. Nếu bạn muốn di chuyển các thay đổi từ b sang c, bạn sẽ phải thực hiện hai lần rebase vì c đứng trước b: một để lấy các thay đổi ra khỏi b, sau đó một để chỉnh sửa c và thêm các thay đổi đã stash.
Nếu bạn muốn xoá các commit đã push, bạn có thể làm như sau. Tuy nhiên, cách này sẽ thay đổi lịch sử commit không thay đổi được và làm hỏng lịch sử của bất kỳ ai khác đã pull từ repository. Tóm lại, nếu bạn không chắc chắn, bạn không bao giờ nên làm cách này.
$ git reset HEAD^ --hard
$ git push --force-with-lease [remote] [branch]
Nếu bạn chưa push, để đảo ngược Git về trạng thái trước khi bạn thực hiện commit mới nhất (trong khi vãn giữ các thay đổi trong stage) hãy chạy lệnh:
(my-branch)$ git reset --soft HEAD^
Cách này chỉ phù hợp nếu bạn chưa push. Nếu bạn đã push, điều thực sự an toàn nhất cần làm là git revert SHAcủaCommitSai
. Lệnh này sẽ tạo một commit mới để quay trở lại thay đổi của commit trước đó. Hoặc nếu nhánh bạn đã push là rebase-safe (không có kỳ vọng các dev khác sẽ pull từ nó), bạn chỉ có thể sử dụng git push --force-with-lease
. Để biết thêm, hãy xem phần trên.
Lưu ý như trên. Không bao giờ làm điều này nếu có thể tránh được.
$ git rebase --onto SHA1_OF_BAD_COMMIT^ SHA1_OF_BAD_COMMIT
$ git push --force-with-lease [remote] [branch]
Hoặc thực hiện một interactive rebase và loại bỏ các dòng tương ứng với các commit bạn muốn loại bỏ.
To https://github.com/yourusername/repo.git
! [rejected] mybranch -> mybranch (non-fast-forward)
error: failed to push some refs to 'https://github.com/tanay1337/webmaker.org.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Lưu ý rằng, như với rebase (xem bên dưới), amend thay thế commit cũ với một commit mới, nên bạn phải force push (--force-with-lease
) các thay đổi của bạn nếu bạn đã push commit trước amend lên remote của bạn. Hãy cẩn thận khi bạn cách này – luôn luôn đảm bảo rằng bạn đã chỉ định một nhánh!
(my-branch)$ git push origin mybranch --force-with-lease
Nói chung, tránh force push. Tốt nhất là tạo và push một commit mới thay vì force-push commit đã sửa đổi vì nó sẽ gây xung đột trong lịch sử commit cho bất kỳ developer nào đã tương tác với nhánh được đề cập hoặc bất kỳ nhánh con nào. --force-with-lease
sẽ vẫn fail, nếu ai khác cũng đang làm việc trên cùng một nhánh với bạn và việc push lên sẽ ép trên những thay đổi đó.
Nếu bạn hoàn toàn chắc chắn rằng không ai đang làm việc trên cùng một nhánh hoặc bạn muốn cập nhật đỉnh nhánh (tip of branch) vô điều kiện, bạn có thể sử dụng --force
(-f
), nhưng cách này nói chung nên tránh.
Nếu vô tình bạn thực hiện git reset --hard
, bạn có thể vẫn phục hồi lại được commit của bạn, vì git giữ một bản log cho tất cả mọi thứ trong vài ngày.
Chú ý: Điều này chỉ hợp lệ nếu đã có sao lưu, tức là đã có commit hoặc được stash
. Lệnh git reset --hard
sẽ loại bỏ các thay đổi chưa được commit, vì vậy hãy sử dụng nó một cách thận trọng. (Một lựa chọn an toàn là git reset --keep
.)
(main)$ git reflog
Bạn sẽ thấy danh sách các commit gần đây và một commit để reset. Chọn SHA của commit bạn muốn trở lại tới và reset lại:
(main)$ git reset --hard SHA1234
Thế này là xong.
Nếu bạn vô tình merge một nhánh tính năng mới vào nhánh phát triển chính trước khi sẵn sàng để merge, bạn vẫn có thể đảo ngược merge. Nhưng có một điểm phải nắm được: Một commit merge có một hoặc nhiều hơn một parent (gốc) (thường là 2).
Lệnh để chạy:
(feature-branch)$ git revert -m 1 <commit>
Dòng -m 1
là để cho biết cần chọn parent thứ nhất` (nhánh mà merge được thực hiện) làm parent để đảo ngược lại.
Chú ý: Số parent không phải là số commit. Thay vào đó, một commit merge sẽ có một dòng như Merge: 8e2ce2d 86ac2e7
. Số parent là số số nhận dạng đầu-1 (1-based index) của dòng nay, số nhận dạng đầu tiên là 1 cho parent thứ nhất, thứ 2 là cho parent 2, và tiếp tục như thế.
Nếu bạn vô tình push lên các file chứa dữ liệu nhạy cảm (mật khẩu, keys, etc.), bạn có thể amend commit trước. Lưu ý rằng khi bạn đã đẩy một commit, bạn nên coi bất kỳ dữ liệu nào đã bị đẩy như đã bị lộ. Các bước này có thể xoá dữ liệu nhạy cảm từ repo công khai (public repo) hoặc bản sao nội bộ, nhưng bạn không thể xóa dữ liệu nhạy cảm khỏi các bản sao đã được tải về bởi người khác. Nếu bạn có commit mật khẩu, hãy thay đổi mật khẩu ngay lập tức. Nếu bạn đã commit một key, hãy tạo lại key đó ngay lập tức. Việc amend commit đã đẩy là không đủ, vì bất kỳ ai cũng có thể đã pull commit chứa dữ liệu nhạy cảm của bạn trong thời gian đấy.
Nếu bạn đã chỉnh sửa tệp và xóa dữ liệu nhạy cảm, hãy chạy
(feature-branch)$ git add EditedFile
(feature-branch)$ git commit --amend --no-edit
(feature-branch)$ git push --force-with-lease origin [branch]
Nếu bạn muốn xóa toàn bộ tệp (nhưng giữ trên local), hãy chạy:
(feature-branch)$ git rm --cached sensitive_file
echo sensitive_file >> .gitignore
(feature-branch)$ git add .gitignore
(feature-branch)$ git commit --amend --no-edit
(feature-branch)$ git push --force-with-lease origin [branch]
Ngoài ra, lưu trữ dữ liệu nhạy cảm của bạn trong các biến môi trường (variable) của local.
Nếu bạn muốn xóa hoàn toàn toàn bộ tệp (và không giữ tệp tại local), hãy chạy
(feature-branch)$ git rm sensitive_file
(feature-branch)$ git commit --amend --no-edit
(feature-branch)$ git push --force-with-lease origin [branch]
Nếu bạn đã thực hiện các commit khác (tức là dữ liệu nhạy cảm nằm tại commit trước commit mới nhất), bạn sẽ phải rebase.
Nếu file bạn muốn xóa cần bảo mật hay là file chưa thông tin nhạy cảm, xem phần xóa file chứa thông tin nhạy cảm.
Mặc dù bạn đã xóa một file to hay file không muốn có trong dự án, nó có thể vẫn tồn tại trong lịch sử git (git history) của respository trong thư mục .git
, và sẽ khiến các lệnh git clone
tải file không cần thiết.
Những bước trong phần này sẽ yêu cầu push ép, và viết lại phần nào lịch sử git của repository, thế nên nếu bạn làm việc với những người khác, kiểm tra là những thay đổi của họ đã được đẩy.
Có hai lựa chọn để viết lại lịch sử, sử dụng tính năng sãn có git-filter-branch
hoặc dùng bfg-repo-cleaner
. bfg
thao tác sạch hơn và nhanh hơn, nhưng đây là phần mềm bên thứ ba và cần có Java. Chúng ta sẽ xem cả hai cách. Bước cuối cùng là push ép thay đổi của bạn, lần này sẽ còn cần chú ý xem xét hơn các push ép bình thường bởi vì một phần không nhỏ lịch sử repository sẽ thay đổi vĩnh viễn.
Sử dụng bfg-repo-cleaner cần có Java. Tải file dạng .jar cho phần mềm bfg với đường link này. Ví dụ tại đây sẽ dùng bfg.jar
, nhưng file bạn tải xuống có thể có thêm số phiên bản như bfg-1.13.0.jar
.
Để xóa một file, dùng lệnh:
(main)$ git rm path/to/FileToRemove
(main)$ git commit -m "Commit removing filetoremove"
(main)$ java -jar ~/Downloads/bfg.jar --delete-files FileToRemove
Lưu ý là với bfg bạn dùng tển của file chứ không phải đường dẫn đến file.
Bạn cũng có thể xóa file dượng theo một khuôn mẫu, ví dụ xóa tất cả file dạng .jpg:
(main)$ git rm *.jpg
(main)$ git commit -m "Commit removing *.jpg"
(main)$ java -jar ~/Downloads/bfg.jar --delete-files *.jpg
Với bfg, the files that exist on your latest commit will not be affected. For example, if you had several large .tga files in your repo, and then in an earlier commit, you deleted a subset of them, this call does not touch files present in the latest commit
Lưu ý, nếu bạn thay đổi tên file trong một commit trước, ví dụ: nếu tệp bắt đầu với tên LargeFileFirstName.mp4
và một commit đổi tên tệp thành LargeFileSecondName.mp4
, chạy lệnh java -jar ~/Downloads/bfg.jar --delete-files LargeFileSecondName.mp4
sé không xóa file trong lịch sử git. Hoặc là chạy lệnh --delete-files
với cả hai tên, hoặc với khuôn mẫu như trên.
git-filter-branch
nặng hơn và ít tính năng hơn, nhưng bạn có thể dùng cách này nếu không thể cài hay chạy bfg
.
Trong lệnh bên dưới, thay filepattern
với tên file hoặc khuông mẫu, v.d. *.jpg
. Lệnh này sẽ xóa file theo khuôn mẫu khỏi tất cả lịch sử của tất cả các nhánh.
(main)$ git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch filepattern' --prune-empty --tag-name-filter cat -- --all
Giải thích lệnh trên:
--tag-name-filter cat
khá là nặng, nhưng là cách đơn giản nhất để giữ nguyên các tags cho các commit mới bằng cách sử dụng lệnh cat
.
--prune-empty
xóa những commit bây giờ để trống rỗng.
Một khi bạn đã xóa file, kiểm tra thật cẩn thận là bạn không làm hỏng cái gì trong repo - và nếu bạn đã làm hỏng cái gì đó, dễ nhất là clone repo lại và bắt đầu từ đầu. Để kết thúc, bạn có thể dùng chức năng thu hồi rác (garbage collection) để giảm thiểu kích cỡ tệp .git và rồi push ép. c
(main)$ git reflog expire --expire=now --all && git gc --prune=now --aggressive
(main)$ git push origin --force --tags
Vì bạn vừa viết lại toàn bộ lịch sử git repository, lệnh git push
có thể quá to để thi hành, và gửi lại thông điệp lỗi (error) “The remote end hung up unexpectedly”
. Nếu việc này xảy ra, bạn có thể thử tăng post buffer của git:
(main)$ git config http.postBuffer 524288000
(main)$ git push --force
Nếu cách này không hiệu quả, bạn sẽ phải push thủ công lịch sử repo từng cục một. Với lệnh bên dưới, dần dần tăng con số cho <số cục>
đến khi nào lệnh push thành công.
(main)$ git push -u origin HEAD~<số cục>:refs/head/main --force
Một khi lệnh push thành công, dần dần giảm thiểu <số cục>
cho đến khi một lệnh git push
bình thường thành công.
Giả sử bạn đã có vài (v.d. ba) commit và sau nhận ra là bạn quên mất không cho vào một thứ gì đó hợp hơn với commit đầu tiên. Việc này làm phiền bạn vì mặc dù nếu tiếp tục commit bạn sẽ có lịch sử sạch sẽ nhưng commit của bạn không nguyên chất (những thay đổi liên quan với nhau nên ở cùng một commit). Trong trường hợp như vậy, bạn chắc muốn cho thêm những thay đổi liên quan vào commit mong muốn nhưng không muốn những commit sau tiếp cũng phải sửa theo. Trong trường hợp như vây, git rebase
có thể cứu bạn.
Hay xem trường hợp mà bạn muốn thay đổi commit số ba nếu đếm ngược.
(nhánh-bạn)$ git rebase -i HEAD~4
Lệnh trên đưa bạn vào mode (chế độ) rebase tương tác (interactive rebase), chế độ cho phép bạn edit ba commit mới nhất. Một trình soạn thảo (text editor) sẽ bật lên trông giống như sau:
pick 9e1d264 commit trước ba
pick 4b6e19a commit trước hai
pick f4037ec commit trước
và bạn thay/viết thành:
edit 9e1d264 commit trước ba
pick 4b6e19a commit trước hai
pick f4037ec commit trước
Lệnh này bảo rebase là bạn muốn thay đổi commit trước ba và giữ hai commit kia không thay đổi. Sau đó bạn save (và đóng) trình soạn thảo. Git bây giờ sẽ bắt đầu rebase. Nó dừng lại ở commit bạn để là edit và cho bạn cơ hội thay đổi commit đấy. Bây giờ bạn có thể cho thêm những thay đổi bạn lỡ không cho vào lần đầu. Để làm thế, bạn edit rồi stage những thay đổi đấyNow you can apply the changes which you missed applying when you initially committed that commit. Sau đó bạn chạy lệnh:
(your-branch)$ git commit --amend
Lệnh bảo Git là cần tạo lại commit, nhưng giữ nguyên thông điệp commit. which tells Git to recreate the commit, but to leave the commit message unedited. Thế là xong phần khó nhất. Cuối cùng là chạy lệnh:
(your-branch)$ git rebase --continue
Lệnh trên sẽ giải quyết phần còn lại.
$ git add -u
# Để nâng các file dạng .txt
$ git add -u *.txt
# Để nang các file trong thu mục src
$ git add -u src/
(my-branch*)$ git commit --amend
Nếu bạn đã biết bạn không muốn thay đổi thông điệp commit, bạn có thể yêu cầu git sử dụng lại commit message:
(my-branch*)$ git commit --amend -C HEAD
Thông thường, nếu bạn muốn stage một phần của một file, bạn chạy lệnh này:
$ git add --patch filename.x
Bạn có thể dùng -p
thay --patch
cho ngắn. Lệnh này sẽ mở chế độ interactive. Bạn có thể cho thêm s
để cắt commit - tuy nhiên, nếu là file mới, bạn sẽ không có lựa chọn này. Để thêm một file mới, làm như sau:
$ git add -N filename.x
Sau đó, bạn sẽ cần sử dụng e
để thủ công thêm dòng. Chạy lệnh git diff --cached
hoặc
git diff --staged
sẽ cho bạn thấy những dòng bạn đã stage so với những dòng vẫn lưu ở local.
git add
sẽ thêm toàn bộ file vào một commit. git add -p
sẽ cho vào chế độ tương tác để chọn những thay đổi bạn muốn thêm vào.
git reset -p
sẽ mở chế độ patch và hộp thoại để reset. Việc này sẽ giống như với lệnh git add -p
, ngoại trừ là việc chọn "yes" sẽ đưa thay đổi khỏi stage, loại trừ nó khỏi commit tiếp đến.
Phần lớn thời gian, bạn nên hạ tất cả các file đã trên stage và chọn lại những file bạn muốn commit.Nhưng giả sử bạn muốn thay các thay đổi lên và hạ stage, bạn có thể tạo một commit tạm thời, nâng lên stage các thay đổi, rồi stash (cất) nó. Sau đó, reset cái commit tạm thời rồi pop cái stage bạn vừa cất.
$ git commit -m "WIP"
$ git add . # "." sẽ thêm tất cả file chưa theo dõi
$ git stash
$ git reset HEAD^
$ git stash pop --index 0
GHI CHÚ 1: Lý do để dùng pop
là để giữ nguyên các thay đổi nhất có thể.
GHI CHÚ 2: Các file đã nâng lên stage sẽ bị hạ nếu không có thêm cờ --index
. (Link explains why.)
Đôi khi chúng tôi có một hoặc nhiều tệp vô tình bị stage và những tệp này chưa được commit trước đó. Để loại bỏ chúng:
$ git reset -- <filename>
Lệnh trên dẫn đến việc unstage tệp và làm cho nó không bị track.
Nếu bạn muốn bỏ tất cả các thay đổi đã lên hoặc chưa lên stage tại local của bạn, bạn có thể làm như sau:
(my-branch)$ git reset --hard
# hoặc
(main)$ git checkout -f
Lệnh sau sẽ hạ khỏi stage tất cả thay đổi bạn đã cho lên stage với git add
:
$ git reset
Lệnh sau sẽ đảo ngược tất cả các thay đổi chưa commit tại local (nên chạy tại thư mục gốc repo):
$ git checkout .
Bạn cũng có thể đảo ngược các thay đổi chưa commit cho một file hoặc một thư mục cụ thể:
$ git checkout [thư_mục|file.txt]
Tuy nhiên, một cách khác để đảo ngược tất cả các thay đổi chưa commit (dài hơn để nhập, nhưng hoạt động từ bất kỳ thư mục con nào):
$ git reset --hard HEAD
Lệnh trên sẽ xoá tất cả các file chưa được theo dõi(untracked) tại local, do đó, chỉ các file đã được theo dõi bởi git (tracked) còn tồn:
$ git clean -fd
Thêm cờ -x
để xoá tất cả các file đã ignore.
Khi bạn muốn loại bỏ một số, nhưng không phải tất cả, các thay đổi trong bản sao làm việc của bạn.
Checkout các thay đổi không mong muốn, giữ các thay đổi tốt.
$ git checkout -p
# Trả lời y đối với những thay đổi bạn không muốn giữ
Một cách khác thì sử dụng stash
(cất). Cất tất cả các thay đổi tốt, reset bản sao làm việc và apply lại các thay đổi tốt.
$ git stash -p
# Chọn những thay đổi bạn muốn giữ
$ git reset --hard
$ git stash pop
Ngoài ra, còn cách cất những thay đổi không mong muốn của bạn và sau đó drop stash.
$ git stash -p
# Chọn những thay đổi bạn không muốn giữ
$ git stash drop
Khi bạn muốn loại bỏ một file cụ thể trong bản sao làm việc của bạn.
$ git checkout FileCủaTôi
Ngoài ra, để loại bỏ nhiều file trong bản sao làm việc của bạn, hãy liệt kê tất cả chúng.
$ git checkout FileThứNhất FileThứHai
Khi bạn muốn loại bỏ tất cả các thay đổi chưa commit mà chưa stage tại local
$ git checkout .
Khi bạn muốn loại bỏ tất cả các file chưa được theo dõi
$ git clean -f
Liệt kê các nhanh tại local
$ git branch
Liệt kê cách nhánh trên remote
$ git branch -r
Liệt kê tất cả các nhánh (cả local và remote)
$ git branch -a
$ git checkout -b <nhánh> <SHA1_Của_COMMIT>
Đây là một cơ hội khác để dùng git reflog
để xem HEAD đã trỏ ở đâu trước khi pull sai.
(main)$ git reflog
ab7555f HEAD@{0}: pull origin nhánh-sai: Fast-forward
c5bc55a HEAD@{1}: checkout: checkout message goes here
Chỉ cần reset nhánh của bạn về commit mong muốn:
$ git reset --hard c5bc55a
Xong.
Kiểm tra rằng bạn chưa push các thay đổi của mình đến server.
git status
sẽ hiển thị số lượng các commit bạn có hơn origin:
(my-branch)$ git status
# On branch my-branch
# Your branch is ahead of 'origin/my-branch' by 2 commits.
# (use "git push" to publish your local commits)
#
Một cách để reset về origin (để có nhánh giống như trên remote) là chạy lệnh:
(my-branch)$ git reset --hard origin/my-branch
$ git checkout -b my-branch
$ git stash
$ git checkout my-branch
$ git stash pop
Tạo nhánh mới trong khi giữ main:
(main)$ git branch my-branch
Reset nhánh main đến commit trước đó:
(main)$ git reset --hard HEAD^
HEAD^
là viết tắt của HEAD^1
. Đây là viết tắt của parent thứ nhất HEAD
, tương tự HEAD^2
là viết tắt của parent thứ hai của commit (merge có thể có 2 parent).
Chú ý rằng HEAD^2
không giống như HEAD~2
(xem link để thêm thông tin).
Ngoài ra, nếu bạn không muốn sử dụng HEAD^
, tìm mã hash của commit mà bạn muốn main trỏ về(git log
sẽ giúp bạn). Sau đó reset về mã hash đấy. git push
sẽ đảm bảo rằng thay đổi này sẽ hiện trên remote của bạn.
Ví dụ, nếu hash của commit mà nhánh main của bạn đáng ra là a13b85e
:
(main)$ git reset --hard a13b85e
HEAD is now at a13b85e
Checkout một nhánh mới để tiếp tục làm việc:
(main)$ git checkout my-branch
Giả sử bạn có một cột mũi làm việc (xem Ghi Chú), với hàng trăm thay đổi. Mọi thứ đang hoạt động. Bây giờ, bạn commit vào một nhánh khác để lưu những thay đổi đó:
(solution)$ git add -A && git commit -m "Cho tất cả các thay đổi trong cột mũi làm việc này vào một commit to."
Khi bạn muốn đặt nó vào một nhánh (có thể là feature, có thể develop
), bạn quan tâm đến việc giữ toàn bộ các file. Bạn muốn chia commit lớn của bạn thành những cái nhỏ hơn.
Giả sử bạn có:
- nhánh
solution
, với giáp pháp bạn phát triển với cột mũi làm việc của bạn. Hơndevelop
một commit. - nhánh
develop
, nơi bạn muốn thêm các thay đổi của bạn.
Bạn có thể giải quyết bằng cách mang nội dung thay đổi sang nhánh của bạn:
(develop)$ git checkout solution -- file1.txt
Lệnh trên sẽ lấy nội dung của tập tin đó trong nhánh solution
đến nhánh develop
của bạn:
# On branch develop
# Your branch is up-to-date with 'origin/develop'.
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: file1.txt
Sau đó, commit như bình thường.
Lưu ý: Cột mũi giải pháp được phát triển để phân tích hoặc giải quyết vấn đề. Các giải pháp này được sử dụng để ước tính và loại bỏ sau khi mọi người hiểu rõ vấn đề. ~ Wikipedia.
Giả sử bạn đang ở trên nhánh main của bạn. Chạy git log
, bạn thấy bạn đã thực hiện 2 commit:
(main)$ git log
commit e3851e817c451cc36f2e6f3049db528415e3c114
Author: Alex Lee <alexlee@example.com>
Date: Tue Jul 22 15:39:27 2014 -0400
Bug #21 - Added CSRF protection
commit 5ea51731d150f7ddc4a365437931cd8be3bf3131
Author: Alex Lee <alexlee@example.com>
Date: Tue Jul 22 15:39:12 2014 -0400
Bug #14 - Fixed spacing on title
commit a13b85e984171c6e2a1729bb061994525f626d14
Author: Aki Rose <akirose@example.com>
Date: Tue Jul 21 01:12:48 2014 -0400
First commit
Hãy lưu ý các hash commit của chúng ta cho mỗi bug (lỗi) (e3851e8
cho #21, 5ea5173
cho #14).
Trước tiên, hãy đặt lại nhánh main của chúng ta về commit chính xác (a13b85e
):
(main)$ git reset --hard a13b85e
HEAD is now at a13b85e
Bây giờ, chúng ta có thể tạo ra một nhánh mới cho lỗi #21 của chúng ta:
(main)$ git checkout -b 21
(21)$
Bây giờ, hãy cherry-pick commit cho bug #21 trên đầu của nhánh. Nói tóm lại là chúng ta sẽ áp commit đó, và chỉ commit đó, trực tiếp vào đầu của nhánh.
(21)$ git cherry-pick e3851e8
Tại thời điểm này, có khả năng có thể có xung đột hợp (merge conflicts). Hãy xem phần Có một vài xung đột trong phần interactive rebasing ở trên để làm thế nào giải quyết xung đột hợp.
Bây giờ chúng ta hãy tạo một nhánh mới cho bug # 14, cũng dựa trên nhánh main:
(21)$ git checkout main
(main)$ git checkout -b 14
(14)$
Và cuối cùng, hãy cherry-pick commit cho bug #14:
(14)$ git cherry-pick 5ea5173
Khi bạn kết hợp (merge) một pull request trên GitHub, nó sẽ cho bạn lựa chọn để xóa nhánh đã được kết hợp trong fork của bạn. Nếu bạn không có kế hoạch tiếp tục làm việc trên nhánh đấy, mọi thứ sẽ sạch hơn nếu xóa các bản sao local của nhánh, do đó bạn không tồn đọng một cách lộn xộn tại bản sao làm việc của bạn với các nhánh cũ.
$ git fetch -p upstream
upstream` là remote bạn muốn fetch (gọi) về.
Nếu bạn thường xuyên push lên remote, bạn sẽ an toàn phần lớn thời gian. Nhưng đôi khi bạn có thể sẽ xóa các nhánh của bạn. Giả sử chúng ta tạo một nhánh và tạo một tệp mới:
(main)$ git checkout -b my-branch
(my-branch)$ git branch
(my-branch)$ touch foo.txt
(my-branch)$ ls
README.md foo.txt
Hãy thêm nó và rồi tạo commit.
(my-branch)$ git add .
(my-branch)$ git commit -m 'foo.txt added'
(my-branch)$ foo.txt added
1 files changed, 1 insertions(+)
create mode 100644 foo.txt
(my-branch)$ git log
commit 4e3cd85a670ced7cc17a2b5d8d3d809ac88d5012
Author: siemiatj <siemiatj@example.com>
Date: Wed Jul 30 00:34:10 2014 +0200
foo.txt added
commit 69204cdf0acbab201619d95ad8295928e7f411d5
Author: Kate Hudson <katehudson@example.com>
Date: Tue Jul 29 13:14:46 2014 -0400
Fixes #6: Force pushing after amending commits
Bây giờ chúng ta chuyển lại về main và 'vô tình' xóa nhánh của chúng ta
(my-branch)$ git checkout main
Switched to branch 'main'
Your branch is up-to-date with 'origin/main'.
(main)$ git branch -D my-branch
Deleted branch my-branch (was 4e3cd85).
(main)$ echo ôi không,tôi delete nhánh tôi!
ôi không,tôi delete nhánh tôi!
Tại thời điểm này, bạn nên làm quen với 'reflog', một logger (ký sử) được nâng cấp. Nó lưu trữ lịch sử của tất cả các hành động trong repo.
(main)$ git reflog
69204cd HEAD@{0}: checkout: moving from my-branch to main
4e3cd85 HEAD@{1}: commit: foo.txt added
69204cd HEAD@{2}: checkout: moving from main to my-branch
Như bạn có thể thấy chúng ta có số hash của commit từ nhánh đã xóa của chúng ta. Hãy xem liệu chúng ta có thể khôi phục nhánh đã xóa của chúng ta hay không.
(main)$ git checkout -b my-branch-help
Switched to a new branch 'my-branch-help'
(my-branch-help)$ git reset --hard 4e3cd85
HEAD is now at 4e3cd85 foo.txt added
(my-branch-help)$ ls
README.md foo.txt
Và đấy! Chúng ta đã phục hồi lại được file bị xóa của chúng ta. git reflog
cũng hữu ích khi rebase tạo sai lầm lớn.
Để xoá một nhánh tại remote:
(main)$ git push origin --delete my-branch
Bạn cũng có thể chạy :
(main)$ git push origin :my-branch
Để xoá nhánh tại local:
(main)$ git branch -d my-branch
Để xoá một nhánh local chưa được merge với nhánh hiện tại hoặc trên upstream (luồng trước):
(main)$ git branch -D my-branch
Giả sử bạn muốn xoá tất cả các nhánh bắt đầu với fix/
:
(main)$ git branch | grep 'fix/' | xargs git branch -d
Để đổi tên nhánh local hiện tại:
(main)$ git branch -m tên-mới
Để đổi tên nhánh local khác:
(main)$ git branch -m tên-cũ tên-mới
Để vừa xóa nhánh tên-cũ
tại remote và push nhánh tên-mới
từ local:
(main)$ git push origin :tên_cũ tên_mới
Đầu tiên, fetch tất cả nhánh từ remote:
(main)$ git fetch --all
Giả sử bạn muốn checkout sang daves
từ remote.
(main)$ git checkout --track origin/daves
Branch daves set up to track remote branch daves from origin.
Switched to a new branch 'daves'
(--track
là viết tắt của git checkout -b [branch] [remotename]/[branch]
)
Lệnh này sẽ cung cấp cho bạn một bản sao tại local của nhánh daves
và mọi cập nhật đã được push cũng sẽ được hiển thị từ remote.
$ git push <remote> HEAD
Nếu bạn cũng muốn đặt nhánh remote là upstream cho nhánh hiện tại, sử dụng:
$ git push -u <remote> HEAD
Với chế độ upstream
và simple
(mặc định trong Git 2.0) của cấu hình push.default
, lệnh sau sẽ push nhánh hiện tại lên nhánh remote được đăng ký trước đó với -u
:
$ git push
Các hành vi của các chế độ khác của git push
được mô tả trong doc cho push.default
.
Bạn có thể thiết lập một nhánh remote làm upstream cho nhánh local hiện tại bằng cách chạy lệnh:
$ git branch --set-upstream-to [remotename]/[branch]
# hoặc, dùng ký tắt:
$ git branch -u [remotename]/[branch]
Để thiết lập nhánh upstream remote cho nhánh local khác:
$ git branch -u [remotename]/[branch] [local-branch]
Bằng cách kiểm tra các nhánh remote của bạn, bạn có thể thấy nhánh remote nào mà HEAD của bạn đang theo dõi. Trong một số trường hợp, có thể đấy không phải là nhánh mong muốn.
$ git branch -r
origin/HEAD -> origin/gh-pages
origin/main
Để thay đổi origin/HEAD
sang theo dõi origin/main
, bạn có thể chạy lệnh này:
$ git remote set-head origin --auto
origin/HEAD set to main
Bạn đã thực hiện các thay đổi chưa được commit và nhận ra bạn đang ở sai nhánh. Stash (cất) các thay đổi và apply (áp dụng) chúng vào nhánh bạn muốn:
(wrong_branch)$ git stash
(wrong_branch)$ git checkout nhánh_đúng
(correct_branch)$ git stash apply
Bạn đã tạo rất nhiều commit trên một nhành và bây giờ bạn muốn tách nhánh ra thành hai, một nhánh kết thúc với một commit cũ, và một nhánh với tất cả các thay đổi.
Dùng git log
để tìm commit bạn muốn làm mốc để tách. Sau đó chạy lệnh như sau:
(original_branch)$ git checkout -b new_branch
(new_branch)$ git checkout original_branch
(original_branch)$ git reset --hard <số sha1 commit để tách>
Nếu bạn trước đó đã push nhánh gốc lên remote, bạn sẽ cần phải push ép (force push). Để thêm thông tin xem Stack Overlflow.
Bạn có thể đã merge hoặc rebase nhánh hiện tại của bạn với một nhánh sai hoặc bạn không thể tìm ra cách hoàn thành quá trình rebase/merge. Git lưu con trỏ original HEAD trong một variable (biến) được gọi là ORIG_HEAD trước khi chạy các hành động nguy hiểm, vì vậy bạn có thể dễ dàng khôi phục lại trạng thái trước khi rebase/merge.
(my-branch)$ git reset --hard ORIG_HEAD
Thật không may, bạn bắt buộc phải push ép, nếu bạn muốn những thay đổi đó được phản ánh trên nhánh remote. Điều này là do bạn đã thay đổi lịch sử. Nhánh remote sẽ không chấp nhận thay đổi trừ khi bạn push ép. Đây là một trong những lý do chính khiến nhiều người sử dụng quy trình làm việc trên merge, thay vì quy trình làm việc trên rebasing - các nhóm lớn có thể gặp rắc rối khi developer push ép. Nên sử dụng rebase một cách thận trọng. Một cách an toàn hơn để sử dụng rebase không là không phản ánh các thay đổi của bạn trên nhánh remote và thay vào đó thực hiện các thao tác sau:
(main)$ git checkout my-branch
(my-branch)$ git rebase -i main
(my-branch)$ git checkout main
(main)$ git merge --ff-only my-branch
Để biết thêm hãy xem chủ đề này trên SO.
Giả sử bạn đang làm việc trong một nhánh có / sẽ trở thành một pull-request cho main
. Trong trường hợp đơn giản nhất khi bạn chỉ muốn là kết hợp tất cả các commit thành một commit và bạn không quan tâm đến timestamp (mốc thời gian) của commit, bạn có thể reset và commit lại. Đảm bảo rằng nhánh main được cập nhật và tất cả các thay đổi của bạn được commit, sau đó:
(my-branch)$ git reset --soft main
(my-branch)$ git commit -am "New awesome feature"
Nếu bạn muốn kiểm soát được nhiều hơn và cũng để bảo vệ timestamp, bạn cần phải làm một vài thứ được gọi là interactive rebase:
(my-branch)$ git rebase -i main
Nếu bạn không làm việc với một nhánh khác, bạn phải rebase tương đối so với HEAD
của bạn. Nếu bạn muốn gộp 2 commit cuối, bạn sẽ phải rebase tới HEAD~2
. Cho 3 commit cuối, HEAD~3
,...
(main)$ git rebase -i HEAD~2
Sau khi bạn chạy lệnh interactive rebase, bạn sẽ thấy trông giống thế này trong trình soạn thảo (text editor) của bạn:
pick a9c8a1d Some refactoring
pick 01b2fd8 New awesome feature
pick b729ad5 fixup
pick e3851e8 another fix
# Rebase 8074d12..b729ad5 onto 8074d12
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
Tất cả các dòng bắt đầu bằng một #
là các comment (chú thích), chúng sẽ không ảnh hưởng đến rebase của bạn.
Sau đó bạn thay thể lệnh pick
với những bất cứ lệnh nào trong danh sách trên và bạn cũng có thể loại bỏ các commit khỏi rebase bằng cách xoá các dòng tương ứng.
Ví dụ, nếu bạn muốnn dữ nguyên commit cũ nhất(đầu tiên) và kết hợp tất cả commit sau với commit cũ thứ hai, bạn nên chỉnh sửa chữ cái bên cạnh mỗi commit ngoại trừ chữ cái đầu tiên và chữ cái thứ hai với f
:
pick a9c8a1d Some refactoring
pick 01b2fd8 New awesome feature
f b729ad5 fixup
f e3851e8 another fix
Nếu bạn muốn kết hợp tất cả các commit và đổi tên commit, bạn nên thêm một chữ cái r
bên cạnh commit thứ 2 hoặc đơn giản sử dụng s
thay vì f
:
pick a9c8a1d Some refactoring
pick 01b2fd8 New awesome feature
s b729ad5 fixup
s e3851e8 another fix
Bạn có thể đổi tên commit trong đoạn hội thoại sẽ bật lên.
Newer, awesomer features
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# rebase in progress; onto 8074d12
# You are currently editing a commit while rebasing branch 'main' on '8074d12'.
#
# Changes to be committed:
# modified: README.md
#
Nếu mọi thứ thành công, bạn sẽ thấy giống như thế này:
(main)$ Successfully rebased and updated refs/heads/main.
--no-commit
thực hiện merge nhưng giả vờ kết hợp không thành công và không tự động tạo commit, cho phép người dùng có cơ hội kiểm tra và chỉnh thêm kết quả merge trước khi commit. no-ff
duy trì bằng chứng rằng một nhánh tính năng đã từng tồn tại, giữ lịch sử dự án nhất quán.
(main)$ git merge --no-ff --no-commit my-branch
(main)$ git merge --squash my-branch
Đôi khi bạn có một số commit trong khi công-việc-đang-tiến-hành và bạn muốn kết hợp thành một trước khi bạn đẩy lên upstream. Bạn không muốn vô tình kết hợp bất kỳ commit nào đã được push lên upstream vì người khác có thể đã thực hiện các commit tham chiếu chúng.
(main)$ git rebase -i @{u}
Lệnh này sẽ thực hành một interactive rebase mà chỉ liệt kê các commit bạn chưa push, vì vậy mọi thứ sẽ an toàn để sắp xếp lại / sửa chữa / squash (gộp) bất cứ gì trong danh sách
Đôi khi việc merge có thể gây ra sự cố trong một số file nhất định, trong những trường hợp đó, chúng ta có thể sử dụng cờ abort
để hủy bỏ quá trình giải quyết xung đột hiện tại và cố gắng xây dựng lại trạng thái trước merge.
(my-branch)$ git merge --abort
Lệnh này có sẵn từ phiên bản Git >= 1.7.4
Giả sử tôi có một nhánh main, một nhánh feature-1 tách từ main và một nhánh feature-2 tách từ feature-1. Nếu tôi thực hiện commit đối với feature-1, thì commit của feature-2 không còn chính xác nữa (gốc nên là đầu của feature-1, vì chúng ta đã tách nhánh từ nó). Chúng ta có thể sửa vấn đề này với git rebase --onto
.
(feature-2)$ git rebase --onto feature-1 <commit đầu tiên trong nhánh feature-2 mà bạn không muốn mang theo> feature-2
Lệnh này giúp trong các trường hợp khó nơi bạn có thể có một feature được xây dựng trên một feature khác chưa được merge, hoặc một bugfix (vá lỗi) trên nhánh feature-1 cần được phản ánh trong nhánh feature-2 của bạn.
Để kiểm tra tất cả commit trên một nhánh đã được merge vào nhánh khác, bạn nên diff (khác biệt) giữa các head (hoặc các commit) của các nhánh:
(main)$ git log --graph --left-right --cherry-pick --oneline HEAD...feature/120-on-scroll
Lệnh này sẽ cho bạn biết nếu bất kỳ commit ở trong một nhánh nhưng không trong nhánh kia, và sẽ cung cấp cho bạn một danh sách của bất kỳ tệp không chia sẽ giữa các nhánh. Một lựa chọn khác là chạy lệnh:
(main)$ git log main ^feature/120-on-scroll --no-merges
Nếu bạn thấy như sau:
noop
Điều này có nghĩa bạn đang cố rebase lại một nhánh đang có commit giống hệt hoặc là ở trước nhánh hiện tại. Bạn có thể thử:
- đảm bảo nhánh main của bạn ở đúng chỗ
- rebase với
HEAD~2
hoặc cũ hơn
Nếu bạn không thể hoàn tất thành công rebase, bạn có thể phải giải quyết xung đột.
Đầu tiên chạy git status
để xem tệp nào có xung đột:
(my-branch)$ git status
On branch my-branch
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
both modified: README.md
Trong ví dụ đó, README.md
có xung đột. Mở tệp đó và tìm những dòng trông như sau:
<<<<<<< HEAD
some code
=========
some code
>>>>>>> new-commit
Bạn sẽ cần phải giải quyết sự khác biệt giữa code đã được thêm vào với commit mới của bạn (trong ví dụ, mọi thứ từ dòng ở giữa cho đến new-commit
) và HEAD
của bạn.
Nếu bạn muốn giữ phiên bản code của một nhánh, bạn có thể sử dụng --ours
hoặc --theirs
:
(main*)$ git checkout --ours README.md
- Khi đang merge, sử dụng
--ours
để giữ các thay đổi từ nhánh local, hoặc--theirs
để giữ các thay đổi từ nhánh khác. - Khi đang rebase, sử dụng
--theirs
để giữ các thay đổi từ nhánh local, hoặc--ours
để giữ các thay đổi từ nhánh khác. Để hiểu giải thích về sự hoán đổi này, hãy xem ghi chú này trong tài liệu Git.
Nếu việc merge phức tạp hơn, bạn có thể sử dụng trình chỉnh sửa khác biệt trực quan (visual diff editor):
(main*)$ git mergetool -t opendiff
Sau khi bạn đã giải quyết tất cả xung đột và đã kiểm tra code của mình, git add
các file đã thay đổi và sau đó tiếp tục rebase với git rebase --continue
(my-branch)$ git add README.md
(my-branch)$ git rebase --continue
Nếu sau khi giải quyết tất cả các xung đột bạn kết thúc với một cây giống hệt với cái trước khi thực hiện, bạn cần git rebase --skip
.
Nếu bất kỳ lúc nào bạn muốn dừng toàn bộ quá trình rebase và quay trở lại trạng thái ban đầu nhánh của bạn, bạn có thể làm như thế này:
(my-branch)$ git rebase --abort
Để stash tất cả các chỉnh sửa trong thư mục làm việc
$ git stash
Nếu bạn cũng muốn stash các file chưa được theo dõi, sử dụng cờ -u
.
$ git stash -u
Để stash chỉ một file từ thư mục làm việc
$ git stash push working-directory-path/filename.ext
Để stash nhiều file từ thư mục làm việc
$ git stash push working-directory-path/filename1.ext working-directory-path/filename2.ext
$ git stash save <message>
hoặc
$ git stash push -m <message>
Đầu tiên kiểm tra danh sách các stash với message bằng lệnh
$ git stash list
Sau đó apply (áp dụng) một stash cụ thể từ danh sách với
$ git stash apply "stash@{n}"
Ở đây, 'n' cho biết vị trí của stash trong stack. Stash trên cùng sẽ là vị trí 0.
Hơn nữa, cũng có thể chỉ stash dựa vào mốc thời gian
$ git stash apply "stash@{2.hours.ago}"
Bạn có thể tạo một stash commit
, rồi dùng lệnh git stash store
.
$ git stash create
$ git stash store -m <message> CREATED_SHA1
Để tìm một chuỗi ký tự được giới thiệu với commit, bạn có thể sử dụng lệnh như sau:
$ git log -S "chuỗi ký tự để tìm"
Các cờ thường dùng:
-
--source
có nghĩa là hiển thị tên ref được đưa ra trên dòng lệnh mà mỗi lần commit đã đạt tới. -
--all
nghĩa là bắt đầu từ mọi nhánh. -
--reverse
in theo thứ tự ngược lại, có nghĩa là hiển thị commit đầu tiên đã thực hiện thay đổi.
Để tìm tất cả commit từ tác giả hoặc người commit bạn có thể sử dụng:
$ git log --author=<tên hoặc email>
$ git log --committer=<tên hoặc email>
Hãy nhớ rằng tác giả và người commit không giống nhau. --author
là người ban đầu viết code; mặt khác, --committer
, là người đã commit code thay mặt tác giả gốc.
Để tìm tất cả các commit chưa một file cụ thể bạn có thể sử dụng:
$ git log -- <path to file>
Bạn thường sẽ chỉ định một đường dẫn (filepath) chính xác, nhưng bạn cũng có thể sử dụng các ký tự đại diện bất kỳ cho đường dẫn và tên tệp:
$ git log -- **/*.js
Trong khi sử dụng ký tự đại diện bất kỳ, sẽ hữu ích hơn khi thêm --name-status
để xem danh sách các tệp trong commit:
$ git log --name-status -- **/*.js
Để truy tìm lịch sử tiến hóa của một function là dùng lệnh:
$ git log -L :TênFunction:FilePath
Ghi chú là bạn có thể xây dựng lệnh trên thêm với các cờ git log
khác, giống như phạm vi sửa đổi và hạn mức commit.
Để tìm tất cả các tag có chứa một commit cụ thể
$ git tag --contains <commitid>
$ git clone --recursive git://github.com/foo/bar.git
Nếu đã clone:
$ git submodule update --init --recursive
Tạo một submodule là khá rõ rành, nhưng xóa chúng ít không như vậy. Các lệnh bạn cần là:
$ git submodule deinit submodulename
$ git rm submodulename
$ git rm --cached submodulename
$ rm -rf .git/modules/submodulename
$ git checkout <nhánh-có-tệp-bạn-muốn> -- <thư mục hoặc tên file>
Đầu tiên tìm commit cuối cùng ma file vẫn tồn tại:
$ git rev-list -n 1 HEAD -- filename
Sau đó checkout file:
git checkout id-của-commit-delete-trên^ -- filename
$ git tag -d <tag_name>
$ git push <remote> :refs/tags/<tag_name>
Nếu bạn muốn khôi phục tag đã bị xóa, bạn có thể làm được vậy với các bước sau: Trước tiên, bạn cần phải tìm tag không thể truy cập
$ git fsck --unreachable | grep tag
Ghi lại mã hash của tag. Sau đó, khôi phục tag đã xóa theo cách sử dụng git update-ref
:
$ git update-ref refs/tags/<tag_name> <hash>
Tag của bạn bây giờ đã được khôi phục.
Nếu ai đó đã gửi cho bạn một pull request trên GitHub, nhưng sau đó đã xoá chúng trên fork gốc, bạn sẽ không thể clone repository của họ hoặc sử dụng git am
vì url của .diff, .patch không dùng được. Nhưng bạn có thể checkout chính PR bằng cách sử dụng GitHub's special refs. Để fetch nội dung của PR#1 vào một nhánh được gọi là pr_1, chạy:
$ git fetch origin refs/pull/1/head:pr_1
From github.com:foo/bar
* [new ref] refs/pull/1/head -> pr_1
$ git archive --format zip --output /full/path/to/zipfile.zip main
Nếu có một tag trên một remote repository mà có tên giống với một nhánh bạn sẽ gặp phải lỗi khi cố push nhanh với một lệnh $ git push <remote> <branch>
bình thường.
$ git push origin <branch>
error: dst refspec same matches more than one.
error: failed to push some refs to '<git server>'
Sửa lỗi này bằng cách chỉ định bạn muốn đẩy tham chiếu của head.
$ git push origin refs/heads/<branch-name>
Nếu bạn muốn đẩy một tag vào một repository tại remote có cùng tên với một nhánh, bạn có thể sử dụng một lệnh tương tự.
$ git push origin refs/tags/<tag-name>
(main)$ git mv --force myfile MyFile
(main)$ git fetch --all
(main)$ git reset --hard origin/main
(main)$ git rm --cached log.txt
Giả sử mã hash của commit bạn muốn là c5f567:
(main)$ git checkout c5f567 -- fileSố1/Để/PhụcHồi fileSố2/Để/PhụcHồi
Nếu bạn muốn đảo ngược các thay đổi được thực hiện chỉ 1 commit trước c5f567, đưa số hash commit như c5f567~1:
(main)$ git checkout c5f567~1 -- file1/to/restore file2/to/restore
Giả sử bạn muốn so sánh commit cuối cùng với tệp từ commit c5f567:
$ git diff HEAD:path_to_file/file c5f567:path_to_file/file
# hoặc
$ git diff HEAD c5f567 -- path_to_file/file
Cũng giống khi so sánh nhánh nhánh:
$ git diff main:path_to_file/file staging:path_to_file/file
# hoặc
$ git diff main staging -- path_to_file/file
Các bước sau khá hợp cho các mẫu cấu hình hoặc các tệp yêu cầu thêm thông tin đăng nhập tại local mà không nên commit
$ git update-index --assume-unchanged file-to-ignore
Lưu ý rằng điều này không xóa tệp khỏi kiểm soát source - nó chỉ bị bỏ qua tại local. Để hoán đổi thao tác này và yêu cầu Git lại chú ý các thay đổi, lệnh sau sẽ xóa cờ bỏ qua (ignore flag):
$ git update-index --no-assume-unchanged file-to-stop-ignoring
Lệnh git-bisect sử dụng tìm nhị phân để tìm commit đã giớ thiệu lỗi.
Giả sử bạn đang ở nhánh main
và bạn muốn tìm commit đã làm hỏng cái gì đó. Bạn bắt đầu bisect với:
$ git bisect start
Sau đó bạn đề rõ commit nào tồi và commit nào biết là tốt. Giả sử bạn biết phiên bản hiện tại là tồi, và v1.1.1
là tốt:
$ git bisect bad
$ git bisect good v1.1.1
Bây giờ git-bisect
chọn commit ở giữa khoảng cách bạn lựa chọn, checkout cái commit đấy, và hỏi bạn là commit này tồi hay tốt. Bạn sẽ thấy giống như thế này:
$ Bisecting: 5 revision left to test after this (roughly 5 step)
$ [c44abbbee29cb93d8499283101fe7c8d9d97f0fe] Commit message
$ (c44abbb)$
Bạn kiểm tra commit xem tốt hay tồi. Nếu tốt:
$ (c44abbb)$ git bisect good
và git-bisect
sẽ chọn một commit khác trong phạm vi của bạn. Quá trình này sẽ tiếp tục lặp lại cho đến khi không còn sửa đổi cần kiểm tra, và lệnh sẽ cuối cùng in ra mô tả của commit tồi đầu tiên
Trên OS X và Linux, file cấu hình git được lưu trong ~/.gitconfig
. Tôi đã thêm một số bí danh mẫu mà tôi sử dụng làm shortcut (và một số lỗi chính tả phổ biến của tôi) trong phần [alias]
được hiển thị như dưới đây:
[alias]
a = add
amend = commit --amend
c = commit
ca = commit --amend
ci = commit -a
co = checkout
d = diff
dc = diff --changed
ds = diff --staged
extend = commit --amend -C HEAD
f = fetch
loll = log --graph --decorate --pretty=oneline --abbrev-commit
m = merge
one = log --pretty=oneline
outstanding = rebase -i @{u}
reword = commit --amend --only
s = status
unpushed = log @{u}
wc = whatchanged
wip = rebase -i @{u}
zap = fetch -p
day = log --reverse --no-merges --branches=* --date=local --since=midnight --author=\"$(git config --get user.name)\"
delete-merged-branches = "!f() { git checkout --quiet main && git branch --merged | grep --invert-match '\\*' | xargs -n 1 git branch --delete; git checkout --quiet @{-1}; }; f"
Bạn không thể! Git không hỗ trợ điều này, nhưng có một hack. Bạn có thể tạo tệp .gitignore trong thư mục với các nội dung sau:
# Bỏ qua tất cả mọi thứ trong repository
*
# Ngoại trừ file này
!.gitignore
Một quy ước chung khác là tạo một tệp trống trong thư mục có tên .gitkeep.
$ mkdir mydir
$ touch mydir/.gitkeep
Bạn cũng có thể đặt tên tệp là .keep, trong trường hợp đó dòng thứ hai ở trên sẽ touch mydir/.keep
Bạn có thể có một repository yêu cầu xác thực (authentication). Trong trường hợp này bạn có thể cache một username và password vì vậy bạn không phải nhập nó vào mỗi lần push / pull. Phụ tá chứng chỉ(credential.helper) có thể làm điều này cho bạn.
$ git config --global credential.helper cache
# Đặt git dùng bộ nhớ đệm chứng chỉ
$ git config --global credential.helper 'cache --timeout=3600'
# Đặt bộ nhớ đệm kết thúc sau 1h (cấu hình dùng giây/s)
Để tìm phụ tá chứng chỉ:
$ git help -a | grep credential
# Phô bày các phụ tá chứng chỉ
Bộ nhớ đệm chứng chỉ cho các hệ điều hành (operating system/OS) cụ thể :
$ git config --global credential.helper osxkeychain
# cho OSX
$ git config --global credential.helper manager
# Git for Windows 2.7.3+
$ git config --global credential.helper gnome-keyring
# Ubuntu và các bản phân phối dựa trên GNOME
Các phụ tá chứng chỉ khác có khả năng cao tìm được cho các bản phân phối và hệ điều hành khác.
$ git config core.fileMode false
Nếu bạn muốn đặt hành vi này là hành vi mặc định cho người dùng đã đăng nhập, thì hãy sử dụng:
$ git config --global core.fileMode false
Để cấu hình thông tin người dùng được sử dụng trên tất cả các repository tại local và để đặt tên có thể nhận dạng khi xem lịch sử phiên bản:
$ git config --global user.name "[tên-riêng tên-họ]"
Để đặt địa chỉ email gắn với mỗi mốc lịch sử:
git config --global user.email "[email-có-hiệu-lực]"
Ok, bạn gặp rắc rối lớn - bạn reset
vài thứ, hoặc bạn merge sai nhánh, hoặc bạn push ép (force push) và bây giờ bạn không thể tìm thấy các commit của bạn. Bạn biết, tại một số thời điểm, bạn không có vấn đề và bạn muốn quay trở lại trạng thái bạn đang ở đó.
Đây là tình huống cho git reflog
. reflog
theo dõi bất kỳ thay đổi nào đối với đầu nhánh, ngay cả khi đầu nhánh đó không được tham chiếu bởi nhánh hoặc tag. Về cơ bản, mỗi lần HEAD thay đổi, một mục mới được thêm vào reflog. Thật đáng buồn là cách này chỉ hoạt động tốt đối với các repository ở local, và nó chỉ theo dõi các chuyển động (ví dụ: không thay đổi một tệp không được ghi ở bất kỳ đâu).
(main)$ git reflog
0a2e358 HEAD@{0}: reset: moving to HEAD~2
0254ea7 HEAD@{1}: checkout: moving from 2.2 to main
c10f740 HEAD@{2}: checkout: moving from main to 2.2
Các reflog ở trên cho thấy một checkout từ main đến nhánh 2.2 rồi quay trở lại. Từ đó, có một reset cứng về một commit cũ hơn. Hoạt động mới nhất được thể hiện ở đầu được gắn nhãn HEAD@{0}
.
Nếu nó chỉ ra rằng bạn vô tình di chuyển trở lại, các reflog sẽ chứa commit mà main chỉ đến (0254ea7) trước khi bạn vô tình giảm 2 commit
$ git reset --hard 0254ea7
Sử dụng git reset
để có thể thay đổi main trở về commit trước đó. Cách này cung cấp mạng lưới an toàn trong trường hợp lịch sử vô tình bị thay đổi.
(đã sao chép và chỉnh sửa từ Source).
Một khi bạn thấy thoải mái với các lệnh trên, bạn có thể muốn tạo các phím tắt cho Git Bash. Cách này giúp bạn làm việc nhanh hơn vì chạy các hành vi phức tạp với các lệnh ngắn hơn.
alias sq=squash
function squash() {
git rebase -i HEAD~$1
}
Copy các lệnh này vào .bashrc hoặc .bash_profile của bạn.
Nếu bạn dùng Powershell trên Windows, bạn cũng có thể đặt các bí danh và chức năng tắt. Cho thêm các lệnh này vào prolfe của bạn, đường dẫn được định nghĩa ở biến $profile
. Học thêm với trang About Profiles tại trang tài liệu tham khảo của Microsoft .
Set-Alias sq Squash-Commits
function Squash-Commits {
git rebase -i HEAD~$1
}
- Learn Enough Git to Be Dangerous - Sách của Michael Hartl cho Git từ những điều cơ bản
- Pro Git - Một cuốn sách xuất chúng của Scott Chacon và Ben Straub
- Git Internals - Cuốn sách xuất chúng khác về Git của Scott Chacon
- Sổ tay NASA
- 19 mẹo sử dụng GIT hàng ngày - Một danh sách các mẹo dùng GIT hữu ích.
- Hướng dẫn Git của Atlassian - Sử dụng Git đúng với các hướng dẫn từ cơ bản đến nâng cao.
- Học về nhánh Git - Hướng dẫn phân nhánh / merging / rebasing dựa trên web interactive
- Chở nên vững chắc về Git rebase vs. merge
- Tờ gian lận lệnh và thực hành tốt Git - Một Git cheat sheet trong một bài đăng trên blog với nhiều giải thích hơn
- Git từ trong ra ngoài - Hướng dẫn đi sâu vào Git
- git-workflow - Aaron Meurer viết cách sử dụng Git để đóng góp vào repository mã nguồn mở (open source)
- GitHub as a workflow - Một ý kiến thú vị về sử dụng GitHub như một quy trình làm việc, đặc biệt với các PR trống.
- Githug - Một trò chơi để học thêm về luồng làm việc thường thấy của Git.
- learnGitBranching - Hình dung git có tương tác để thử thách và giáo dục!
- firstaidgit.io - Danh sách được lựa chọn có thể tìm kiếm các câu hỏi thường gặp về Git
- git-extra-commands - Tập hợp các script Git mở rộng hữu ích
- git-extras - Các tiện ích GIT -- Repo tóm tắt, thay thế, số lượng thay đổi, tỷ lệ phần trăm của tác giả và nhiều nữa
- git-fire - git-fire là một plugin cho Git để giúp trong trường hợp khẩn cấp bằng cách thêm tất cả các tệp hiện tại, commit và push vào một nhánh mới (để ngăn xung đột khi merge).
- git-tips - Các mẹo Git nhỏ
- git-town - Hỗ trợ luồng làm việc Git chung, tầm nâng cao! http://www.git-town.com
- GitKraken - Client sang trọng cho Windows, Mac & Linux
- git-cola - Git client khác cho Windows và OS X
- GitUp - Một GUI mới mẻ mà có một số cách rất quan tâm để giải quyết các việc khó chịu của Git
- gitx-dev - Một Git client đồ hoạ khác cho OS X
- Sourcetree - Sự đơn giản nhưng mạnh mẽ cho giao diện Git đẹp và miễn phí cho Windows và Mac.
- Tower - Git client đồ hoạ cho OS X (trả phí)
- tig - Terminal text-mode interface cho Git
- Magit - Interface cho Git thực hiện như một gói Emacs .
- GitExtensions - Một shell extension, một Visual Studio 2010-2015 plugin và một công cụ Git repository độc lập.
- Fork - Một Git client nhanh và thân thiện cho Mac (beta)
- gmaster - Một Git client cho Windows với 3 cách merge, analyze refactors, semantic diff và merge (beta)
- gitk - Một Git client cho Linux để cho phép xem đơn giản cho trạng thái repo.
- SublimeMerge - Client nhanh, mở rộng, cung cấp 3 cách merge, tìm kiếm mạnh mẽ và làm nổi bật cú pháp, đang phát triển tích cực.