Git amend là gì

Bất kể ai từ newbie tới “gừng già” đều sẽ học được cái gì đó về Git.

Đôi khi cách tốt nhất để nắm được vấn đề là phải “ngụp lặn” mầm mò trong code. Nhưng đáng tiếc là, những thay đổi trong quá trình lại không được tối ưu lắm, trong trường hợp này thì revert file về lại trạng thái ban đầu có lẽ là cách nhanh gọn nhất:

git checkout -- Gemfile # reset specified path git checkout -- lib bin # also works with multiple arguments

Dấu 2 gạch [--] biểu thị cho tính năng command line để báo kết thúc các option của command.

Đôi khi chúng ta phải tốn khá nhiều thời gian để nhận ra rằng mình đang đi sai hướng, và đến lúc đó thì không ít thay đổi đã được áp dụng. Đấy là lúc mà git reset trở nên có ích:

git reset HEAD~2 # undo last two commits, keep changes git reset --hard HEAD~2 # undo last two commits, discard changes

Hãy cẩn thận với option --hard! Nó sẽ reset hệ thống bạn làm việc, cũng như tất cả các thay đổi cũng sẽ bị mất đi luôn.

Nếu bạn không cẩn thận khi dùng git add, bạn sẽ add một mớ file không mong muốn vào mà không hay biết. Tuy nhiên, git rm sẽ remove nó trên cả staging area, cũng như file system của bạn – một điều không ai mong muốn. Trong trường hợp này, hãy đảm bảo rằng bạn chỉ remove bản staged thôi, và add file vào .gitignore để tránh lặp lại lỗi lần hai:

git reset filename # or git remove --cached filename echo filename >> .gitignore # add it to .gitignore to avoid re-adding it

Lúc này sẽ có typo, nhưng may thay là bạn có thể sửa nó khá dễ trên commit messages:

git commit --amend # start $EDITOR to edit the message git commit --amend -m "New message" # set the new message directly

Nhưng đó vẫn chưa hết công dụng của git-amend. Nếu bạn có quên add một file vào, chỉ cần add vào và chỉnh lại commit trước đó!

git add forgotten_file git commit --amend

Hãy nhớ rằng --amend thật ra tạo commit mới thay thế cho cái trước, nên đừng dùng nó để thay đổi commit mà đã được đẩy vào giữa repository. Có một ngoại lệ đó là bạn phải chắc chắn về việc không có developer nào đã check các bản trước và làm dựa trên đó, như thế thì một forced push [git push --force] vẫn sẽ ổn. Cần có option --force ở đây vì lịch sử của hệ thống đã bị điều chỉnh local, nghĩa là push sẽ bị reject bởi server từ xa vì fast-forward merge là không thể.

Mặc dù --amend rất hữu ích, nó không thật sự có tác dụng nếu phần commit bạn muốn  reword không phải là cái cuối. Trong trường hợp này, bạn sẽ cần đến một interactive rebase:

git rebase --interactive # if you didn't specify any tracking information for this branch # you will have to add upstream and remote branch information: git rebase --interactive origin branch

Nó sẽ mở ra phần editor và hiển thị menu sau:

pick 8a20121 Upgrade Ruby version to 2.1.3 pick 22dcc45 Add some fancy library # Rebase fcb7d7c..22dcc45 onto fcb7d7c # # 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

Ở phần đầu, bạn sẽ thấy một list các local commit, kèm theo phần giải thích cho các command có sẵn. Chỉ cần chọn phần commit bạn muốn update, đổi pick thành reword [hoặc viết tắt là r], và bạn sẽ có được một view mới nơi bạn có thể edit message.

Tuy nhiên, như có thể thấy từ listing trên, các interactive rebase offer nhiều cái khác nữa ngoài edit commit message: bạn có thể remove commit hoàn toàn bằng cách xoá nó khỏi list, cũng như edit hoặc sắp xếp lại hoặc squash nó. Squash cho phép bạn gộp nhiều commit thành một, một bước tôi hay làm trên các nhánh feature trước khi push chúng vào phần remote. Từ đó sẽ không còn các bản record các commit “Add các file bị miss” và “Sửa typo” nữa!

