Là một nhà phát triển, bạn luôn làm việc với Git.
Bạn đã bao giờ đi đến một điểm mà bạn nói: “Uh-oh, tôi vừa làm gì vậy?”
Bài đăng này sẽ cung cấp cho bạn các công cụ để tự tin viết lại lịch sử.
Tôi cũng đã nói chuyện trực tiếp về nội dung của bài đăng này. Nếu bạn thích một video (hoặc muốn xem nó cùng với việc đọc) - bạn có thể tìm thấy nó
Tôi đang viết một cuốn sách về Git! Bạn có muốn đọc các phiên bản ban đầu và cung cấp phản hồi không? Gửi email cho tôi: [email protected]
Trước khi hiểu cách hoàn tác mọi thứ trong Git, trước tiên bạn nên hiểu cách chúng tôi ghi lại các thay đổi trong Git. Nếu bạn đã biết tất cả các thuật ngữ, vui lòng bỏ qua phần này.
Sẽ rất hữu ích khi nghĩ về Git như một hệ thống ghi lại ảnh chụp nhanh của hệ thống tệp theo thời gian. Xem xét kho lưu trữ Git, nó có ba “trạng thái” hoặc “cây”:
Thông thường, khi chúng tôi làm việc trên mã nguồn của mình, chúng tôi làm việc từ một thư mục đang hoạt động . Thư mục đang hoạt động(ectrory) (hoặc working tree ) là bất kỳ thư mục nào trong hệ thống tệp của chúng tôi có kho lưu trữ được liên kết với nó.
Nó chứa các thư mục và tệp của dự án của chúng tôi và cũng có một thư mục có tên .git
. Tôi đã mô tả nội dung của thư mục .git
chi tiết hơn trong
Sau khi bạn thực hiện một số thay đổi, bạn có thể muốn ghi lại chúng trong kho lưu trữ của mình. Một kho lưu trữ (viết tắt: repo ) là một tập hợp các cam kết , mỗi cam kết là một kho lưu trữ về cây làm việc của dự án trông như thế nào vào một ngày trong quá khứ, cho dù trên máy của bạn hay của người khác.
Một kho lưu trữ cũng bao gồm những thứ khác ngoài các tệp mã của chúng tôi, chẳng hạn như HEAD
, các nhánh, v.v.
Ở giữa, chúng tôi có chỉ mục hoặc khu vực tổ chức ; hai thuật ngữ này có thể hoán đổi cho nhau. Khi chúng tôi checkout
một nhánh, Git sẽ điền vào chỉ mục tất cả nội dung tệp được kiểm tra lần cuối vào thư mục làm việc của chúng tôi và chúng trông như thế nào khi chúng được kiểm xuất ban đầu.
Khi chúng tôi sử dụng git commit
, cam kết được tạo dựa trên trạng thái của chỉ mục .
Vì vậy, chỉ mục hoặc khu vực tổ chức là sân chơi của bạn cho lần cam kết tiếp theo. Bạn có thể làm việc và làm bất cứ điều gì bạn muốn với chỉ mục , thêm tệp vào chỉ mục, xóa mọi thứ khỏi chỉ mục và sau đó chỉ khi bạn đã sẵn sàng, bạn mới tiếp tục và cam kết với kho lưu trữ.
Đã đến lúc bắt tay vào làm 🙌🏻
Sử dụng git init
để khởi tạo kho lưu trữ mới. Viết một số văn bản vào một tệp có tên 1.txt
:
Trong số ba trạng thái cây được mô tả ở trên, 1.txt
hiện ở đâu?
Trong cây làm việc, vì nó chưa được đưa vào chỉ mục.
Để sắp xếp nó, để thêm nó vào chỉ mục, hãy sử dụng git add 1.txt
.
Bây giờ, chúng ta có thể sử dụng git commit
để chuyển các thay đổi của mình vào kho lưu trữ.
Bạn đã tạo một đối tượng cam kết mới, bao gồm một con trỏ tới cây mô tả toàn bộ cây đang hoạt động. Trong trường hợp này, nó sẽ chỉ là 1.txt
trong thư mục gốc. Ngoài một con trỏ tới cây, đối tượng cam kết bao gồm siêu dữ liệu, chẳng hạn như dấu thời gian và thông tin tác giả.
Để biết thêm thông tin về các đối tượng trong Git (chẳng hạn như xác nhận và cây),
(Vâng, "kiểm tra", ý định chơi chữ 😇)
Git cũng cho chúng ta biết giá trị SHA-1 của đối tượng cam kết này. Trong trường hợp của tôi, đó là c49f4ba
(chỉ có 7 ký tự đầu tiên của giá trị SHA-1, để tiết kiệm dung lượng).
Nếu bạn chạy lệnh này trên máy của mình, bạn sẽ nhận được một giá trị SHA-1 khác, vì bạn là một tác giả khác; đồng thời, bạn sẽ tạo cam kết trên một dấu thời gian khác.
Khi chúng ta khởi tạo repo, Git sẽ tạo một nhánh mới (được đặt tên là main
theo mặc định). Vàmain
. Điều gì xảy ra nếu bạn có nhiều chi nhánh? Làm cách nào để Git biết nhánh nào là nhánh đang hoạt động?
Git có một con trỏ khác gọi là HEAD
, con trỏ này (thường) trỏ tới một nhánh, sau đó trỏ tới một cam kết. Nhân tiện,HEAD
Đã đến lúc giới thiệu nhiều thay đổi hơn đối với repo!
Bây giờ, tôi muốn tạo một cái khác. Vì vậy, hãy tạo một tệp mới và thêm nó vào chỉ mục, như trước đây:
Bây giờ, đã đến lúc sử dụng git commit
. Điều quan trọng, git commit
thực hiện hai việc:
Đầu tiên, nó tạo một đối tượng cam kết, do đó, có một đối tượng trong cơ sở dữ liệu đối tượng bên trong của Git với giá trị SHA-1 tương ứng. Đối tượng cam kết mới này cũng trỏ đến cam kết gốc. Đó là cam kết mà HEAD
trỏ đến khi bạn viết lệnh git commit
.
Thứ hai, git commit
di chuyển con trỏ của nhánh đang hoạt động — trong trường hợp của chúng tôi, đó sẽ là main
, để trỏ đến đối tượng cam kết mới được tạo.
Để viết lại lịch sử, hãy bắt đầu với việc hoàn tác quá trình giới thiệu một cam kết. Để làm được điều đó, chúng ta sẽ tìm hiểu lệnh git reset
, một công cụ siêu mạnh.
git reset --soft
Vì vậy, bước cuối cùng bạn đã làm trước đây là git commit
, điều này thực sự có nghĩa là hai điều - Git đã tạo một đối tượng cam kết và di chuyển main
, nhánh đang hoạt động. Để hoàn tác bước này, hãy sử dụng lệnh git reset --soft HEAD~1
.
Cú pháp HEAD~1
đề cập đến cha đầu tiên của HEAD
. Nếu tôi có nhiều hơn một cam kết trong biểu đồ cam kết, hãy nói “Cam kết 3” trỏ đến “Cam kết 2”, nghĩa là trỏ đến “Cam kết 1”.
Và giả sử HEAD
đang trỏ đến “Commit 3”. Bạn có thể sử dụng HEAD~1
để chỉ “Cam kết 2” và HEAD~2
sẽ chỉ “Cam kết 1”.
Vì vậy, quay lại lệnh: git reset --soft HEAD~1
Lệnh này yêu cầu Git thay đổi bất cứ thứ gì mà HEAD
đang trỏ tới. (Lưu ý: Trong sơ đồ bên dưới, tôi sử dụng *HEAD
cho “bất cứ thứ gì mà HEAD
trỏ tới”). Trong ví dụ của chúng tôi, HEAD
đang trỏ đến main
. Vì vậy, Git sẽ chỉ thay đổi con trỏ của main
thành HEAD~1
. Nghĩa là, main
sẽ trỏ đến “Commit 1”.
Tuy nhiên, lệnh này không ảnh hưởng đến trạng thái của chỉ mục hoặc cây làm việc. Vì vậy, nếu bạn sử dụng git status
bạn sẽ thấy 2.txt
được phân chia theo giai đoạn, giống như trước khi bạn chạy git commit
.
Còn git log?
Nó sẽ bắt đầu từ HEAD
, chuyển đến main
và sau đó đến “Commit 1”. Lưu ý rằng điều này có nghĩa là “Commit 2” không còn có thể truy cập được từ lịch sử của chúng tôi.
Điều đó có nghĩa là đối tượng cam kết của “Cam kết 2” đã bị xóa? 🤔
Không, nó không bị xóa. Nó vẫn nằm trong cơ sở dữ liệu đối tượng bên trong của Git.
Nếu bạn đẩy lịch sử hiện tại ngay bây giờ, bằng cách sử dụng git push
, Git sẽ không đẩy “Cam kết 2” đến máy chủ từ xa, nhưng đối tượng cam kết vẫn tồn tại trên bản sao cục bộ của kho lưu trữ.
Bây giờ, hãy cam kết lại — và sử dụng thông báo cam kết của “Commit 2.1” để phân biệt đối tượng mới này với “Commit 2” ban đầu:
Tại sao “Commit 2” và “Commit 2.1” khác nhau? Ngay cả khi chúng tôi đã sử dụng cùng một thông báo cam kết và mặc dù chúng trỏ đến1.txt
và 2.txt
), chúng vẫn có các dấu thời gian khác nhau vì chúng được tạo vào các thời điểm khác nhau.
Trong hình vẽ trên, tôi giữ “Commit 2” để nhắc bạn rằng nó vẫn tồn tại trong cơ sở dữ liệu đối tượng bên trong của Git. Cả “Commit 2” và “Commit 2.1” giờ đây đều trỏ đến “Commit 1", nhưng chỉ có thể truy cập “Commit 2.1” từ HEAD
.
Đã đến lúc đi lùi và lùi xa hơn nữa. Lần này, hãy sử dụng git reset --mixed HEAD~1
(lưu ý: --mixed
là công tắc mặc định cho git reset
).
Lệnh này bắt đầu giống như lệnh git reset --soft HEAD~1
. Có nghĩa là nó lấy con trỏ của bất cứ thứ gì HEAD
đang trỏ tới, là nhánh main
và đặt nó thành HEAD~1
, trong ví dụ của chúng tôi - “Cam kết 1”.
Tiếp theo, Git còn tiến xa hơn, hoàn tác hiệu quả những thay đổi mà chúng tôi đã thực hiện đối với chỉ mục. Tức là thay đổi chỉ số sao cho khớp với HEAD
hiện tại, HEAD
mới sau khi đã thiết lập ở bước đầu tiên.
Nếu chúng ta đã chạy git reset --mixed HEAD~1
, điều đó có nghĩa là HEAD
sẽ được đặt thành HEAD~1
(“Commit 1”), và sau đó Git sẽ khớp chỉ mục với trạng thái “Commit 1” — trong trường hợp này, nó có nghĩa là 2.txt
sẽ không còn là một phần của chỉ mục.
Đã đến lúc tạo một cam kết mới với trạng thái của “Cam kết 2” ban đầu. Lần này chúng ta cần tạo lại giai đoạn 2.txt
trước khi tạo nó:
Tiếp tục, hoàn tác nhiều hơn nữa!
Hãy tiếp tục và chạy lệnh git reset --hard HEAD~1
Một lần nữa, Git bắt đầu với giai đoạn --soft
, đặt bất kỳ HEAD
nào đang trỏ tới ( main
), thành HEAD~1
(“Cam kết 1”).
Càng xa càng tốt.
Tiếp theo, chuyển sang giai đoạn --mixed
, so khớp chỉ mục với HEAD
. Nghĩa là, Git hoàn tác việc dàn dựng 2.txt
.
Đã đến lúc thực hiện bước --hard
khi Git tiến xa hơn nữa và khớp thư mục đang hoạt động với giai đoạn của chỉ mục. Trong trường hợp này, điều đó có nghĩa là cũng xóa 2.txt
khỏi thư mục đang hoạt động.
(**Lưu ý: Trong trường hợp cụ thể này, tệp không bị theo dõi, vì vậy nó sẽ không bị xóa khỏi hệ thống tệp; mặc dù vậy, việc hiểu git reset
không thực sự quan trọng).
Vì vậy, để giới thiệu một thay đổi đối với Git, bạn có ba bước. Bạn thay đổi thư mục làm việc, chỉ mục hoặc khu vực tổ chức, sau đó bạn thực hiện một ảnh chụp nhanh mới với những thay đổi đó. Để hoàn tác những thay đổi này:
git reset --soft
, chúng tôi sẽ hoàn tác bước cam kết.
git reset --mixed
, chúng tôi cũng hoàn tác bước dàn dựng.
git reset --hard
, chúng tôi sẽ hoàn tác các thay đổi đối với thư mục đang hoạt động. Vì vậy, trong một tình huống thực tế, hãy viết “Tôi yêu Git” vào một tệp ( love.txt
), vì tất cả chúng ta đều yêu thích Git 😍. Hãy tiếp tục, lên sân khấu và cam kết điều này:
Ồ, ôi!
Thật ra, tôi không muốn bạn phạm phải nó.
Điều tôi thực sự muốn bạn làm là viết thêm vài lời yêu thương vào tệp này trước khi cam kết.
Bạn có thể làm gì?
Chà, một cách để khắc phục điều này là sử dụng git reset --mixed HEAD~1
, hoàn tác hiệu quả cả hành động cam kết và dàn dựng mà bạn đã thực hiện:
Vì vậy, các điểm main
là "Cam kết 1" một lần nữa và love.txt
không còn là một phần của chỉ mục nữa. Tuy nhiên, tập tin vẫn còn trong thư mục làm việc. Bây giờ bạn có thể tiếp tục và thêm nhiều nội dung hơn vào đó:
Hãy tiếp tục, tạo giai đoạn và cam kết tệp của bạn:
Làm tốt lắm 👏🏻
Bạn có lịch sử rõ ràng, hay ho về “Commit 2.4” chỉ vào “Commit 1”.
Bây giờ chúng tôi có một công cụ mới trong hộp công cụ của mình, git reset
💪🏻
Công cụ này siêu, siêu hữu ích và bạn có thể hoàn thành hầu hết mọi thứ với nó. Nó không phải lúc nào cũng là công cụ thuận tiện nhất để sử dụng, nhưng nó có khả năng giải quyết hầu hết mọi tình huống viết lại lịch sử nếu bạn sử dụng nó cẩn thận.
Đối với người mới bắt đầu, tôi khuyên bạn chỉ nên sử dụng git reset
cho hầu hết thời gian bạn muốn hoàn tác trong Git. Khi bạn cảm thấy thoải mái với nó, đã đến lúc chuyển sang các công cụ khác.
Chúng ta hãy xem xét một trường hợp khác.
Tạo một tệp mới có tên new.txt
; giai đoạn và cam kết:
Ối. Trên thực tế, đó là một sai lầm. Bạn đang sử dụng main
và tôi muốn bạn tạo cam kết này trên một nhánh tính năng. Tệ của tôi 😇
Có hai công cụ quan trọng nhất mà tôi muốn bạn rút ra từ bài đăng này. Thứ hai là git reset
. Điều đầu tiên và quan trọng hơn nhiều là đánh dấu trạng thái hiện tại so với trạng thái bạn muốn.
Đối với kịch bản này, trạng thái hiện tại và trạng thái mong muốn trông giống như sau:
Bạn sẽ nhận thấy ba thay đổi:
các điểm main
đối với “Cam kết 3” (màu xanh lam) ở trạng thái hiện tại, nhưng đối với “Cam kết 2.4” ở trạng thái mong muốn.
nhánh feature
không tồn tại ở trạng thái hiện tại, nhưng nó tồn tại và trỏ đến “Cam kết 3” ở trạng thái mong muốn.
HEAD
trỏ đến main
ở trạng thái hiện tại và để feature
ở trạng thái mong muốn.
Nếu bạn có thể vẽ cái này và bạn biết cách sử dụng git reset
, bạn chắc chắn có thể thoát khỏi tình huống này.
Vì vậy, một lần nữa, điều quan trọng nhất là hít một hơi và rút ra điều này.
Quan sát hình vẽ trên, ta làm thế nào để đi từ trạng thái hiện tại đến trạng thái mong muốn?
Tất nhiên, có một số cách khác nhau, nhưng tôi sẽ chỉ trình bày một tùy chọn cho từng tình huống. Vui lòng chơi xung quanh với các tùy chọn khác.
Bạn có thể bắt đầu bằng cách sử dụng git reset --soft HEAD~1
. Điều này sẽ thiết lập main
để trỏ đến lần xác nhận trước đó, “Cam kết 2.4”:
Nhìn lại sơ đồ hiện tại so với mong muốn, bạn có thể thấy rằng bạn cần một nhánh mới, phải không? Bạn có thể sử dụng git switch -c feature
cho nó hoặc git checkout -b feature
(thực hiện điều tương tự):
Lệnh này cũng cập nhật HEAD
để trỏ đến nhánh mới.
Vì bạn đã sử dụng git reset --soft
, nên bạn đã không thay đổi chỉ mục, vì vậy nó hiện có chính xác trạng thái mà bạn muốn cam kết — thật tiện lợi! Bạn có thể chỉ cần cam kết với nhánh feature
:
Và bạn đã đạt đến trạng thái mong muốn 🎉
Sẵn sàng để áp dụng kiến thức của bạn cho các trường hợp bổ sung?
Thêm một số thay đổi vào love.txt
, đồng thời tạo một tệp mới có tên cool.txt
. Giai đoạn họ và cam kết:
Ồ, rất tiếc, thực ra tôi muốn bạn tạo hai cam kết riêng biệt, mỗi cam kết có một thay đổi 🤦🏻
Bạn muốn tự mình thử cái này?
Bạn có thể hoàn tác các bước cam kết và dàn dựng:
Làm theo lệnh này, chỉ mục không còn bao gồm hai thay đổi đó nữa, nhưng cả hai thay đổi đó vẫn còn trong hệ thống tệp của bạn. Vì vậy, bây giờ, nếu bạn chỉ tạo giai đoạn love.txt
, bạn có thể cam kết nó một cách riêng biệt và sau đó thực hiện tương tự cho cool.txt
:
Đẹp 😎
Tạo một tệp mới ( new_file.txt
) với một số văn bản và thêm một số văn bản vào love.txt
. Giai đoạn cả hai thay đổi và cam kết chúng:
Rất tiếc 🙈🙈
Vì vậy, lần này, tôi muốn nó ở một nhánh khác, nhưng không phải là một nhánh mới , mà là một nhánh đã tồn tại.
vậy, bạn có thể làm gì?
Tôi sẽ cho bạn một gợi ý. Câu trả lời thực sự ngắn gọn và thực sự dễ dàng. Chúng ta làm gì đầu tiên?
Không, không reset
. Chúng tôi vẽ. Đó là điều đầu tiên cần làm, vì nó sẽ khiến mọi thứ khác trở nên dễ dàng hơn rất nhiều. Vì vậy, đây là trạng thái hiện tại:
Và trạng thái mong muốn?
Làm thế nào để bạn chuyển từ trạng thái hiện tại sang trạng thái mong muốn, điều gì sẽ dễ dàng nhất?
Vì vậy, một cách sẽ là sử dụng git reset
như bạn đã làm trước đây, nhưng có một cách khác mà tôi muốn bạn thử.
Đầu tiên, di chuyển HEAD
để trỏ đến nhánh existing
:
Theo trực giác, những gì bạn muốn làm là thực hiện các thay đổi được giới thiệu trong cam kết màu xanh lam và áp dụng những thay đổi này (“sao chép-dán”) lên trên nhánh existing
. Và Git có một công cụ dành riêng cho việc đó.
Để yêu cầu Git thực hiện các thay đổi được giới thiệu giữa cam kết này và cam kết gốc của nó và chỉ áp dụng những thay đổi này trên nhánh đang hoạt động, bạn có thể sử dụng git cherry-pick
. Lệnh này nhận các thay đổi được giới thiệu trong bản sửa đổi đã chỉ định và áp dụng chúng cho cam kết hoạt động.
Nó cũng tạo một đối tượng cam kết mới và cập nhật nhánh đang hoạt động để trỏ đến đối tượng mới này.
Trong ví dụ trên, tôi đã chỉ định mã định danh SHA-1 của cam kết đã tạo, nhưng bạn cũng có thể sử dụng git cherry-pick main
, vì cam kết có những thay đổi mà chúng tôi đang áp dụng là thay main
đang trỏ đến.
Nhưng chúng tôi không muốn những thay đổi này tồn tại trên nhánh main
. git cherry-pick
chỉ áp dụng các thay đổi cho nhánh existing
. Làm thế nào bạn có thể loại bỏ chúng khỏi main
?
Một cách là switch
về main
, sau đó sử dụng git reset --hard HEAD~1
:
Bạn làm được rồi! 💪🏻
Lưu ý rằng git cherry-pick
thực sự tính toán sự khác biệt giữa cam kết được chỉ định và cha mẹ của nó, sau đó áp dụng chúng cho cam kết hoạt động. Điều này có nghĩa là đôi khi, Git sẽ không thể áp dụng những thay đổi đó vì bạn có thể gặp xung đột, nhưng đó là một chủ đề cho một bài đăng khác.
Ngoài ra, lưu ý rằng bạn có thể yêu cầu Git cherry-pick
các thay đổi được giới thiệu trong bất kỳ cam kết nào, không chỉ các cam kết được tham chiếu bởi một nhánh.
Chúng tôi đã có được một công cụ mới, vì vậy chúng tôi có git reset
cũng như git cherry-pick
dưới vành đai của mình.
Được rồi, vậy một ngày khác, một repo khác, một vấn đề khác.
Tạo một cam kết:
Và push
nó đến máy chủ từ xa:
Ừm, rất tiếc 😓…
Tôi chỉ nhận thấy một cái gì đó. Có một lỗi đánh máy ở đó. Tôi đã viết This is more tezt
thay vì This is more text
. Rất tiếc. Vậy vấn đề lớn bây giờ là gì? Tôi push
sửa, điều đó có nghĩa là người khác có thể đã pull
sửa những thay đổi đó.
Nếu tôi ghi đè những thay đổi đó bằng cách sử dụng git reset
, như chúng ta đã làm cho đến nay, chúng ta sẽ có những lịch sử khác nhau và tất cả có thể bị phá vỡ. Bạn có thể viết lại bản sao repo của riêng mình bao nhiêu tùy thích cho đến khi bạn push
nó.
Một khi bạn push
thay đổi, bạn cần chắc chắn rằng không ai khác đã tìm nạp những thay đổi đó nếu bạn định viết lại lịch sử.
Ngoài ra, bạn có thể sử dụng một công cụ khác gọi là git revert
. Lệnh này lấy cam kết mà bạn đang cung cấp cho nó và tính toán Diff từ cam kết gốc của nó, giống như git cherry-pick
, nhưng lần này, nó tính toán các thay đổi ngược lại.
Vì vậy, nếu trong cam kết được chỉ định, bạn đã thêm một dòng, thì ngược lại sẽ xóa dòng đó và ngược lại.
git revert
đã tạo một đối tượng cam kết mới, nghĩa là nó là phần bổ sung cho lịch sử. Bằng cách sử dụng git revert
, bạn đã không viết lại lịch sử. Bạn đã thừa nhận sai lầm trong quá khứ của mình và cam kết này là sự thừa nhận rằng bạn đã phạm sai lầm và giờ bạn đã sửa nó.
Một số người sẽ nói đó là cách trưởng thành hơn. Một số người sẽ nói rằng nó không phải là lịch sử rõ ràng như bạn sẽ nhận được nếu bạn sử dụng git reset
để viết lại cam kết trước đó. Nhưng đây là một cách để tránh viết lại lịch sử.
Bây giờ bạn có thể sửa lỗi đánh máy và cam kết lại:
Hộp công cụ của bạn hiện đã được tải bằng một công cụ sáng bóng mới, revert
:
Hoàn thành một số công việc, viết một số mã và thêm nó vào love.txt
. Giai đoạn thay đổi này và cam kết:
Tôi cũng làm như vậy trên máy của mình và tôi đã sử dụng phím Mũi tên Up
trên bàn phím để cuộn trở lại các lệnh trước đó, sau đó tôi nhấn Enter
và… Chà.
Rất tiếc.
Có phải tôi vừa sử dụng git reset --hard
? 😨
Điều gì thực sự đã xảy ra? Git đã di chuyển con trỏ đến HEAD~1
, vì vậy lần xác nhận cuối cùng, với tất cả công việc quý giá của tôi, không thể truy cập được từ lịch sử hiện tại. Git cũng bỏ phân tầng tất cả các thay đổi từ khu vực tổ chức, sau đó so khớp thư mục làm việc với trạng thái của khu vực tổ chức.
Đó là, mọi thứ phù hợp với trạng thái này khi công việc của tôi… biến mất.
Freak ra thời gian. Hoảng sợ.
Nhưng, thực sự, có một lý do để hoảng sợ? Không hẳn… Chúng tôi là những người thoải mái. Chúng ta làm gì? Chà, theo trực giác, cam kết có thực sự biến mất không? Không. Tại sao không? Nó vẫn tồn tại bên trong cơ sở dữ liệu nội bộ của Git.
Nếu tôi biết nó ở đâu, tôi sẽ biết giá trị SHA-1 xác định cam kết này, chúng tôi có thể khôi phục nó. Tôi thậm chí có thể hoàn tác việc hoàn tác và reset
lại về cam kết này.
Vì vậy, điều duy nhất tôi thực sự cần ở đây là SHA-1 của cam kết "đã xóa".
Vì vậy, câu hỏi là, làm thế nào để tôi tìm thấy nó? git log
có hữu ích không?
Vâng, không thực sự. git log
sẽ chuyển đến HEAD
, trỏ đến main
, trỏ đến cam kết gốc của cam kết mà chúng tôi đang tìm kiếm. Sau đó, git log
sẽ theo dõi lại chuỗi gốc, không bao gồm cam kết với công việc quý giá của tôi.
Rất may, những người rất thông minh đã tạo ra Git cũng đã tạo một kế hoạch dự phòng cho chúng tôi và kế hoạch đó được gọi là reflog
.
Trong khi bạn làm việc với Git, bất cứ khi nào bạn thay đổi HEAD
, bạn có thể thực hiện điều này bằng cách sử dụng git reset
, cũng như các lệnh khác như git switch
hoặc git checkout
, Git sẽ thêm một mục vào reflog
.
Chúng tôi tìm thấy cam kết của chúng tôi! Đó là cái bắt đầu bằng 0fb929e
.
Chúng ta cũng có thể liên tưởng đến nó bằng “biệt danh” của nó — HEAD@{1}
. Vì vậy, chẳng hạn như Git sử dụng HEAD~1
để truy cập cấp độ gốc đầu tiên của HEAD
và HEAD~2
để chỉ cấp độ gốc thứ hai của HEAD
, v.v., Git sử dụng HEAD@{1}
để chỉ cấp độ gốc reflog đầu tiên của HEAD
, nơi HEAD
đã trỏ đến trong bước trước.
Chúng tôi cũng có thể yêu cầu git rev-parse
hiển thị cho chúng tôi giá trị của nó:
Một cách khác để xem reflog
là sử dụng git log -g
, yêu cầu git log
thực sự xem xét reflog
:
Ở trên, chúng ta thấy rằng reflog
, giống như HEAD
, trỏ đến main
, trỏ đến “Commit 2”. Nhưng cha của mục đó trong reflog
trỏ tới “Commit 3”.
Vì vậy, để quay lại “Cam kết 3”, bạn chỉ cần sử dụng git reset --hard HEAD@{1}
(hoặc giá trị SHA-1 của “Cam kết 3”):
Và bây giờ, nếu chúng ta git log
:
Chúng tôi đã tiết kiệm trong ngày! 🎉👏🏻
Điều gì sẽ xảy ra nếu tôi sử dụng lại lệnh này? Và chạy git commit --reset HEAD@{1}
? Git sẽ đặt HEAD
thành nơi HEAD
trỏ đến trước lần reset
cuối cùng, nghĩa là “Cam kết 2”. Chúng ta có thể tiếp tục đi cả ngày:
Bây giờ hãy nhìn vào hộp công cụ của chúng ta, nó chứa đầy những công cụ có thể giúp bạn giải quyết nhiều trường hợp xảy ra sự cố trong Git:
Với những công cụ này, giờ đây bạn đã hiểu rõ hơn về cách hoạt động của Git. Có nhiều công cụ hơn cho phép bạn viết lại lịch sử một cách cụ thể, git rebase
), nhưng bạn đã học được rất nhiều điều trong bài đăng này. Trong các bài đăng trong tương lai, tôi cũng sẽ đi sâu vào git rebase
.
Công cụ quan trọng nhất, thậm chí còn quan trọng hơn năm công cụ được liệt kê trong hộp công cụ này, là viết bảng trắng về tình hình hiện tại so với tình hình mong muốn. Hãy tin tôi về điều này, nó sẽ làm cho mọi tình huống dường như bớt khó khăn hơn và giải pháp rõ ràng hơn.
Tôi cũng đã nói chuyện trực tiếp về nội dung của bài đăng này. Nếu bạn thích một video (hoặc muốn xem nó cùng với việc đọc) - bạn có thể tìm thấy nó
Nói chung,
Omer có bằng Thạc sĩ Ngôn ngữ học tại Đại học Tel Aviv và là người tạo ra
Xuất bản lần đầu tại đây