You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
git-flight-rules/README_vi.md

98 KiB

Flight rules cho Git

🌍 EnglishEspañolРусский繁體中文简体中文한국어Tiếng ViệtFrançais日本語

"Flight rules" là gì?

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.

Quy chuẩn cho tài liệu này

Để 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.

Join the chat at https://gitter.im/k88hudson/git-flight-rules

Danh mục nội dung generated with DocToc

Repositories (Kho)

Tôi muốn tạo một repository trên local

Để tạo một Git repository tại thư mục đã tồn tại:

(thư-mục-của-tôi) $ git init

Tôi muốn clone một remote repository

Để 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

Tôi để sai remote repository

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.

Tôi muốn thêm sửa code cho repository của người khác

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.

Thêm sửa code với pull requests

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.

Thêm sửa code với các patch (vá)

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

Tôi cần update fork của tôi với những thay đổi mới nhất từ repository nguyên bản

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

Chỉnh sửa Commit

Tôi vừa commit cái gì?

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

Tôi đã viết sai vài thứ trong message (thông điệp) của commit

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.

Tôi đã commit với cấu hình tên và email sai

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.

Tôi muốn xoá một file từ commit trước

Để 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.

Tôi muốn xoá hoặc loại bỏ commit mới nhất

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.

Xoá/loại bỏ bất kỳ commit nào

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ỏ.

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

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.

Tôi đã vô tình thực hiện hard reset và tôi muốn các thay đổi của tôi.

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.

Tôi vô tình commit và đẩy lên một merge

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ế.

Tôi vô tình commit và đẩy các file chứa dữ liệu nhảy cảm

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.

Tôi muốn xóa file to quá để chưa bao giờ xuất hiện trong lịch sử repository

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.

Cách khuyến khích: Sử dụng dịch vụ bên thứ ba bfg

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.

Cách có sẵn: Sử dụng git-filter-branch

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.

Bước cuối: Đẩy lịch sử đã thay đổi của repository

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.

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

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.

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

$ git add -u

Chỉ nâng một phần các file đang theo dõi

# Để 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/

Tôi cần cho thêm các thay đổi đang trong stage vào commit trước

(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

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

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.

Tôi muốn thêm các thay đổi trong một file vào 2 commit khác nhau

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.

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

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.

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

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.)

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

$ git checkout -b 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

$ git stash
$ git checkout nhánh-tồn-tại
$ git stash pop

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)

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.

Tôi muốn loại bỏ các thay đổi cụ thể chưa lên stage

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

Tôi muốn loại bỏ các file cụ thể chưa lên stage

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

Tôi muốn chỉ loại bỏ các thay đổi chưa lên stage tại local

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 .

Tôi muốn loại bỏ tất cả các file chưa được theo dõi (track)

Khi bạn muốn loại bỏ tất cả các file chưa được theo dõi

$ git clean -f

Tôi muốn hạ khỏi stage một file cụ thể đã stage

Đôi khi, chúng ta có một hoặc nhiều file đã vô tình lên stage và các file này chưa được commit trước đó. Để hạ chúng khỏi stage:

$ git reset -- <TênFile>

Lệnh trên sẽ hạ file khỏi stage và làm nó không được theo dõi (untracked).

Nhánh

Tôi muốn liệt kê tất cả các nhánh

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

Tạo một nhánh mới từ một commit

$ git checkout -b <nhánh> <SHA1_Của_COMMIT>

Tôi đã pull (kéo) từ/vào sai nhánh

Đâ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.

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

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

Tôi đã tạo commit lên main thay vì một nhánh mới

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

Tôi muốn giữ toàn bộ file từ một ref-ish khác

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ơn develop 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.

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

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

Tôi muốn xóa các nhánh local đã bị xóa tại luồng trước (upstream)

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ề.

Tôi vô tình xóa nhánh của tôi

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.

Tôi muốn xoá một nhánh

Để 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

Tôi muốn xoá nhiều nhánh

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

Tôi muốn đổi tên một nhánh

Để đổ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

Tôi muốn checkout đến một nhánh remote mà người khác đang làm việc trên đó

Đầ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.