Mặc dù một số giải pháp đã được nêu rõ trong những tip trước, một số commit sai đôi khi sẽ trội lên hẳn trong repository. Không sao cả, vì git đã cho bạn một cách đơn giản để revert một hoặc nhiều commit:

git revert c761f5c # reverts the commit with the specified id git revert HEAD^ # reverts the second to last commit git revert develop~4..develop~2 # reverts a whole range of commits

Nếu bạn không muốn tạo thêm revert commit mà chỉ muốn áp dụng các thay đổi cần thiết vào hệ thống, bạn có thể dùng option --no-commit/-n.

# undo the last commit, but don't create a revert commit git revert -n HEAD

Trang manual tại man 1 git-revert cũng list ra nhiều option khác và cho thêm một số ví dụ khác nữa.

Một chuyện mà mọi developer đều hiểu, rằng việc fix merge conflict nghe thật sự kinh dị, nhưng việc cứ xử lý đi xử lý lại một conflict [ví dụ: trong các feature branch chạy lâu dài] thì thật sự là một cơn ác mộng. Nếu bạn đã gặp tình trạng này, bạn sẽ rất vui khi được biết về feature reuse recorded resolution feature. Hãy add nó vào global config cho tất cả các project:

git config --global rerere.enabled true

Ngoài ra, bạn có thể dùng nó trên từng dự án bằng cách tạo thủ công thư mục .git/rr-cache.

Đây không phải là một feature cho tất cả mọi người, nhưng riêng đối với những ai cần nó,  nó sẽ tiết kiệm cho bạn không ít thời gian. Hãy tưởng tượng team của bạn đang làm nhiều nhánh tính năng khác nhau cùng một lúc. Bây giờ bạn muốn hợp nhất tất cả chúng lại với nhau thành một nhánh có thể test được trước khi release. Từ đó bạn sẽ phải xử lý một số conflict về merge. Không may một trong các nhánh vẫn chưa hoàn thiện, vì vậy bạn phải hủy merge lại một lần nữa. Vài ngày [hoặc vài tuần] sau khi nhánh cuối cùng đã sẵn sàng, bạn sẽ merge lại, nhưng nhờ vào các độ phân giải đã ghi, bạn sẽ không phải giải quyết xung đột hợp nhất lần nữa.

Trang chính [man git-rerere] có nhiều info hơn về các case và các command [git rerere status, git rerere diff, v.v].

Việc mò ra commit đã kéo bug sau khi merge khá tốn thời gian. May mắn là git cung cấp cho chức năng search nhị phân rất hay dưới dạng git-bisect. Đầu tiên bạn cần biểu hiện setup đầu tiên:

git bisect start # starts the bisecting session git bisect bad # marks the current revision as bad git bisect good revision # marks the last known good revision

After this git will automatically checkout a revision halfway between the known “good” and “bad” versions. You can now run your specs again and mark the commit as “good” or “bad” accordingly.

Sau khi git tự động checkout các phiên bản “tốt” và bản “xấu”. Bạn cũng có thể chạy lại các spec và đánh dấu commit tương ứng là “tốt” hay “xấu”.

git bisect good # or git bisec bad

Process này sẽ tiếp tục cho đến khi bạn tìm ra commit mang bug.

Một số lỗi bị lặp lại rất thường xuyên, nhưng sẽ dễ tránh được nếu như check thường xuyên hoặc các task cleanup ở một stage nhất định trong git workflow. Hook được thiết kế để dùng cho trường hợp này. Để tạo ra hook mới, hãy add thêm một file vào .git/hooks. Tên của script phải tương thích với một trong những hook có sẵn , một list đầy đủ những cái có sẵn trên manual page [man githooks]. Bạn cũng có thể define các global hook để dùng trong các project của mình bằng cách tạo ra template directory cho git sử dụng khi khởi động một repository mới. Một template directory mẫu trông như sau:

[init] templatedir = ~/.git_template → tree .git_template .git_template └── hooks └── pre-commit

Khi khởi động initialize một repository mới, các file trong template directory sẽ bị copy về vị trí tương ứng trong  .git directory của project.

Dứoi đây là một hook commit-msg mẫu, để đảm bảo rằng mỗi commit message đều hướng về một ticket number như “#123“.

