== Bài Học về Lịch Sử ==

Một hệ quả tất yếu của đặc tính phân tán của Git là việc lịch sử có thể biên soạn lại một cách
dễ dàng. Nhưng nếu bạn xáo trộn quá khứ, hãy cẩn thận: chỉ biên soạn lại các phần trong lịch sử
chỉ khi bạn sở hữu nó một mình. Cũng giống như việc các quốc gia tranh cãi không kết thúc xem ai là người
tận tâm, hành động nào là tàn ác, nếu một người khác có một bản sao mà lịch sử của nó lại khác với
cái của bạn, bạn sẽ gặp rắc rối ngay khi cần tương tác với họ.

Một số nhà phát triển phần mềm quả quyết rằng lịch sử không thể thay đổi, tất cả mọi thứ.
Một số khác lại cho rằng chỉnh sửa lại cấu trúc trước khi phát hành nó ra
đại chúng. Git chấp nhận cả hai quan điểm. Giống như việc nhân bản, tạo nhánh và hòa trộn,
viết lại lịch sử đơn giản chỉ là một quyền lực mà Git trao cho bạn. Bạn có thể làm thế
nếu muốn.

=== Dừng Lại Sửa Chữa ===

Bạn vừa mới commit, nhưng lại ước rằng mình đã gõ những dòng chú thích có nội dung khác phải không? Thế thì hãy chạy:

 $ git commit --amend

để thay đổi chú thích cuối cùng. Bạn giật mình vì quên thêm các tập tin vào? Chạy lệnh *git add* để
thêm nó vào, và sau đó lại chạy lệnh ở trên.

Bạn muốn thêm vài chỉnh sửa vào lần cuối mình đã commit ư? Thế thì cứ sửa chúng đi và sau đó chạy lệnh:

 $ git commit --amend -a

=== ... Và Sau đó là Nhiều Lần ===

Giả sử vấn đề trục trặc ở lần commit cách đây mười lần. Sau một buổi làm việc dài, bạn đã
tạo ra hàng tá các lần commit. Nhưng bạn không hoàn toàn hài lòng với cách mà chúng được
tổ chức, và một số lần commit cần được soạn lại phần mô tả. Thế thì hãy gõ:

 $ git rebase -i HEAD~10

và 10 lần commit cuối sẽ xuất hiện trong $EDITOR yêu thích của bạn. Dưới đây là một đoạn trích ví dụ:

    pick 5c6eb73 Added repo.or.cz link
    pick a311a64 Reordered analogies in "Work How You Want"
    pick 100834f Added push target to Makefile

Lần commit cũ đứng trước lần mới hơn trong danh sách, không giống như kết quả khi chạy lệnh `log`.
Ở đây, 5c6eb73 là lần commit cũ nhất, và 100834f là mới nhất. Thế thì:

- Xóa bỏ các lần commit bằng cách xóa các dòng tương ứng. Giống như lệnh revert, nhưng không
ghi biên bản: nó sẽ coi như là lần commit đó chưa từng bao giờ tồn tại.
- Đặt lại thứ tự các lần commit bằng cách thay đổi thứ tự các dòng.
- Thay thế `pick` bằng:
   * `edit` để đánh dấu lần commit đó là dành cho việc tu bổ.
   * `reword` để thay đổi phần chú giải.
   * `squash` để hòa trộn với lần commit trước.
   * `fixup` để hòa trộn với lần commit trước và bỏ qua việc ghi lại phần chú giải.

Ví dụ, chúng ta chẳng hạn thay thế `pick` ở dòng thứ hai bằng `squash`:

    pick 5c6eb73 Added repo.or.cz link
    squash a311a64 Reordered analogies in "Work How You Want"
    pick 100834f Added push target to Makefile

Sau đó chúng ta ghi lại thay đổi và thoát ra. Git trộn lần a311a64 vào 5c6eb73. Vì vậy *squash* trộn
với lần kế trước nó: có thể nghĩ đây là quá trình ``nén dữ liệu''.

Hơn thế nữa, Git sau đó tổ hợp nhật ký của chúng và hiện tại và chỉnh sửa lại. Lệnh
*fixup* bỏ qua bước này; việc sửa nhật ký đơn giản là bỏ qua.

