Bài 5:Một số vấn đề thường gặp khi sử dụng Git và cách khắc phục

Bài 5:Một số vấn đề thường gặp khi sử dụng Git và cách khắc phục

Bài 5:Một số vấn đề thường gặp khi sử dụng Git và cách khắc phục

Phải thừa nhận rằng nhờ có công cụ kiểm soát phiên bản Git, các nhà phát triển có thể thử nghiệm nhiều hơn trong quá trình hoàn thành dự án của mình. Nếu trong quá trình phát triển xảy ra lỗi thì nhờ có Git, các nhà phát triển luôn có cách hoàn tác phiên bản dự án của mình về thời điểm trước khi họ gặp sự cố.

Cách thức lưu trữ và kiến trúc hoạt động của Git được thiết kế để cho phép chúng ta thực hiện các thay đổi dữ liệu được xem xét và sửa đổi trước khi di chuyển dữ liệu đến giai đoạn tiếp theo. Sau đây là những vấn đề thường gặp trong khi phát triển dự án cùng với Git và cách khắc phục

Unstage file hoặc thư mục

Trong khi thêm hoặc sửa đổi các tệp, bạn thường có quán tính sử dụng lệnh ‘git add’, đó là thêm tất cả các tệp và thư mục vào chỉ mục để chuẩn bị commit. Tuy nhiên, sau khi xem xét lại bạn cảm thấy cần phải hủy bỏ một số tệp nhất định hoặc sửa đổi chúng lần cuối trước khi commit chúng. Cú pháp:

git reset <tên file/tên thư mục>

hoặc

git restore –staged <tên file / thư mục>

Un-staging hoặc restore file/thư mục ra giúp chúng ta có chuyển các file hoặc thưc mục đã sử dụng lệnh git add từ stagging-area về lại working-directory. Sau khi chuyển xong bạn có thể thay đổi lại file, rồi thực hiện thêm vào lại chỉ mục để chuẩn bị commit.

Mẹo: Sử dụng lệnh để cập nhật thay đổi cho tất cả các file và thư mục trong dự án thay vì thêm từng file

git add .

Chỉnh sửa thông điệp của commit cuối cùng

Có trường hợp, sau khi bạn hoàn tất các thay đổi trên file xong và đã thực hiện commit cho sự thay đổi đó. Vì lý do gì đó, mà bạn gõ sai nội dung thông điệp và bạn muốn chỉnh sửa lại thông điệp đó. Hãy gõ lệnh dưới đây, ngay khi bạn vừa hoàn tất commit đó.

git commit –amend

Mẹo: Để hiển thị log đẹp hơn bạn có thể sử dụng cú pháp sau:

git log –pretty=format:”%C(yellow)%h%Creset %ad | %C(green)%s%Creset%C(red)%d%Creset %C(blue)[%an]” –graph –decorate –date=short’

Nếu lệnh trên dài dòng khó nhớ, hãy tạo alias định danh cho nó bằng cú pháp:

git config –global alias.hist ‘log –pretty=format:”%C(yellow)%h%Creset %ad | %C(green)%s%Creset%C(red)%d%Creset %C(blue)[%an]” –graph –decorate –date=short’

Sau này bạn chỉ cần gõ:

git hist

Lưu ý: Không sửa đổi thông điệp commit đã được đẩy vào kho lưu trữ từ xa và đã chia sẻ với người khác, vì điều đó sẽ làm cho lịch sử cam kết trước đó không hợp lệ và do đó mọi công việc dựa trên đó có thể bị ảnh hưởng. Sau khi thực hiện thay đổi commit ID sha-1 của commit đó cũng sẽ bị thay đổi.

Unstage file hoặc thư mục

Lấy lại dữ liệu cá nhân đã được commit vào kho lưu trữ cục bộ (local repository)

Tôi muốn xóa một số dữ liệu khỏi kho lưu trữ cục bộ local repository nhưng giữ các tệp trong thư mục làm việc working directory. Giả sử có một dự án, trong đó có file chứa nội dung về username và password tài khoản hosting của tôi. Nhưng tôi lỡ thực hiện commit cho nó, bây giờ làm thế nào để lấy lại và loại trừ nó khi thực hiện các commit tiếp theo.