Tôi muốn tạo một nhánh remote mới từ một nhánh local hiện tại

$ 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ế độ upstreamsimple (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.

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

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]

Tôi muốn để HEAD của tôi dõi theo nhánh mặc định của remote

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

Tôi đã thực hiện thay đổi trên sai nhánh

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

Tôi muốn tách một nhánh thành hai

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.

Rebasing và Merging

Tôi muốn đảo ngược rebase/merge

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

Tôi đã rebase, nhưng tôi không muốn push ép (force push)

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.

Tôi cần kết hợp các commit

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.

Chiến lược merge an toàn

--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

Tôi cần merge một nhánh thành một commit duy nhất

(main)$ git merge --squash my-branch

Tôi chỉ muốn kết hợp các commit chưa push

Đô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

Tôi cần huỷ bỏ merge

Đô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

Tôi cần cập nhật commit gốc (parent commit) cho nhánh của tôi

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 xem tất cả commit trên một nhánh đã được merge

Để 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

Các vấn đề có thể xảy ra với interactive rebase

Màn hình chỉnh sửa rebase ghi 'noop'

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

Có một vài xung đột

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 (Cất)

Stash tất cả chỉnh sửa

Để 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 các file cụ thể

Để 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

Stash với message (thông điệp)

$ git stash save <message>

hoặc

$ git stash push -m <message>

Apply một stash cụ thể từ danh sách

Đầ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}"

Stash trong khi giữ các thay đổi chưa stage

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

Finding (Tìm)

Tôi muốn tìm một chuỗi ký tự trong bất kỳ commit nào

Để 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ôi muốn tìm tác giả hoặc người commit

Để 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ôi muốn liệt kê các commit chứa các file cụ thể

Để 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

Tôi muốn xem lịch sử commit của một function (chức năng) cụ thể

Để 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 đổihạn mức commit.

Tìm một tag mà một commit đã tham chiếu

Để tìm tất cả các tag có chứa một commit cụ thể

$ git tag --contains <commitid>

Submodules

Clone tất cả submodules

$ git clone --recursive git://github.com/foo/bar.git

Nếu đã clone:

$ git submodule update --init --recursive

Xoá một submodule

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

Miscellaneous Objects (Những thứ khác)

Copy thư mục hoặc tệp file từ một nhánh sang nhánh khác

$ git checkout <nhánh-có-tệp-bạn-muốn> -- <thư mục hoặc tên file>

Khôi phục một file đã bị xoá

Đầ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

Xoá tag

$ git tag -d <tag_name>
$ git push <remote> :refs/tags/<tag_name>

Khôi phục một tag đã bị xoá

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.

Patch (Vá) bị xóa

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

Xuất một repository ra một file Zip

$ git archive --format zip --output /full/path/to/zipfile.zip main

Push một nhánh và một tag có tên giống nhau

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>

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

(main)$ git mv --force myfile MyFile

Tôi muốn ghi đè lên các tệp local khi thực hiện lệnh git pull

(main)$ git fetch --all
(main)$ git reset --hard origin/main

Tôi muốn xóa một tệp khỏi Git nhưng vẫn giữ tệp

(main)$ git rm --cached log.txt

Tôi muốn đảo ngược tệp về bản sửa đổi cụ thể

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

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

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

Tôi muốn Git bỏ qua những thay đổi đối với một tệp cụ thể

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

Debugging (Gỡ lỗi) with Git

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

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

Cấu hình (Configuration)

Tôi muốn thêm bí danh (alias) cho một số lệnh Git

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"

Tôi muốn thêm một thư mục trống vào repository của tôi

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

Tôi muốn cache (cho vào bộ nhớ đệm) một username và password cho một repository

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.

Tôi muốn Git bỏ qua các quyền và thay đổi về filemode (chế độ file)

$ 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

Tôi muốn đặt người dùng toàn cục (global user)

Để 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]"

Tôi không biết mình đã làm gì sai

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).

Git Shortcuts (phím tắt)

Git Bash

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.

PowerShell trên Windows

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
}

Tài nguyên khác

Sách

Hướng dẫn

Scripts (tập lệnh) và các công 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

GUI Clients

  • 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.