Nếu bạn đánh dấu một lần commit bằng *edit*, Git đưa bạn trở lại quá khứ, tới lần
commit lâu nhất đó. Bạn có thể tu bổ một lần commit cũ như đã mô tả ở phần trên,
và thậm chí tạo ra các lần commit mới ở chỗ này. Một khi bạn đã hài lòng với việc
``retcon'', hãy chạy 'cỗ máy thời gian' bằng cách chạy lệnh:

 $ git rebase --continue

Git sửa commits cho tới *edit* kế tiếp, hoặc tới hiện tại nếu không còn việc gì cần phải làm.

Bạn còn có thể bãi bỏ việc rebase bằng lệnh:

 $ git rebase --abort

Do vậy cứ commit thoải mái và thường xuyên bởi vì bạn có thể dọn dẹp cho gọn gàng sau này bằng lệnh rebase.

=== Thay Đổi Riêng Sắp Xếp Sau ===

Bạn đang làm việc trên một dự án đang hoạt động. Bạn đã tạo ra một số lần commit tại máy tính của mình, và
sau đó bạn đồng bộ hóa với cây chính thức bằng cách hòa trộn. Chu kỳ này tự lặp chính nó một số lần trước khi bạn thực sự push tới cây trên máy chủ trung tâm.

Nhưng hiện tại lịch sử bản sao Git trên máy tính của bạn là một mớ hỗn độn của những lần thay đổi trên máy tính riêng và máy chính thức. Bạn muốn thấy tất cả các thay đổi của riêng mình trong một đoạn liên tục không ngắt quãng, và sau tất cả các thay đổi từ kho chính thức.

Đây chính là công việc dành cho lệnh *git rebase* đã được miêu tả ở trên. Trong nhiều trường hợp bạn có thể sử dụng
cờ *--onto* và tránh xa sự tương tác với các máy tính khác.

Xem thêm trong *git help rebase* để thấy được chi tiết các ví dụ dành cho lệnh đáng kinh ngạc này. Bạn có thể chia cắt các lần commit. Bạn còn có thể xắp xếp lại các nhánh của một cấu trúc cây.

Hãy cẩn thận: rebase là một lệnh mạnh mẽ. Với những lần rebases phức tạp, trước hết hãy tạo ra
một bản sao lưu dự phòng bằng lệnh *git clone*.

=== Viết Lại Lịch Sử ===

Thỉnh thoảng, bạn muốn việc quản lý mã nguồn giống việc người ta sơn vẽ chân dung một
con người, tẩy xóa chúng từ lịch sử như theo ý của Stalinesque. Ví dụ,
giả sử chúng ta có ý định phát hành dự án, nhưng lại liên đới đến một tập tin mà nó phải được giữ bí mật
vì lý do nào đó. Chẳng hạn như tôi đã quên khi ghi lại số thẻ tín dụng vào trong một tập tin
văn bản và ngẫu nhiên nó được thêm vào trong dự án. Việc xóa tập tin này là
chưa đủ, bởi vì ta có thể đọc nó từ lần commit cũ. Chúng ta phải gỡ bỏ
tập tin này từ tất cả các lần đã commit:

 $ git filter-branch --tree-filter 'rm top/secret/file' HEAD

Xem *git help filter-branch*, nội dung của nó sẽ thảo luận về ví dụ này và đưa ra một cách thức
nhanh hơn. Đại thể, lệnh *filter-branch* giúp bạn thay đổi cả một chương lớn của lịch sử chỉ
chỉ bằng một lệnh đơn.

Sau này, thư mục +.git/refs/original+ mô tả trạng thái của công việc trước khi thực hiện. Kiểm tra lệnh filter-branch đã làm những thứ bạn muốn chưa, sau đó xóa thư mục này đi nếu bạn muốn chạy lệnh filter-branch lần nữa.

Cuối cùng, thay thế bản sao của dự án của bạn bằng phiên bản bạn đã sửa lại nếu bạn muốn tương thích với chúng sau này.

=== Tự Tạo Lịch Sử ===

[[makinghistory]]
Bạn muốn chuyển đổi dự án của mình sang sử dụng Git? Nếu nó được quản lý bởi một hệ thống nổi tiếng hơn, thế thì nếu may mắn sẽ có người nào đó đã viết sẵn một đoạn kịch bản để xuất toàn bộ lịch sử ra thành Git.