Hãy thực hiện theo cách sau:

git reset –mixed HEAD~n

HEAD~n chỉ ra một cam kết theo số thứ tự gần đây bởi nhánh hiện tại, Chỉ số n được hiểu như sau: Ví dụ ta có 3 commit là commit mới nhất, commit thứ 2, commit thứ 3 thì tương ứng với chỉ số n=1, n=2, n=3. Mặc định để HEAD~ thì Git sẽ hiểu là n = 1, nghĩa là lấy Commit mới nhất.

git reset –mixed <commit-id>

Commit id chỉ định cụ thể một cam kết kiểu như số Chứng minh nhân dân vậy ^^. Đây là một chuỗi 40 ký tự bao gồm các ký tự thập lục phân (chứa các số từ 0-9 và ký tự từ a-f) và được tính toán dựa trên nội dung của cấu trúc tệp hoặc thư mục trong Git. Có dạng như sau:

24b9da6552252987aa493b52f8696cd6d3b00373

Ở ví dụ dưới đây, giả sử tôi có file aptechbmt.edu.vn.pass lưu trữ username/password, tôi đang có dự định tạo 1 commit cho file LeHuyC.txt nhưng theo quán tính tôi hay sử dụng lệnh “git add . ” để thêm file cho nhanh thay vì gõ tên file (bệnh lười ^^) nhưng thật không may lệnh này sẽ add tất cả các file/thư mục đã được thay đổi trong đó bao gồm cả file aptechbmt.edu.vn.pass ,điều này có thể gây ra nguy hại sau này.

Sau khi kết thúc lệnh git reset, các file hiện tại đã bị loại ra khỏi “local repository” và “stagging area” việc tiếp theo là add lại file LeHuyC.txt và thực hiện commit cho nó. Dưới đây là cách reset theo id-commit, lưu ý: tùy vào thời điểm bạn commit hãy lựa chọn id-commit cho chính xác

Mẹo: Thêm nội dung sau trong tệp .gitignore để loại trừ chúng khỏi bị theo dõi bởi git.

vim .gitignore# password files #*.pass*.key*.passwd

Với điều này, commit có ảnh chụp nhanh của các file có phần mở rộng được liệt kê trong file .gitignore sẽ bị loại trừ. Các tập tin của tôi vẫn hiện diện trong thư mục làm việc của tôi nhưng không còn hiện diện trong kho lưu trữ cục bộ, cũng sẽ không được push vào một kho lưu trữ từ xa. Xem thêm mô tả bên dưới:

Cảnh báo: Nếu bạn làm mất các file đó, git không thể phục hồi chúng cho bạn vì Git không theo dõi chúng.

Thay thế commit mới nhất bằng một commit khác

git reset –soft [<commit-id>/HEAD~n>]

Khác với tùy chọn ‘mixed’ tùy chọn ‘–soft’ chỉ cần xóa các tệp đã commit khỏi kho lưu trữ cục bộ “local repository” trong khi chúng vẫn được giữ lại trong chỉ mục “stagging area” và bạn có thể thực hiện cam kết lại chúng sau khi xem xét. Tương tự <commit-id> là sha-1 của ảnh chụp nhanh mà bạn muốn xóa khỏi local repo và <HEAD ~ n> trong đó n là số thứ tự commit như đã nói ở trên. Giả sử: tôi tạo ra 2 file mới là PhamThiD.txt và TranVanE.txt, lúc đầu tôi thực hiện commit cho cả 2 file cùng lúc. Nhưng sau đó tôi lại muốn thực hiện commit lại cho từng file mà không thay đổi nội dung file, thì tôi làm theo cách sau:

Commit sai dữ liệu không mong muốn và muốn xóa dữ liệu đó

git reset –hard HEAD~ngit reset –hard <commit-id>

Giả sử tôi có một file mới là MaiVanF-Draff.txt , tôi lỡ thực hiện commit cho nó xong nhưng chợt nhận ra file này là file nháp bị lỗi. Bây giờ tôi muốn lấy lại commit cũ và xóa luôn file này khỏi kho lưu trữ của mình. Tôi làm như sau:

Kết quả là cam kết mới nhất và các tệp đó được xóa khỏi kho lưu trữ cục bộ “local repository” và khu vực tổ chức cũng như thư mục làm việc “working directory”

