Ngày git từ chối cú push của tôi
Một ngày nọ, git đơn giản là ngừng cho phép tôi lưu lại công việc của mình. Cú push của tôi thất bại với một lỗi máy chủ mà tôi chưa từng thấy trước đây — không phải xung đột merge, không phải vấn đề quyền truy cập, chỉ là một sự từ chối thẳng thừng từ đầu bên kia. Tôi đã commit một cách vui vẻ suốt nhiều tuần, và giờ đây câu lệnh thường nhật nhất trong ngày của tôi lại không chịu hoàn thành.
Nguyên nhân không phải là một lỗi trong git. Mà là vì trong những tuần ấy tôi đã âm thầm nhồi nhét vào repository của mình hàng nghìn thứ chẳng có lý do gì để nằm ở đó, và cuối cùng nó đã trở nên quá nặng để nhấc lên.
Cú push không chịu đi
Ban đầu tôi cho rằng đó là chuyện ngẫu nhiên — một trục trặc mạng, một máy chủ đang trải qua giây phút tồi tệ. Nhưng nó cứ tiếp diễn, đều đặn, mỗi lần. Khối lượng tích tụ của repository dường như là nguyên nhân: cú push cứ thất bại theo cùng một cách mỗi lần, và dù có thử lại bao nhiêu lần cũng chẳng thể khắc phục một vấn đề mang tính cấu trúc chứ không phải nhất thời.
Khi cuối cùng tôi nhìn vào những gì repository thực sự chứa đựng, câu trả lời thật đáng xấu hổ. Phần mã nguồn — thứ mà một repository sinh ra để chứa — chỉ là một thiểu số nhỏ trong đó. Phần áp đảo là dữ liệu: hàng nghìn tệp được sinh ra, các bộ nhớ đệm, và các sản phẩm biên dịch, mỗi thứ đều được theo dõi, đánh phiên bản và mang theo một cách trung thành trong mọi thao tác.
Một repo lặng lẽ trở thành một nhà kho như thế nào
Không ai quyết định đưa hàng gigabyte dữ liệu vào quản lý phiên bản cả. Nó xảy ra từng bước một, mỗi bước trông có vẻ hợp lý. Bạn sinh ra vài tệp và chúng rơi vào thư mục dự án của bạn. Bạn chạy một câu lệnh add, có lẽ hơi cẩu thả, và chúng theo vào cùng. Nó hoạt động. Chẳng có gì phàn nàn. Thế là bạn làm lại lần nữa, rồi lần nữa, và repository tích tụ dữ liệu theo cách một ngăn kéo tích tụ đồ lộn xộn — một cách vô hình, cho đến ngày nó không chịu đóng lại nữa.
Đến lúc git từ chối cú push của tôi, tôi đang theo dõi hàng nghìn tệp dữ liệu. Tôi chưa bao giờ đưa ra lựa chọn có ý thức để đặt chúng dưới sự quản lý phiên bản. Tôi chỉ đơn giản là chưa bao giờ đưa ra lựa chọn để giữ chúng ở ngoài.
Dữ liệu và mã nguồn muốn những điều trái ngược nhau
Lý do việc trộn lẫn chúng gây tổn hại là vì chúng được đánh phiên bản vì những lý do hoàn toàn khác nhau. Mã nguồn nhỏ gọn, có ý nghĩa, và thay đổi theo những cách mà bạn muốn thấy từng dòng một — git được xây dựng chính xác cho điều đó. Dữ liệu thì lớn, mờ đục, và có xu hướng thay đổi toàn bộ; một bản diff từng dòng của nó hầu như chẳng nói cho bạn điều gì, và việc lưu trữ mọi phiên bản quá khứ của nó mãi mãi phần lớn là lãng phí. Git tuyệt vời ở công việc thứ nhất; lịch sử tiêu chuẩn của nó thường không phù hợp với công việc thứ hai, ít nhất là khi dữ liệu trở nên lớn hoặc thay đổi liên tục.
Đặt chúng lại với nhau và bạn nhận được điều tệ nhất của cả hai. Lịch sử của bạn phình to với những thay đổi khổng lồ mà bạn chẳng bao giờ đọc được một cách có ý nghĩa, mọi cú clone và push đều kéo lê tất cả chúng theo, và thứ nhỏ bé, quý giá — phần mã nguồn thực sự — cuối cùng bị chôn vùi trong một repository chủ yếu là vật dằn.
Cách khắc phục: vạch ra ranh giới mà bạn đã bỏ qua
Việc sửa chữa về mặt khái niệm thì tầm thường và hơi tẻ nhạt: nói cho git biết, một cách rõ ràng, cái gì không cần theo dõi. Các tệp được sinh ra, các bộ nhớ đệm, các sản phẩm biên dịch — tất cả đều bị loại trừ, và repository trở lại đúng với những gì lẽ ra nó luôn phải là: một mái nhà cho những thứ mà tôi đã chủ ý tạo ra — mã nguồn, cấu hình, tài liệu, kiểm thử — chứ không phải khối lượng đầu ra được sinh ra. Dữ liệu chuyển đến nơi mà những thứ lớn, có thể tái tạo thuộc về — được tạo ra theo nhu cầu, hoặc lưu trữ ở một chỗ được xây dựng cho các tệp lớn, nhưng không bao giờ còn bị đánh phiên bản từng dòng như thể nó là mã nguồn nữa.
Cú push đã đi qua ngay lần thử đầu tiên. Cách khắc phục chẳng có gì khôn khéo. Nó chỉ là một ranh giới mà lẽ ra tôi nên vạch ra ngay ngày đầu tiên nhưng chưa bao giờ làm.
Thói quen nằm bên dưới: mã nguồn so với phái sinh
Bài học còn lại không thực sự nói về git. Nó là một câu hỏi mà giờ đây tôi đặt ra về mọi tệp mà một dự án sinh ra: tôi đã tạo ra cái này, hay mã của tôi đã sinh ra nó? Nó là mã nguồn — thứ không thể thay thế mà một con người đã viết ra — hay nó là phái sinh, một đầu ra có thể được tạo lại bằng cách chạy lại mã nguồn?
Mã nguồn nói chung thuộc về quản lý phiên bản. Những tệp phái sinh lớn hoặc cồng kềnh thường thì không — chúng chỉ nên được commit khi tính tái lập hoặc quy trình làm việc thực sự đòi hỏi, và lý tưởng nhất là vẫn có thể tái tạo từ mã nguồn vào bất cứ lúc nào. Nhiều repository mục ruỗng thành một mớ hỗn độn không thể quản lý nổi đều như vậy bởi vì chúng đánh mất sự phân biệt đó — bằng cách đối xử với một thứ phái sinh như thể nó quý giá, và để cho những thứ luôn có thể được tái tạo chất đống bên cạnh những thứ không bao giờ có thể thay thế được.
Git đã không hỏng hóc ngày hôm đó. Nó chỉ cuối cùng khiến tôi nhận ra rằng tôi đã ngừng giữ mã nguồn và đầu ra ở những chỗ riêng biệt của chúng, và đã yêu cầu một công cụ làm một công việc mà nó chưa bao giờ được xây dựng để làm.
— Không tín hiệu, không lợi nhuận, không phải lời khuyên đầu tư.