ruby #!/usr/bin/env ruby message = File.read[ARGV[0]] unless message =~ /\s*#\d+/ puts "[POLICY] Your message did not reference a ticket." exit 1 end

Tới đây thì chúng ta đã cover được khá nhiều cách fix các lỗi thường gặp khi làm việc với git. Hầu hết giải pháp đều khá ổn, tuy nhiên cũng có lúc không dùng được cách dễ và phải viết lại history của nguyên branch. Một cách hay dùng trong trường hợp này đó là remove phần data nhạy cảm [ví dụ như uỷ quyền login cho hệ thống production] gắn liền với public repository:

git filter-branch --force --index-filter \ 'git rm --cached --ignore-unmatch secrets.txt' \ --prune-empty --tag-name-filter cat -- --all

Việc này sẽ remove file secrets.txt khỏi tất cả các branch và tag. Nó cũng remove mọi commit trống sau khi thực hiện remove. Hãy nhớ rằng việc này cũng sẽ viết lại history của toàn bộ project mà có thể broke trên workflow. Và trong lúc file đang bị remove, phần uỷ quyền vẫn khá nguy hiểm!

GitHub đã có một bài tutorial cách remove data nhạy cảm và man git-filter-branch có mọi chi tiết về những filter và option có sẵn.

Bài viết được dịch từ bài Gitでやらかした時に使える19個の奥義 của tác giả muran001 trên Qiita.

[Image Source: Medium]

Những nội dung sau rất nguy hiểm, nếu bạn làm theo, xin hãy chịu trách nhiệm về bản thân mình.

Nếu có gì sai sót, mong bạn hãy chỉ ra để tôi được biết

Ở trên local

Có hiệu quả khi làm việc trên môi truờng phát triển của bản thân, không làm ảnh hưởng đến người khác. Khi làm việc trên remote sẽ phát sinh ảnh hưởng đến người khác nên rất nguy hiểm.

Sự cố 1: Khi trong commit message lỡ có những từ cấm kị khiến bạn chỉ muốn chết đi cho xong

Để sửa lại nội dung commit thì rất đơn giản. Cũng có thể thêm được cả file vào trong commit

$ git commit --amend

Sự cố 2: Khi lỡ tay commit dưới tên của môt user khác

Khi làm việc ở nhà và khi làm việc trên công ty thì hẳn sẽ có lúc sử dung git config khác nhau và cũng có lúc bị nhầm thiết lập.

Ở đây chúng ta đang nói về việc thay đổi tác giả [Author] Hãy đổi Commiter ở .gitconfig hoặc .git/config

Khi muốn đổi tên user hoặc email của HEAD

$ git commit --amend -m "nội dung commit message" --author="user.name "

Trường hợp muốn sửa lại commit từ lâu rồi

$ git rebase -i ==== pick aa11bbc commit message 1 pick b2c3c4d commit message 2 pick 4e56fgh commit message 3 ・・・ edit aa11bbc commit message 1 pick b2c3c4d commit message 2 pick 4e56fgh commit message 3 ・・・ ==== $ git commit --amend -m "commit message" --author="user.name " $ git rebase --continue

[NGUY HIỂM] Viết lại toàn bộ lịch sử commit

Khi muốn sửa các commit của người dùng [committer] nhất định thì có thể dùng cách sau để sửa

Lịch sử commit sẽ bị thay đổi hoàn toàn

git filter-branch --commit-filter ' if [ "$GIT_COMMITTER_NAME" = "" ]; then GIT_COMMITTER_NAME=""; GIT_AUTHOR_NAME=""; GIT_COMMITTER_EMAIL=""; GIT_AUTHOR_EMAIL=""; git commit-tree "[email protected]"; else git commit-tree "[email protected]"; fi' HEAD

Sự cố 3: Khi đã lỡ tay commit một số file thừa nhưng sau khi nghĩ lại thì nên ignore thì hơn

Trường hợp commit xoá

Nếu có những phần thừa thì ta xoá đi rồi thêm vào ignore

$ git rm --cached $ echo '' >> .gitignore

Trường hợp muốn coi như là chưa từng có file đó

Khi mà bạn muốn: "Hả, file nào cơ, là gì có file nào như thế ?"