Chú ý: Git reset –hard là một lệnh nguy hiểm khiến bạn mất tập tin trong thư mục làm việc. Nó không được đề xuất trên một kho lưu trữ được chia sẻ từ xa.

Bạn có thể theo dõi quy trình của Git được mô tả ở biểu đồ dưới đây để hiểu rõ hơn:

Quay trở lại trạng thái cũ của dự án

Một lợi thế của Git là bạn có thể chuyển sang trạng thái cũ hơn của dự án trong lịch sử. Nếu bạn làm hỏng phiên bản mới nhất hoặc cần cải tiến trong phiên bản cũ hơn, bạn có thể tạo một nhánh branch khác từ ảnh chụp dự án ở phiên bản cũ mà bạn muốn phát triển lại từ nó để không cản trở công việc hiện tại của bạn. Chúng ta hãy làm theo các bước sau:

a. Liệt kê lịch sử dự án và quyết định lựa chọn một id-commit cũ hơn bằng lệnh: git log
b. Tạo một nhánh khác từ id-commit đã được chọn: git branch <tên branch> <id-commit>
c. Tiếp tục làm việc trên nhánh vừa tạo với phiên bản đó và khi đã ưng ý thì merge / rebase với nhánh master

Ví dụ: Tôi có lịch sử commit như sau:

​​​​​​​$ git log –onelinecaa556d (HEAD -> master) Them TranVanE097b835 Them PhamThiD5da0b8b Them file .gitignore va them LeHuyCe5d0fd6 Thay doi noi dung file Nguyen Van A.txt8aff54c Them NguyenVanA va TranThiB

Bây giờ tôi muốn tách 1 nhánh tại commit HEAD~3 tương đương với id-commit là 5da0b8b và đặt tên cho nó là nhánh kiem-tra với mục đích là kiểm tra xem file thông tin cá nhân có tên là MaiVanF.txt có nội dung đúng chưa. Nếu đã có chứa đầy đủ nội dung, tôi sẽ hợp nhất vào lại nhánh master rồi sau đó xóa branch kiem-tra đi.

Quay trở lại trạng thái cũ của dự án

Khôi phục một Branch ở kho lưu trữ cục bộ đã bị xóa

Có thể lấy lại công việc đã bị mất trên một nhánh tham chiếu. Giả sử, tôi mở thêm một nhánh ‘phat-trien’ để phát triển một chức năng lưu trữ danh sách được đặt trên file luutrudanhsach.php ,nhánh này tôi quên không hợp nhất với nhánh master và xóa mất nó, kết quả tôi bị mất file luutrudanhsach.php. Và tôi cũng quên không đẩy nhánh này đến một kho lưu trữ từ xa, giờ thì phải làm sao để lấy lại file này? Rất may là git theo dõi và giữ một mục nhật ký của tất cả các thay đổi được thực hiện trên mỗi file trong một bảng riêng biệt gọi là reflog.

Có 2 cách để khôi phục một Branch và nội dung của nó dựa trên HEAD~n hoặc id của lịch sử reflog. Nhưng đầu tiên bạn cần phải kiểm tra lịch sử trên reflog bằng lệnh sau:

git reflog

  • Cách 1: git hard reset về thời điểm bạn mong muốn ví dụ như trước khi xóa branch chẳng hạn. Cách này sẽ thay đổi commit Head~1 của nhánh master trường hợp xấu nhất còn gây mất commit mới nhất của nhánh master nếu thứ tự commit của nhánh master nhỏ hơn thứ tự commit mới nhất của nhánh cần lấy dựa trên reflog
  • Cách 2: sử dụng lệnh cherry-pick <id-commit> để lấy lại lịch sử commit dựa trên reflog, cách này sẽ báo xung đột conflict file, do file đã bị xóa. Chúng ta phải git add và git commit lại mới OK​​​​​​​
  • Cách 3: dài dòng hơn nữa là tạo một branch khác (mục đich là để sau này cần thì quay lại phát triển tiếp trên này) tại thời điểm bạn mong muốn, sau khi đã kiểm tra kỹ lưỡng tất cả mọi thứ mới merge hoặc rebase vào nhánh master. Tôi sẽ thực hiện​ theo cách này, như video dưới đây:

Hoàn tác các thay đổi đã được thực hiện trong một commit

git Revert được sử dụng cho việc ghi lại một số commit để đảo ngược tác dụng của một số commit trước đó.

git revert <id-commit>

Từ lịch sử commit của tôi, tôi muốn revert thay đổi dựa trên id-commit, điều này không làm mất lịch sử commit mới hơn bản thân nó, mà git revert sẽ tạo ra một commit ghi nhận có sự revert trong hệ thống. Lấy ví dụ, tôi có một project-web như bên dưới đây. Tại thời điểm đầu tôi có tạo một logo cho website và thực hiện commit cho nó, tuy nhiên sau này tôi muốn đổi lại một logo mới hoàn thiện hơn nhưng tôi lại muốn giữ lại lịch sử commit cũ để sau này xem lại và không muốn làm xáo trộn lịch sử commit mới nhất.

Lưu ý: bạn không nên reset –hard lại các commit đã được chia sẻ, mà thay vào đó, hãy ‘git revert’ chúng để lưu giữ lịch sử từ đó mọi người dễ dàng theo dõi nhật ký lịch sử để tìm ra những gì được hoàn nguyên, bởi ai và tại sao? Bạn cũng có thể sử dụng con trỏ chính HEAD thay vì đưa ra id-commit, ví dụ như HEAD ~ 3 hoặc HEAD ~ 4, v.v.

Sửa lại tên branch ở kho lưu trữ cục bộ do đặt sai tên

Bạn có thể đổi tên một Branch trong kho lưu trữ cục bộ. Trước khi biết điều này, bạn hay thường tạo một branch mới với cái tên mà mình muốn rồi sau đó thực hiện di chuyển toàn bộ công việc tại branch cũ qua bằng lệnh mere/rebase. Nhưng giờ khác rồi, bạn có thể ở trên cùng một nhánh hoặc một nhánh khác và vẫn có thể đổi tên nhánh như mong muốn bằng lệnh dưới đây:

git branch -m <tên cũ> <tên mới>

Bạn tự hỏi liệu git có theo dõi việc đổi tên này không? Vâng, nó vẫn được đề cập đến trong ‘reflog’ của bạn, kiểm tra thử nhé!

Đổi tên một chi nhánh sẽ không ảnh hưởng đến chi nhánh theo dõi từ xa của nó. Chúng ta sẽ tìm hiểu nó trong phần từ xa “làm thế nào để thay thế một nhánh trên kho lưu trữ từ xa”

Sắp xếp lại nhật ký lịch sử commit trước khi push lên kho lưu trữ từ xa

Trong quá trình thực hiện dự án, để lịch sử commit trước khi được chia sẻ trông gọn gàng và dễ hiểu hơn. Chúng ta có thể sử dụng lệnh git rebase để thực hiện các thao tác này. Cú pháp như sau:

git rebase -i <id-commit>

Trong đó, id-commit là mốc thời gian mà bạn muốn thực hiện các thay đổi cho lịch sử commit bắt đầu từ nó trở về hiện tại (hiểu nôm na là các id-commit được hình thành phía sau nó sẽ bị ảnh hưởng). Giả sử tôi có lịch sử commit và muốn thay đổi.

Như vậy, tất cả các commit hình thành sau commit có id là f1fa83 sẽ nằm trong danh sách bị thay đổi.

Bạn có thể truy cập lại tài liệu rebase git trên trang https://git-scm.com/docs/git-rebase để hiểu làm thế nào là một ‘rebinteractive hoặc -i’ rebase khác với rebase thông thường.

Tách một commit thành nhiều commit khác nhau

Có bao giờ, bạn gặp phải trường hợp như thế này chưa? Sau khi hoàn thành một dự án, ngồi đọc lại lịch sử commit của mình trước khi push lên một kho lưu trữ từ xa như Github thì thấy có một vài commit có thể tách ra thành nhiều commit logic hơn để giúp người đọc code rõ nghĩa hơn về công việc đó.

Ví dụ: mình đang viết một đoạn code HTML+CSS để thiết kế giao diện cho thẻ <header> của trang web, nhưng trong <header> bao gồm thẻ <nav> menu và thẻ <img> log. Nhưng chỉ có một commit duy nhất cho cả 2 phần này, là commit cho <header> thôi. Bây giờ bạn muốn tách ra để logic hơn và người đọc code cũng thuận lợi hơn.

