Git không thực sự đổi tên. Chúng là tất cả được tính theo kiểu "sau khi thực tế": git so sánh một cam kết với một cam kết khác và, tại thời điểm so sánh, quyết định xem có đổi tên hay không. Điều này có nghĩa rằng cho dù git xem xét một cái gì đó "đổi tên" thay đổi động. Tôi biết bạn đang hỏi về một cam kết thậm chí bạn chưa thực hiện, nhưng chịu với tôi, điều này thực sự tất cả đều buộc trong (nhưng câu trả lời sẽ được lâu dài).
Khi bạn hỏi git (thông qua git show
hoặc git log -p
hoặc git diff HEAD^ HEAD
) "những gì đã xảy ra trong các cam kết cuối cùng", nó chạy một diff của trước đó cam kết (HEAD^
hoặc HEAD~1
hay thực tế liệu SHA-1 cho trước cam kết — bất kỳ điều nào trong số này sẽ làm để xác định nó) và cam kết hiện tại (HEAD
). Trong việc tạo ra sự khác biệt đó, nó có thể phát hiện ra rằng đã từng là old.txt
và không còn nữa; và không có new.txt
nhưng hiện tại đã có.
Những tên tệp này — các tệp đã từng tồn tại ở đó nhưng không phải và các tệp hiện không có — được đặt thành các dấu chấm được đánh dấu là "ứng cử viên để đổi tên". Sau đó, đối với mỗi tên trong đống, git so sánh "nội dung cũ" và "nội dung mới". So sánh cho đối sánh chính xác là siêu dễ dàng vì cách git giảm nội dung xuống SHA-1; nếu khớp chính xác không thành công, git chuyển sang tùy chọn "là nội dung ít nhất tương tự" khác để kiểm tra đổi tên. Với git diff
bước tùy chọn này được kiểm soát bởi cờ -M
. Với các lệnh khác, nó được thiết lập bởi các giá trị git config
của bạn hoặc mã hóa cứng vào lệnh.
Bây giờ, quay lại khu vực dàn dựng và git status
: những gì git lưu trữ trong chỉ mục/dàn khu vực về cơ bản là "một nguyên mẫu cho cam kết tiếp theo". Khi bạn git add
một cái gì đó, git lưu trữ nội dung tệp ngay tại thời điểm đó, tính toán SHA-1 trong quá trình và sau đó lưu trữ SHA-1 trong chỉ mục. Khi bạn git rm
một cái gì đó, git lưu trữ một lưu ý trong chỉ mục nói rằng "tên đường dẫn này đang được cố ý loại bỏ trên cam kết tiếp theo".
Lệnh git status
, sau đó, chỉ cần thực hiện một khác biệt — hoặc thực sự, hai khác biệt: HEAD
so với chỉ mục, cho những gì sẽ được cam kết; và chỉ mục so với cây công việc, cho những gì có thể là (nhưng chưa được) sẽ được cam kết.
Trong trường hợp khác biệt đầu tiên, git sử dụng cùng một cơ chế như mọi khi để phát hiện đổi tên. Nếu có đường dẫn trong cam kết HEAD
đã biến mất trong chỉ mục và đường dẫn trong chỉ mục mới và không có trong cam kết HEAD
, đó là một ứng cử viên cho việc phát hiện đổi tên. Lệnh git status
sẽ cố định phát hiện đổi tên thành "bật" (và giới hạn số tệp là 200; chỉ với một ứng cử viên để phát hiện đổi tên giới hạn này là rất nhiều).
Điều này có ý nghĩa gì đối với trường hợp của bạn? Vâng, bạn đã đổi tên một tệp (không sử dụng git mv
, nhưng nó không thực sự quan trọng bởi vì git status
tìm đổi tên hoặc không tìm thấy nó, tại thời điểm git status
) và bây giờ có phiên bản mới hơn, khác nhau của tệp mới.
Nếu bạn git add
phiên bản mới, phiên bản mới hơn được chuyển vào repo và SHA-1 của nó nằm trong chỉ mục và khi số khác git status
sẽ khác so với phiên bản cũ và cũ. Nếu chúng ít nhất là "50% tương tự" (giá trị được bảo đảm cho git status
), git sẽ cho bạn biết tệp được đổi tên.
Tất nhiên, git add
-ing các sửa đổi nội dung không phải là khá gì bạn yêu cầu: bạn muốn làm một trung gian cam kết mà tập tin được chỉ đổi tên, ví dụ, một cam kết với một cây với tên mới , nhưng những nội dung cũ.
Bạn không có để thực hiện việc này, vì tất cả phát hiện đổi tên động ở trên. Nếu bạn muốn để làm điều đó (vì bất kỳ lý do gì) ... tốt, git không làm cho mọi thứ trở nên dễ dàng.
Cách đơn giản nhất cũng giống như bạn đề xuất: di chuyển nội dung đã sửa đổi ở đâu đó ra khỏi đường, sử dụng git checkout -- old-name.txt
, sau đó git mv old-name.txt new-name.txt
, sau đó cam kết. git mv
cả hai sẽ đổi tên tệp trong vùng chỉ mục/dàn dựng và đổi tên phiên bản cây công việc.
Nếu git mv
có một lựa chọn --cached
như git rm
không, bạn có thể chỉ git mv --cached old-name.txt new-name.txt
và sau đó git commit
. Bước đầu tiên sẽ đổi tên tệp trong chỉ mục mà không cần chạm vào cây công việc. Nhưng nó không: nó nhấn mạnh vào việc ghi đè lên phiên bản cây công việc, và nó khẳng định rằng tên cũ phải tồn tại trong cây công việc để bắt đầu.
Phương pháp một bước để thực hiện việc này mà không cần chạm vào cây công việc là sử dụng git update-index --index-info
, nhưng điều đó cũng hơi lộn xộn (dù sao tôi cũng sẽ hiển thị nó). May mắn thay, có một điều cuối cùng chúng ta có thể làm.Tôi đã thiết lập tình huống tương tự bạn đã có, bằng cách đổi tên tên cũ sang cái mới và sửa đổi các file:
$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: old-name.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
new-name.txt
Những gì chúng ta làm bây giờ là, đầu tiên, tay đặt các tập tin trở lại dưới tên cũ của nó , sau đó sử dụng git mv
để chuyển đổi một lần nữa để cái tên mới:
$ mv new-name.txt old-name.txt
$ git mv old-name.txt new-name.txt
lần này git mv
cập nhật tên trong chỉ mục, nhưng giữ các nội dung ban đầu như chỉ số SHA-1, tuy nhiên di chuyển các work- phiên bản cây (nội dung mới) vào vị trí trong công tác cây:
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: old-name.txt -> new-name.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: new-name.txt
Bây giờ chỉ cần git commit
để thực hiện một cam kết với đổi tên tại chỗ, nhưng không phải là nội dung mới.
(Lưu ý rằng điều này phụ thuộc vào có không phải là một tập tin mới với tên cũ!)
gì về việc sử dụng git update-index
? Vâng, trước hết chúng ta hãy có được những thứ trở lại "thay đổi trong công việc-tree, chỉ số phù hợp ĐẦU cam kết" nhà nước:
$ git reset --mixed HEAD # set index=HEAD, leave work-tree alone
Bây giờ chúng ta hãy xem những gì trong chỉ số cho old-name.txt
:
$ git ls-files --stage -- old-name.txt
100644 2b27f2df63a3419da26984b5f7bafa29bdf5b3e3 0 old-name.txt
Vì vậy, những gì chúng ta cần git update-index --index-info
làm là để tiêu diệt các mục nhập cho old-name.txt
nhưng tạo một entry khác giống hệt nhau cho new-name.txt
:
$ (git ls-files --stage -- old-name.txt;
git ls-files --stage -- old-name.txt) |
sed -e \
'1s/^[0-9]* [0-9a-f]*/000000 0000000000000000000000000000000000000000/' \
-e '2s/old-name.txt$/new-name.txt/' |
git update-index --index-info
(lưu ý: tôi đã phá vỡ t anh ta lên trên cho mục đích đăng bài, đó là tất cả một dòng khi tôi gõ nó vào; trong sh/bash, nó sẽ làm việc bị hỏng như thế này, cho các dấu gạch chéo ngược tôi thêm vào để tiếp tục lệnh "sed"). Có một số cách khác để thực hiện việc này, nhưng chỉ cần trích xuất mục chỉ mục hai lần và sửa đổi mục nhập đầu tiên thành lần xóa và thứ hai với tên mới có vẻ dễ nhất ở đây, do đó lệnh sed
. Thay thế đầu tiên thay đổi chế độ tệp (100644 nhưng bất kỳ chế độ nào sẽ được chuyển thành tất cả các số không) và SHA-1 (khớp với bất kỳ SHA-1 nào, thay thế bằng tất cả các giá trị đặc biệt của SHI SHA-1) và thứ hai rời khỏi chế độ và Chỉ SHA-1 trong khi thay thế tên.
Khi chỉ mục cập nhật kết thúc, chỉ mục đã ghi lại việc xóa đường dẫn cũ và thêm đường dẫn mới (với cùng chế độ và SHA-1 như trong đường dẫn cũ).
Lưu ý rằng điều này có thể không thành công nếu chỉ mục có mục nhập chưa được nhấn cho old-name.txt
vì có thể có các giai đoạn khác (1 đến 3) cho tệp.
Đã thử 'git mv'? – vaultah
'git mv' sẽ không hoạt động nếu tệp gốc đã bị xóa hoặc đường dẫn đích tồn tại. Bạn cần thực hiện quá trình lưu/khôi phục mà bạn đã phác thảo ... trong đó, bạn có thể sử dụng 'git mv' thay vì' mv + git add'. Vì git không theo dõi 'new-name.txt' khi bạn thực hiện thay đổi, vì nó không thể giúp tách biệt những thay đổi đó. – hinerm