$ git filter-branch -f --index-filter 'git rm --cached -rf --ignore-unmatch ' HEAD $ git filter-branch -f --index-filter 'git rm -rf --ignore-unmatch ' HEAD

Sự cố 4: Khi mà tên branh bạn đặt quá là "chuối"

Nếu không ai nhìn thấy thì ta "bí mật" sửa lại tên branch thôi nào !

$ git branch -m

Sự cố 5: Khi trong phút nông nổi bạn lỡ commit và giờ muốn commit đó biến mất

Không để cho ai biết (= người khác vẫn chưa pull về)

Sửa lại phần lịch sử commit ngay trước đó

$ git reset --soft HEAD~ $ git reset HEAD~ $ git reset --hard HEAD~

Trường hợp đã bị ai đó phát hiện(= người khác đã pull về)

Nếu thay đổi lịch sử commit sẽ dẫn đến tình trạng lộn xộn nên ta cầm dùng commit xoá để giải quyết

$ git revert

Sự cố 6: Khi muốn tổng hợp những commit vụn vặt trước đây thành một commit

Sử dụng rebase + squash [or fixup] để tổng hợp lại các commit cũ

$ git rebase -i HEAD~ ==== pick aa11bbc commit message 1 pick b2c3c4d commit message 2 pick 4e56fgh commit message 3 ・・・ pick aa11bbc commit message 1 squash b2c3c4d commit message 2 squash 4e56fgh commit message 3 ・・・ ====

Thay vì dùng squash có thể dùng fixup nhưng khi đó commit message của những commit được fixup sẽ bị mất

Sự cố 7: Khi bạn muốn chia commit to thành những commit nhỏ hơn một cách thông minh

Ở mục 6, tuy tổng hợp lại là tốt nhưng khi tổng hợp quá nhiều cũng sẽ trở thành vấn đề Rất tiện khi mà những commit chức năng bị lẫn vào những commit sửa bug

$ git reset HEAD~ $ git add -p $ git commit -m "commit message"

Sự cố 8: Khi lỡ tay commit nhầm sang một branch khác

Có những lúc ta sơ suất lỡ tay commit thẳng vào master trong khi thực ra là muốn commit vào một branch khác

$ git branch other-branch $ git reset --hard HEAD~ $ git checkout other-branch

Sự cố 9: Khi muốn xoá hoàn toàn các file không cần thiết

Có những lúc ta muốn 1 phát xoá tất tần tật những file tự động sinh ra hoặc những file tự động back up. Nếu không phải là những file cần git quản lý hoặc những file sẽ commit thì xoá đi thôi. Ơ mà cái này cũng ko phải là sự cố nhỉ...

$ git add $ git commit -m "commit message" $ git stash -u $ git stash drop

Tiện đây thì trong trường hợp chỉ là những file mà git không quản lý thì chỉ như sau là OK

$ git clean -n $ git clean -f

Sự cố 10: Khi tạm thời cần tạm dừng công việc hiện tại và chuyển sang một branch khác

Tuy mình không xấu nhưng có những lúc cần phải tạm dừng công việc để làm việc khác

$ git stash -u $ git checkout -b other-branch ~làm việc, làm việc, làm việc~ $ git add $ git commit -m "commit message" $ git checkout origin-branch $ git stash pop

Sự cố 11:Khi lỡ tay xoá mất một commit cực kì cực kì quan trọng

Đây hẳn là sự cố khủng khiếp nhất. Có thể xảy ra khi lỡ tay git reset --hard Tuy nhiên, commit nào cũng có thể hồi phục được

$ git reflog $ git reset --hard

Sự cố 12: Khi lỡ tay xoá mất branch và muốn lấy lại

Nếu vẫn còn reflog thì không có sao. Vẫn có thể hồi phục lại được Nếu commit lên một branch không tên, sau đó check out sang một branch khác bị xoá mất thì cũng tương tự

Tương tự giống như sự cố 13 dưới đây

$ git reflog $ git branch

Sự cố 13: Khi muốn thay đổi một phần nội dung commit

Khi có những typo nhỏ và muốn chỉnh sửa thì ko cần thiết phải tạo commit mới để sửa mà hãy tiến hành sửa lại luôn

