2015-04-17 20 views
10

Tôi có một tệp mà tôi đã đổi tên và sau đó chỉnh sửa. Tôi muốn nói với Git về việc đổi tên, chứ không phải thay đổi nội dung. Đó là, tôi muốn giai đoạn xóa tên tập tin cũ, và việc bổ sung các nội dung tập tin cũ với tên tập tin mới.Làm cách nào để đổi tên mà không có chỉnh sửa tiếp theo trong git?

Vì vậy, tôi có điều này:

Changes not staged for commit: 

     deleted: old-name.txt 

Untracked files: 

     new-name.txt 

nhưng muốn một trong hai điều này:

Changes to be committed: 

     new file: new-name.txt 
     deleted: old-name.txt 

Changes not staged for commit: 

     modified: new-name.txt 

hay này:

Changes to be committed: 

     renamed: old-name.txt -> new-name.txt 

Changes not staged for commit: 

     modified: new-name.txt 

(nơi biện pháp tương tự nên là 100%).

Tôi không thể nghĩ ra cách đơn giản để thực hiện việc này.

Có cú pháp để nhận nội dung của một phiên bản cụ thể của một tệp cụ thể không và thêm phần này vào khu vực dàn dựng git theo một đường dẫn cụ thể không?

Phần xóa, với git rm, là tốt:

$ git rm old-name.txt 

Đó là add một phần của đổi tên Tôi đang phải vật lộn với. (Tôi có thể lưu các nội dung mới, kiểm tra một bản sao mới (cho nội dung cũ), mv trong vỏ, git add, và sau đó phục hồi các nội dung mới, nhưng điều đó có vẻ như một cách rất dài xung quanh!)

Cảm ơn!

+4

Đã thử 'git mv'? – vaultah

+1

'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

Trả lời

12

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

+1

Tùy chọn ưa thích của bạn được liệt kê là đổi tên thành cũ, sau đó sử dụng 'git mv'. –

+1

Ngoài ra, có thể đáng lưu ý rằng bạn có thể thực hiện 'git stash' để trở về trạng thái ban đầu, sau đó thực hiện' git mv' để đổi tên. Sau khi cam kết, bạn có thể sử dụng 'git stash --pop', với một xung đột hợp nhất có thể, để quay trở lại vị trí của bạn. –

+1

@BrianJ: vâng, đó (mv, sau đó git mv) chắc chắn là dễ nhất ở đây. Nó có thể khó khăn hơn nếu bạn đã tạo một tệp mới với tên cũ, vì không có "khe trống" để di chuyển các mảnh ghép 15 vòng quanh trong trường hợp đó :-) – torek

4

@torek đưa ra một câu trả lời rất rõ ràng và đầy đủ. Có rất nhiều chi tiết rất hữu ích ở đó; nó cũng đáng đọc.

Nhưng, vì lợi ích của những người trong một cuộc chạy đua, mấu chốt của giải pháp rất đơn giản cho là thế này:

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 

(. Nó chỉ là mv lại rằng tôi đã mất tích, để làm cho git mv thể)

Vui lòng upvote @ torek's câu trả lời nếu bạn thấy điều này hữu ích.

Các vấn đề liên quan