Nếu không, thì nên xem xét đến việc sử dụng lệnh *git fast-import*, lệnh này đọc văn bản đầu vào ở một định dạng
riêng để mà tạo ra một lịch sử cho Git từ ban đầu. Thông thường một script sử dụng
lệnh này là một nhóm lệnh tổ hợp với nhau và chỉ chạy một lần, di chuyển một dự án chỉ bằng
một lệnh đơn.

Dưới đây là một ví dụ, dán danh sách theo sau đâu vào một tập tin tạm thời nào đó, chẳng hạn như là `/tmp/history`:
----------------------------------
commit refs/heads/master
committer Alice <alice@example.com> Thu, 01 Jan 1970 00:00:00 +0000
data <<EOT
Initial commit.
EOT

M 100644 inline hello.c
data <<EOT
#include <stdio.h>

int main() {
  printf("Hello, world!\n");
  return 0;
}
EOT


commit refs/heads/master
committer Bob <bob@example.com> Tue, 14 Mar 2000 01:59:26 -0800
data <<EOT
Replace printf() with write().
EOT

M 100644 inline hello.c
data <<EOT
#include <unistd.h>

int main() {
  write(1, "Hello, world!\n", 14);
  return 0;
}
EOT

----------------------------------

Thế thì tạo một kho Git từ thư mục chứa các tập tin tạm thời này bằng cách gõ:

 $ mkdir project; cd project; git init
 $ git fast-import --date-format=rfc2822 < /tmp/history

Bạn có thể checkout phiên bản cuối của dự án với:

 $ git checkout master .

Lệnh *git fast-export* chuyển đổi bất kỳ một kho chứa nào thành định dạng
phù hợp với lệnh *git fast-import*, và bạn có thể nghiên cứu nó để tạo riêng cho mình một chương trình xuất,
và cũng làm như thế để tạo thành kho chuyên chở ở định dạng con người có thể đọc được. Thực vậy,
những lệnh này có thể gửi một kho chứa ở dạng văn bản thông qua một kênh chỉ cho phép văn bản truyền đi.

=== Vị Trí Nào Phát Sinh Lỗi? ===

Bạn vừa mới phát hiện ra một đặc tính không hoạt động trong chương trình mà bạn chắc chắn là nó đã hoạt động vài tháng trước. Tệ quá! Bạn tự hỏi là lỗi bắt đầu từ chỗ nào nhỉ? Nếu như chỉ có mình bạn kiểm tra cũng như phát triển đặc tính này.

Lúc này thì đã quá muộn rồi. Tuy nhiên, chỉ cần bạn commit thường xuyên, Git
có thể xác định vị trí của trục trặc:

 $ git bisect start
 $ git bisect bad HEAD
 $ git bisect good 1b6d

Git checks out một trạng thái nằm giữa chúng. Kiểm tra đặc tính kỹ thuật, và nếu nó vẫn hỏng:

 $ git bisect bad

Nếu không thì thay "bad" bằng "good". Git sẽ chuyên chở bạn qua lại nửa bước giữa hai trạng
thái là phiên bản "tốt" và "xấu", thu hẹp khả năng lại. Sau khi lặp đi lặp lại một số lần,
việc tìm kiếm nhị phân này sẽ dẫn bạn đến lần commit mà nó làm nguyên nhân dẫn đễ hỏng hóc.
Một khi bạn đã hoàn tất việc điều tra, trở lại trạng thái nguyên bản
của bạn bằng cách gõ:

 $ git bisect reset

Thay vì thử nghiệm mọi thay đổi một cách thủ công, hãy tự động hóa sự tìm kiếm này bằng cách chạy:

 $ git bisect run my_script

Git sử dụng giá trị trả về của lệnh đã cho, thông thường là từ các đoạn kịch bản, để
quyết định là lệnh đã thực hiện tốt hay không: lệnh sẽ trả về giá trị 0
khi tốt, 125 khi sự thay đổi bị bỏ qua, và bất kỳ giá trị nào khác nằm giữa 1
và 127 nếu gặp lỗi. Một giá trị âm sẽ bãi bỏ lệnh bisect.

Bạn có thể làm nhiều hơn thế: trang trợ giúp giảng giải cho bạn làm thế nào để hiểu
được lệnh bisect làm việc như thế nào, xem xét hay xem lại nhật ký lệnh bisect,
và loại trừ các thay đổi ngớ ngẩn để tăng tốc độ tìm kiếm.