$ git rebase -i ==== pick aa11bbc commit message 1 pick b2c3c4d commit message 2 pick 4e56fgh commit message 3 ・・・ edit aa11bbc commit message 1 pick b2c3c4d commit message 2 pick 4e56fgh commit message 3 ・・・ ==== $ git add $ git commit --amend $ git rebase --continue $ git rebase --abort

Sự cố 14: Khi có conflict trong quá trình rebase

Khi không merge mà tiến hành rebase thì nếu có conflict, chắc hẳn là hoảng loạn rồi

$ git rebase branch2 ~~ Phát sinh conflict ~~ $ git branch * [no branch, rebasing branch2] branch1 branch2 master $ git add $ git commit --amend $ git rebase --continue

Sự cố 15: Khi đã merge nhưng lại muốn trở lại như lúc trước

Chỉ cho trường hợp mà người khác vẫn chưa pull code về

$ git checkout $ git merge $ git reset --hard ORIG_HEAD

Ở trên Remote

Sau khi pull code về cập nhật nhưng thấy conflict nhiều như núi thì quả là muốn trở lại như cũ Gần giống như sự cố 15

$ git pull origin master $ git reset --hard ORIG_HEAD

Sự cố 17: Lỡ tay push force lên master trên remote

Việc push -f lên origin/master là việc bị cấm Tuy nhiên thì thỉnh thoảng vẫn có một số người làm...

Ta sẽ sửa lại như sau

Trường hợp chỉ cần push về 1 commit trước đó

$ git commit -m "commit message" $ git push -f origin master $ git push -f origin HEAD~:master

Nếu trên local cũng muốn như vậy thì tiến hành reset cũng tốt

$ git reset HEAD~ $ git push -f origin master

Nếu muốn xoá đi vài commit trước đó

$ git commit -m "commit message" $ git push -f origin master $ git rebase -i ==== pick aa11bbc commit message 1 pick b2c3c4d commit message 2 ・・・ pick b2c3c4d commit message 2 ・・・ ==== $ git push -f origin master

Sự cố 18: Sau khi release phát hiện thấy có bug và muốn trở về như cũ

Do phát sinh thiệt hại nên những thứ đã merge và maste cần phải chuyển tạm thời về như cũ Tuy nhiên không thể revert từng commit 1 nếu có nhiều commit

$ git log --oneline --graph ==== * 1x3y5z7 - Merge pull request |\ | * a2b45c7 - [develop] commit 3 * | dbc65f4 - commit 2 * | f0b0a91 - commit 1 ==== $ git revert -m 1 commit merge>

Nếu vẫn chưa có ai kéo phần code này về thì có thể làm đươc cả những việc sau nữa(Cái này nguy hiểm)

$ git reset ORIG_HEAD $ git push -f origin master

Sự cố 19:Khi sau khi chuyển lại và đã commit thêm một số commit mới nhưng không thể merge lại được

Ở sự cố 18, khi đã xoá bỏ merge, tiến hành chỉnh sửa nhưng merge lại không được

commit revert không có nghĩa là sẽ ko còn commit merge nữa mà là làm ngược lại những việc mà commit merge làm

Thế nên, khi merge lại thì cần phải revert lại commit revert

$ git checkout develop $ git commit -m "commit message" $ git checkout master $ git revert $ git merge develop

Khi muốn thử những điều trên ở một môi trường an toàn thì

Đúng là nếu chưa quen mà tự nhiên lại thực hành luôn thì rất vất vả.

Những ai đã có sản phầm thì có thể clone về và dùng cái đó để test thử

Nếu thế thì cho dù có bị thất bại thì cũng không gây ảnh hưởng gì đến môi trường làm việc chính.

Những ai chưa có sản phẩm có thể lên Github, tìm hiếm 1 repo nào đó nổi tiếng, clone về rồi thực hành

Tuy nhiên cần chú ý 1 điều là những cái này chỉ được thử ở trên môi trường local thôi nhé

Nếu muốn thử cả ở trên remote nữa thì có thể chuẩn bị sẵn môi trường test trên Github, clone về rồi thử

Hình dung cơ bản như sau

$ git clone $ git remote -v $ git remote rm origin $ git remote add origin

Video liên quan

Chủ Đề