Đơn giản là chúng ta vẫn sử dụng lệnh dưới:

git rebase -i <id-commit>

Như ảnh trên, tôi muốn tách commit fcb1f50 tôi sử dụng git rebase -i 63db231(Lưu ý: rebase có tác dụng với các commit được lập sau commit được chỉ định)

Trong trình chỉnh sửa rebase, bạn phải chọn id-commit mà mình muốn tách và thay đổi nó thành ‘edit’ ô màu đỏ thay vì ‘pick’ và lưu lại (Trong Vim nhấn :wq )

Mặc định khi chúng ta chọn edit git sẽ yêu cầu thực hiện git –amend để edit lại commit, nhưng chúng ta hãy xem lại git log trước đã. Lúc này, con trỏ HEAD đã được trỏ tới commit fcb1f50

​​​​​​Việc cần làm bây giờ là sử dụng lệnh git reset HEAD~1 để đặt lại commit.

Sau đó, bạn chỉnh sửa lại theo ý mình và thực hiện commit cho các thay đổi đó bình thường. Dưới đây, tôi thực hiện 2 commit thay đổi cho <img> và <nav>

git add . & git commit -m “Add <img> logo inside <header>”

git add . & git commit -m “Add <nav> menu inside <header>”

Lúc này, bạn sử dụng git log để kiểm tra thì thấy một nhánh mới được tạo như ảnh dưới với 2 commit mới nhất vừa được tạo.

Việc cuối cùng là chạy lệnh git rebase –continue để hoàn tất rebase 2 thay đổi vào nhánh master. Kiểm tra lại git log để thấy sự thay đổi. ​​​​​​​

Như vậy, bạn đã thấy 2 commit được tách ra từ 1 commit như thế nào rồi đúng không? Thực hành ngay nhé.

Lưu ý: Trong một số trường hợp sẽ báo conflict xung đột file do quá trình chỉnh sửa nội dung trên cùng một dòng. Tùy vào trường hợp sẽ có cách khắc phục cụ thể, tuy nhiên tách 1 commit thành nhiều commit chỉ là cách chữa cháy, bạn nên tập thói quyen cho mình khi kết thúc một chức năng nào thì commit luôn cho chắc ăn! Bây giờ hãy tiếp tục với vấn đề tiếp theo nào!

​​​​​​​Thay đổi email tác giả trong tất cả các commit trong kho lưu trữ

Vào một ngày đẹp trời, bạn nhận ra rằng id email của mình đã bị xâm phạm trong nhật ký lịch sử commit thậm chí còn được xuất bản trên các kho lưu trữ từ xa. Điều này có thể xảy ra với bất cứ ai khi ban đầu thiết lập các cấu hình trong tập tin .gitconfig. Để có thể chỉnh sửa lại các biến môi trường mà chúng ta cung cấp khi tạo đối tượng commit, bạn có thể làm như sau:

Đầu tiên tôi lấy danh sách id email để xem những thứ mà mình muốn thay đổi: (

git log –all –pretty=format:”%C(yellow)%h%Creset %ad | %C(green)%s%Creset%C(red)%d%Creset %C(blue)[%an-%ae]” –graph –decorate –date=short

​​​​​​​Thay đổi email tác giả trong tất cả các commit trong kho lưu trữ

Lệnh trông dài dòng, bạn xem thêm ở đây nhé https://git-scm.com/docs/pretty-formats để hiểu thêm về pretty mà tôi áp dụng để lấy các giá trị cần thiết nhé:

Giả sử lịch sử commit của tôi như hình trên, bây giờ tôi muốn thay đổi tất cả email [email protected] thành [email protected]. Tôi sẽ sử dụng một vòng lặp để quét qua mọi cam kết trên mỗi nhánh và viết lại đối tượng cam kết bằng lệnh id email mới

git filter-branch –env-filter ‘if test “” = “Mai Ai Xuan Huong”then GIT_AUTHOR_EMAIL = [email protected]’ — –all

Kết quả kiểm tra git log

Sau khi thay đổi email xong Git sẽ tự động backup lại các commit (các refs/original/* là một bản sao lưu, trong trường hợp bạn làm tác động sâu vào các branch của mình) đây thực sự là một việc hữu ích. Khi bạn đã kiểm tra kết quả và bạn rất tự tin rằng mình đã có những gì mình muốn, bạn có thể xóa các tham chiếu đã sao lưu bằng lệnh sau:

git update-ref -d refs/original/refs/heads/mastergit update-ref -d refs/original/refs/heads/develop-about

Kiểm tra lại git log xem đã Ok chưa nhé! Trong hình tôi đặt alias cho git log bằng lệnh dưới nhé!

git config –blobal alias.newlog ‘log –all –pretty=format:”%C(yellow)%h%Creset %ad | %C(green)%s%Creset%C(red)%d%Creset %C(blue)[%an-%ae]” –graph –decorate –date=short’

Tìm kiếm nhanh tập tin dựa trên nội dung của nó

Giả sử bạn đã quên mất một tập tin và bạn không nhớ tên của nó, nhưng có thể nhớ lại các từ nhất định trong nội dung của tệp. Trong trường hợp này, bạn có thể làm theo các bước sau:

​​​​​​​- Bước 1: Liệt kê tất cả các cam kết đã từng chứa ảnh chụp nhanh tệp với mẫu tìm kiếm

git rev-list –all | xargs git grep -i ‘nội dung trong tập tin’

Giả sử tôi muốn tìm kiếm file chứa từ khóa “BaseController”. Câu lệnh trên giúp tôi tìm được đường dẫn chính xác của file chưa nội dung đó là app/Http/Controllers/Controller.php

Tìm kiếm branch có chứa một id-commit bất kỳ

Đôi khi, sau khi bạn phát hiện ra một id commit lỗi, bạn cũng có thể muốn biết tất cả các branch chứa cam kết này trên chúng từ đó bạn tìm và sửa tất cả chúng. Nếu kiểm tra lịch sử của mỗi branch là không thực tế trong một dự án lớn có nhiều branch.

Để liệt kê các nhánh có id commit đó bạn sử dụng lệnh sau:

git branch –contains <commit-id>

Xóa một commit khỏi lịch sử

Đôi khi bạn cảm thấy cần phải xóa sạch một commit khỏi lịch sử và không để lại dấu vết nào. Tôi khuyên bạn không nên thử làm “diễn viên đóng thế” này trên một nhánh chung mà chỉ trên nhánh local của bạn ^^ (hậu quả thì bạn tự rõ nhé)

git rebase -i <commit-id>

Trong trình chỉnh sửa rebase-> thay thế ‘pick’ bằng ‘drop’ cho id-commit bạn cần xóa. Xem lại phần này để biết thêm

Trong một số trường hợp, việc rewrite này có thể dẫn đến conflict xung đột. Bạn phải giải quyết xung đột sau đó tiến hành thêm.

Cảnh báo : Đây là một lệnh nguy hiểm vì lịch sử commit sẽ bị rewrite và có thể mất dữ liệu.

Xóa một commit khỏi lịch sử

Tổng kết

Trong bài này, tôi đã đề cập đến một số lỗi thường gặp và các biện pháp mà git có thể giúp bạn khắc phục. Mỗi mã code là duy nhất và được phát triển theo cách của nó, vì vậy cũng có những cách khác nhau để tiếp cận và khắc phục một vấn đề. Bạn luôn có thể tham khảo tài liệu git chính thức để hiểu cách mà các lệnh git khác nhau bảo vệ mã code của bạn và cách sử dụng các lệnh một cách tốt nhất có thể.

Bây giờ bạn đã hiểu các lỗi Git phổ biến, hãy xem chương trình đào tạo Lập trình viên quốc tế của Aptech Buôn Ma Thuột. Chương trình học với hơn 100.000 học viên Việt Nam đã và đang theo đuổi với nhiều lợi thế về công nghệ. Đặc biệt tại mỗi kỳ học bạn sẽ được làm việc với các dự án cụ thể và Git luôn là một người bạn đồng hành cùng bạn. Gọi cho chúng tôi theo số điện thoại 02623 50 50 55 được tư vấn và nghe lộ trình đào tạo miễn phí nhé.