=== Ai Đã Làm Nó Sai? ===

Giống như các hệ thống quản lý mã nguồn khác, Git cũng có lệnh blame:

 $ git blame bug.c

lệnh này chú thích tất cả các dòng có trong tập tin được chỉ ra cho thấy ai là người cuối cùng sửa nó, và là khi nào. Không giống các hệ thống quản lý mã nguồn khác, hành động này hoạt động không cần có mạng, việc đọc chỉ đơn thuần từ ổ đĩa của máy tính cá nhân.

=== Kinh Nghiệm Riêng ===

Trong một hệ thống quản lý mã nguồn tập trung, thay đổi lịch sử là một việc làm
khó khăn, và chỉ làm được thế nếu đó là người quản trị. Việc nhân bản, tạo nhánh và
trộn không thể thiếu việc truyền thông qua mạng. Cũng như thế với các tác vụ cơ bản khác
như là duyệt lịch sử, hay là commit một thay đổi. Trong một số hệ thống khác, người dùng
yêu cầu có kết nối mạng chỉ để xem các thay đổi của họ hay mở một tập tin
để biên tập.

Hệ thống tập trung không cho phép làm việc mà không có mạng, và đòi hỏi cơ sở hạ tầng mạng máy tính
đắt đỏ tốn kém, đặc biệt là khi số nhà phát triển phần mềm tăng lên. Điều quan trọng,
tất cả mọi tác vụ sẽ chậm hơn ở mức độ nào đó, thường thường
những người sử dụng sẽ lảng tránh việc sử dụng các lệnh cao cấp trừ phi nó thực sự cần thiết. Trừ những trường hợp
đặc biệt là các lệnh cơ bản. Khi những người dùng phải chạy
các lệnh chạy chậm, hiệu suất bị giảm bởi vì nó làm gián đoạn công việc của cả một dây truyền.

Tôi trực tiếp đã trải qua những hiện tượng đó. Git là hệ thống quản lý mã nguồn đầu tiên tôi sử dụng. 
Tôi nhanh chóng làm quen với nó, bị quyến rũ bởi những đặc tính kỹ thuật mà nó đã cung cấp.
Tôi đơn giản cho rằng các hệ thống khác thì cũng tương tự: việc chọn lựa một
hệ thống quản lý mã nguồn thì cũng chẳng khác việc chọn một trình biên soạn hay một
trình duyệt web.

Tôi sẽ sốc nếu như sau này bị bắt buộc sử dụng hệ thống quản lý mã nguồn tập trung. Một kết nối
Internet chậm chạp cũng chẳng phải là vấn đề lớn đối với Git, nhưng nó sẽ làm cho các
nhà phát triển phần mềm không thể chịu nổi khi nó cần sự tin cậy như ổ đĩa nội bộ. Thêm nữa,
tôi đã gặp một số mắc mớ với một số lệnh, mà chính nó
đã ngăn cản tôi làm việc một cách trôi chảy.

Khi tôi phải chạy những lệnh cần nhiều thời gian, việc làm ngắt quãng việc suy nghĩ
sẽ gây nên thiệt hại rất to lớn. Trong khi chờ cho việc truyền thông
với máy chủ hoàn tất, tôi sẽ phải làm một vài thứ gì đó khác để lấp chỗ trống, chẳng hạn như
lấy thư điện tử hay viết tài liệu. Sau một khoảng thời gian tôi quay trở lại nhiệm vụ
chính của mình, lệnh đã hoàn tất từ lâu rồi, và tôi phải lãng phí thêm nhiều thời gian nữa
để nhớ lại xem mình đang làm gì. Con người thường dở khi phải thay đổi mạch văn.

Ở đây còn có một hậu quả rất đáng quan tâm nữa: đoán trước được việc tắc nghẽn của mạng máy tính,
nhiều cá nhân riêng lẻ có thể chiếm dụng nhiều lưu lượng mạng hơn cần thiết trên các tác vụ
khác nhau để cố gắng giảm thiểu sự chậm trễ có thể xảy ra trong tương lai. Hậu quả cuối cùng là
sự quá tải quá mức, chính việc vô tình ủng hộ việc tiêu dùng cá nhân như thế đã tiêu tốn nhiều lưu lượng mạng hơn
và sau đó nó làm cho việc tắc nghẽn càng lúc càng trở nên tồi tệ hơn.

