paint-brush
Viết lại lịch sử Git với sự tự tin: Hướng dẫntừ tác giả@omerosenbaum
2,007 lượt đọc
2,007 lượt đọc

Viết lại lịch sử Git với sự tự tin: Hướng dẫn

từ tác giả Omer Rosenbaum18m2023/04/27
Read on Terminal Reader

dài quá đọc không nổi

Git là một hệ thống để ghi lại ảnh chụp nhanh của hệ thống tệp theo thời gian. Kho lưu trữ Git có ba “trạng thái” hoặc “cây”: chỉ mục, khu vực tổ chức và cây làm việc. Một thư mục đang hoạt động(ectrory) là bất kỳ thư mục nào trên hệ thống tệp của chúng tôi có repo Git được liên kết với nó.
featured image - Viết lại lịch sử Git với sự tự tin: Hướng dẫn
Omer Rosenbaum HackerNoon profile picture

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

Khi xảy ra sự cố với Git, nhiều kỹ sư cảm thấy bất lực (nguồn: XKCD)


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

Ghi chú trước khi chúng tôi bắt đầu

  1. 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ó .


  2. 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]

Ghi lại các thay đổi trong Git

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

Ba “cây” của repo Git (nguồn: https://youtu.be/ozA1V00GIT8)


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 một bài trước .


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 :

Bắt đầu một repo mới và tạo tệp đầu tiên trong đó (nguồn: https://youtu.be/ozA1V00GIT8)


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.

Tệp `1.txt` hiện chỉ là một phần của thư mục hoạt động (nguồn: https://youtu.be/ozA1V00GIT8)


Để sắp xếp nó, để thêm nó vào chỉ mục, hãy sử dụng git add 1.txt .

Sử dụng `git add` để sắp xếp tệp để tệp hiện cũng có trong chỉ mục (nguồn: https://youtu.be/ozA1V00GIT8)


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

Sử dụng `git commit` để tạo một đối tượng cam kết trong kho lưu trữ (nguồn: https://youtu.be/ozA1V00GIT8)


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), kiểm tra bài viết trước của tôi .


(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à một nhánh trong Git chỉ là một tham chiếu được đặt tên cho một cam kết . Vì vậy, theo mặc định, bạn chỉ có nhánh 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, dưới mui xe, HEAD chỉ là một tập tin. Nó bao gồm tên của chi nhánh với một số tiền tố.


Đã đế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:

Tệp `2.txt` nằm trong thư mục đang hoạt động và được lập chỉ mục sau khi sắp xếp nó bằng `git add` (nguồn: https://youtu.be/ozA1V00GIT8)


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 .

Lúc đầu, một đối tượng cam kết mới đã được tạo — `main` vẫn trỏ đến lần xác nhận trước đó (nguồn: https://youtu.be/ozA1V00GIT8)


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.

`git commit` cũng cập nhật nhánh đang hoạt động để trỏ đến đối tượng cam kết mới được tạo (nguồn: https://youtu.be/ozA1V00GIT8)


Hoàn tác các thay đổi

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

Đang đặt lại `main` thành “Commit 1” (nguồn: https://youtu.be/ozA1V00GIT8)


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ạo cam kết mới (nguồn: https://youtu.be/ozA1V00GIT8)


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ỏ đến cùng một đối tượng cây (của thư mục gốc bao gồm 1.txt2.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 .

Đặt lại Git --Mixed

Đã đế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”.

Bước đầu tiên của `git reset --mixed` cũng giống như `git reset --soft` (nguồn: https://youtu.be/ozA1V00GIT8)


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.

Bước thứ hai của `git reset --mixed` là khớp chỉ mục với `HEAD` mới (nguồn: https://youtu.be/ozA1V00GIT8)


Đã đế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ó:

Tạo “Cam kết 2.2” (nguồn: https://youtu.be/ozA1V00GIT8)


Đặt lại Git --Hard

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

Bước đầu tiên của `git reset --hard` cũng giống như `git reset --soft` (nguồn: https://youtu.be/ozA1V00GIT8)


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 .

Bước thứ hai của `git reset --hard` cũng giống như `git reset --mixed` (nguồn: https://youtu.be/ozA1V00GIT8)


Đã đế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.

Bước thứ ba của `git reset --hard` khớp trạng thái của thư mục đang hoạt động với trạng thái của chỉ mục (nguồn: https://youtu.be/ozA1V00GIT8)


(**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:


  • Nếu chúng tôi sử dụng git reset --soft , chúng tôi sẽ hoàn tác bước cam kết.


  • Nếu chúng tôi sử dụng git reset --mixed , chúng tôi cũng hoàn tác bước dàn dựng.


  • Nếu chúng tôi sử 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.

Kịch bản thực tế cuộc sống!

Cảnh 1

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:

Tạo “Cam kết 2.3” (nguồn: https://youtu.be/ozA1V00GIT8)


Ồ, ô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:

Hoàn tác các bước dàn dựng và cam kết (nguồn: https://youtu.be/ozA1V00GIT8)


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 đó:

Bổ sung thêm lời bài hát yêu thương (nguồn: https://youtu.be/ozA1V00GIT8)


Hãy tiếp tục, tạo giai đoạn và cam kết tệp của bạn:

Tạo cam kết mới với trạng thái mong muốn (nguồn: https://youtu.be/ozA1V00GIT8)


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

git reset hiện đã có trong hộp công cụ của chúng tôi (nguồn: https://youtu.be/ozA1V00GIT8)


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.

Kịch bản #2

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:

Tạo `new.txt` và “Commit 3” (nguồn: https://youtu.be/ozA1V00GIT8)


Ố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ứ haigit 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:

Tình huống #2: trạng thái hiện tại so với trạng thái mong muốn (nguồn: https://youtu.be/ozA1V00GIT8)


Bạn sẽ nhận thấy ba thay đổi:


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


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


  3. 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”:

Thay đổi `chính`; “Commit 3 bị mờ vì nó vẫn ở đó, chỉ là không truy cập được (nguồn: https://youtu.be/ozA1V00GIT8)


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ự):

Tạo nhánh `feature` (nguồn: https://youtu.be/ozA1V00GIT8)


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 :

Cam kết với nhánh `feature` (nguồn: https://youtu.be/ozA1V00GIT8)


Và bạn đã đạt đến trạng thái mong muốn 🎉

Kịch bản #3

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:

Tạo “Cam kết 4” (nguồn: https://youtu.be/ozA1V00GIT8)


Ồ, 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:

Hoàn tác cam kết và dàn dựng bằng cách sử dụng `git reset --mixed HEAD~1` (nguồn: https://youtu.be/ozA1V00GIT8)


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 :

Cam kết riêng (nguồn: https://youtu.be/ozA1V00GIT8)


Đẹp 😎

Kịch bản #4

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:

Cam kết mới (nguồn: https://youtu.be/ozA1V00GIT8)


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:

Cam kết mới trên `main` có màu xanh lam (nguồn: https://youtu.be/ozA1V00GIT8)


Và trạng thái mong muốn?

Chúng tôi muốn cam kết "màu xanh lam" nằm trên một nhánh khác, `hiện tại`, (nguồn: https://youtu.be/ozA1V00GIT8)


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 :

Chuyển sang nhánh `hiện có` (nguồn: https://youtu.be/ozA1V00GIT8)


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.

Sử dụng `git cherry-pick` (nguồn: https://youtu.be/ozA1V00GIT8)


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 :

Đặt lại `chính` (nguồn: https://youtu.be/ozA1V00GIT8)


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.

Kịch bản #5

Đượ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:

Một cam kết khác (nguồn: https://youtu.be/ozA1V00GIT8)


push nó đến máy chủ từ xa:

(nguồn: https://youtu.be/ozA1V00GIT8)


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

Sử dụng `git revert` để hoàn tác các thay đổi (nguồn: https://youtu.be/ozA1V00GIT8)


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:

Thực hiện lại các thay đổi (nguồn: https://youtu.be/ozA1V00GIT8)


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 :

Hộp công cụ của chúng tôi (nguồn: https://youtu.be/ozA1V00GIT8)


Kịch bản #6

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:

Một cam kết khác (nguồn: https://youtu.be/ozA1V00GIT8)


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 `git reset -- hard`? (nguồn: https://youtu.be/ozA1V00GIT8)


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.

`git log` không giúp được gì trong trường hợp này (nguồn: https://youtu.be/ozA1V00GIT8)


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 .


`git reflog` cho chúng tôi biết vị trí của `HEAD` (nguồn: https://youtu.be/ozA1V00GIT8)


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 HEADHEAD~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ó:

(nguồn: https://youtu.be/ozA1V00GIT8)


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 :

Đầu ra của `git log -g` (nguồn: https://youtu.be/ozA1V00GIT8)


Ở 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”):

(nguồn: https://youtu.be/ozA1V00GIT8)


Và bây giờ, nếu chúng ta git log :

Lịch sử của chúng ta đã trở lại!!! (nguồn: https://youtu.be/ozA1V00GIT8)


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:

(nguồn: https://youtu.be/ozA1V00GIT8)


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:

Hộp công cụ của chúng tôi khá phong phú! (nguồn: https://youtu.be/ozA1V00GIT8)


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ìm hiểu thêm về Git

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, kênh YouTube của tôi bao gồm nhiều khía cạnh của Git và nội bộ của nó; bạn được chào đón kiểm tra xem (ý định chơi chữ 😇)

Giới thiệu về tác giả

Omer Rosenbaum là CTO và đồng sáng lập của bơi , một công cụ dành cho nhà phát triển giúp các nhà phát triển và nhóm của họ quản lý kiến thức về cơ sở mã của họ bằng tài liệu nội bộ cập nhật. Omer là người sáng lập Học viện An ninh Check Point và là Trưởng nhóm An ninh mạng tại ITC, một tổ chức giáo dục đào tạo các chuyên gia tài năng để phát triển sự nghiệp trong lĩnh vực công nghệ.


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 Kênh YouTube ngắn gọn .


Xuất bản lần đầu tại đây