Xử lý đổi tên tập tin trong git


440

Tôi đã đọc rằng khi đổi tên tệp trong git , bạn nên thực hiện bất kỳ thay đổi nào, thực hiện đổi tên và sau đó giai đoạn đổi tên tệp của bạn. Git sẽ nhận ra tệp từ nội dung, thay vì xem nó là tệp mới chưa được theo dõi và giữ lịch sử thay đổi.

Tuy nhiên, làm điều này tối nay tôi đã trở lại git mv.

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#

Đổi tên biểu định kiểu của tôi trong Finder từ iphone.cssthànhmobile.css

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   deleted:    css/iphone.css
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   css/mobile.css

Vì vậy, git bây giờ nghĩ rằng tôi đã xóa một tệp CSS và thêm một tệp mới. Không phải những gì tôi muốn, hãy hoàn tác việc đổi tên và để git thực hiện công việc.

> $ git reset HEAD .
Unstaged changes after reset:
M   css/iphone.css
M   index.html

Trở lại nơi tôi bắt đầu.

> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   index.html
#

Hãy sử dụng git mvthay thế.

> $ git mv css/iphone.css css/mobile.css
> $ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    css/iphone.css -> css/mobile.css
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   index.html
#

Có vẻ như chúng tôi tốt. Vậy tại sao git không nhận ra lần đổi tên lần đầu tiên khi tôi sử dụng Finder?


29
Git theo dõi nội dung, không phải tệp, vì vậy việc bạn đưa chỉ mục của mình vào trạng thái phù hợp không quan trọng - add+rmhoặc mv- nó tạo ra kết quả tương tự. Git sau đó sử dụng phát hiện đổi tên / sao chép của nó để cho bạn biết đó là đổi tên. Nguồn bạn trích dẫn là không chính xác, quá. Nó thực sự không quan trọng cho dù bạn sửa đổi + đổi tên trong cùng một cam kết hay không. Khi bạn thực hiện một khác biệt trên cả sửa đổi và đổi tên, phát hiện đổi tên sẽ xem đó là đổi tên + sửa đổi hoặc nếu sửa đổi là viết lại hoàn toàn, nó sẽ hiển thị như đã thêm và xóa - vẫn không thành vấn đề bạn thực hiện như thế nào nó
Cascabel

6
Nếu điều này là đúng, tại sao nó không phát hiện ra nó bằng cách đổi tên của tôi bằng Finder?
Greg K

26
git mv old newtự động cập nhật chỉ mục. Khi bạn đổi tên bên ngoài Git, bạn sẽ phải thực hiện git add newgit rm oldthực hiện các thay đổi cho chỉ mục. Một khi bạn đã làm điều này git statussẽ làm việc như bạn mong đợi.
Chris Johnsen

4
Tôi vừa chuyển một loạt các tập tin vào một public_htmlthư mục, được theo dõi trong git. Đã thực hiện git add .git commit, nó vẫn hiển thị một loạt các tệp 'đã xóa' git status. Tôi đã thực hiện một git commit -avà xóa đã được cam kết nhưng bây giờ tôi không có lịch sử trên các tập tin tồn tại public_htmlbây giờ. Luồng công việc này không suôn sẻ như tôi muốn.
Greg K

Câu trả lời:


352

Đối với git mvcác trang hướng dẫn nói

Chỉ mục được cập nhật sau khi hoàn thành thành công, [Hoài]

Vì vậy, lúc đầu, bạn phải tự cập nhật chỉ mục (bằng cách sử dụng git add mobile.css). Tuy nhiên
git status vẫn sẽ hiển thị hai tệp khác nhau

$ git status
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   index.html
#       new file:   mobile.css
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       deleted:    iphone.css
#

Bạn có thể nhận được một đầu ra khác nhau bằng cách chạy git commit --dry-run -a, kết quả là những gì bạn mong đợi:

Tanascius@H181 /d/temp/blo (master)
$ git commit --dry-run -a
# On branch master
warning: LF will be replaced by CRLF in index.html
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   index.html
#       renamed:    iphone.css -> mobile.css
#

Tôi không thể cho bạn biết chính xác lý do tại sao chúng tôi thấy những khác biệt giữa git status
git commit --dry-run -a, nhưng đây là một gợi ý từ Linus :

git thực sự thậm chí không quan tâm đến toàn bộ "phát hiện đổi tên" trong nội bộ, và bất kỳ cam kết nào bạn đã thực hiện với việc đổi tên đều hoàn toàn độc lập với các heuristic mà chúng ta sử dụng để hiển thị các đổi tên.

A dry-runsử dụng các cơ chế đổi tên thực sự, trong khi git statuscó lẽ không.


1
Bạn đã không đề cập đến bước mà bạn đã làm git add mobile.css. Nếu không, nó git status -asẽ chỉ "nhìn thấy" việc xóa iphone.csstệp được theo dõi trước đó nhưng sẽ không chạm vào mobile.csstệp mới, không bị theo dõi . Ngoài ra, git status -akhông hợp lệ với Git 1.7.0 trở lên. "Tình trạng git" không phải là "git commit --dry-run" nữa. trong kernel.org/pub/software/scm/git/docs/RelNotes-1.7.0.txt . Sử dụng git commit --dry-run -anếu bạn muốn chức năng này. Như những người khác đã nói, chỉ cần cập nhật chỉ mục và git statussẽ hoạt động như OP mong đợi.
Chris Johnsen

3
nếu bạn làm bình thường, git commitnó sẽ không cam kết tập tin được đổi tên và cây làm việc vẫn như vậy. git commit -ađánh bại khá nhiều khía cạnh của mô hình công việc / suy nghĩ của git, mọi thay đổi đều được cam kết. Điều gì xảy ra nếu bạn chỉ muốn đổi tên tệp, nhưng cam kết thay đổi index.htmltrong một cam kết khác?
knittl

@Chris: vâng, tất nhiên tôi đã thêm mobile.csscái mà tôi nên đề cập. Nhưng đó là điểm của câu trả lời của tôi: trang nam nói rằng the index is updatedkhi bạn sử dụng git-mv. Cảm ơn đã status -alàm rõ, tôi đã sử dụng git 1.6.4
tanascius

4
Câu trả lời chính xác! Tôi đang đập đầu vào tường cố gắng tìm hiểu tại sao git statuskhông phát hiện ra việc đổi tên. Chạy git commit -a --dry-runsau khi thêm các tệp "mới" của tôi cho thấy các tên đổi tên và cuối cùng đã cho tôi sự tự tin để cam kết!
stephen.hanson

1
Trong git 1.9.1 git statusbây giờ hành xử như thế nào git commit.
Jacques René Mesrine

77

Bạn phải thêm hai tệp đã sửa đổi vào chỉ mục trước khi git sẽ nhận ra đó là một động thái.

Sự khác biệt duy nhất giữa mv old newgit mv old newlà git mv cũng thêm các tệp vào chỉ mục.

mv old newsau đó git add -Acũng sẽ làm việc

Lưu ý rằng bạn không thể chỉ sử dụng git add .vì điều đó không thêm phần xóa vào chỉ mục.

Xem Sự khác biệt giữa "git add -A" và "git add."


3
Cảm ơn các git add -Aliên kết, rất hữu ích, vì tôi đã tìm kiếm các phím tắt như vậy!
PhiLho

5
Lưu ý rằng với git 2, git add . không thêm các loại bỏ vào chỉ mục.
Nick McCurdy

19

Điều tốt nhất là thử nó cho chính mình.

mkdir test
cd test
git init
touch aaa.txt
git add .
git commit -a -m "New file"
mv aaa.txt bbb.txt
git add .
git status
git commit --dry-run -a

Bây giờ git status và git commit --dry-run -a hiển thị hai kết quả khác nhau trong đó trạng thái git hiển thị bbb.txt khi một tệp mới / aaa.txt bị xóa và các lệnh --dry-run hiển thị tên đổi thực.

~/test$ git status

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   bbb.txt
#
# 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:    aaa.txt
#


/test$ git commit --dry-run -a

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   renamed:    aaa.txt -> bbb.txt
#

Bây giờ hãy tiếp tục và làm thủ tục.

git commit -a -m "Rename"

Bây giờ bạn có thể thấy rằng tập tin thực tế đã được đổi tên và những gì hiển thị trong trạng thái git là sai.

Đạo đức của câu chuyện: Nếu bạn không chắc liệu tệp của mình có được đổi tên hay không, hãy đưa ra "git commit --dry-run -a". Nếu nó cho thấy tập tin được đổi tên, bạn nên đi.


3
Đối với những gì quan trọng đối với Git, cả hai đều chính xác . Cái sau đang gần hơn với cách bạn, như người đi làm có thể nhìn thấy nó, mặc dù. Sự khác biệt thực sự giữa đổi tên và xóa + tạo chỉ ở cấp độ hệ điều hành / hệ thống tập tin (ví dụ như inode # so với inode # mới), điều mà Git không thực sự quan tâm lắm.
Alois Mahdal

15

Đối với git 1.7.x, các lệnh sau hoạt động với tôi:

git mv css/iphone.css css/mobile.css
git commit -m 'Rename folder.' 

Không cần thêm git, vì tệp gốc (ví dụ: css / mobile.css) đã có trong các tệp đã cam kết trước đó.


5
Điều này. Tất cả các câu trả lời khác là vô lý và phức tạp không cần thiết. Điều này duy trì lịch sử tệp giữa các lần xác nhận để hợp nhất trước / sau khi đổi tên tệp không bị hỏng.
Phlucious

10

bạn phải vào git add css/mobile.csstập tin mới và git rm css/iphone.css, vì vậy git biết về nó. sau đó nó sẽ hiển thị cùng một đầu ra tronggit status

bạn có thể thấy nó rõ ràng trong đầu ra trạng thái (tên mới của tệp):

# Untracked files:
#   (use "git add <file>..." to include in what will be committed)

và (tên cũ):

# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)

Tôi nghĩ đằng sau hậu trường git mvkhông gì khác hơn là một tập lệnh bao bọc thực hiện chính xác điều đó: xóa tệp khỏi chỉ mục và thêm nó dưới một tên khác


Tôi không nghĩ rằng tôi phải làm thế git rm css/iphone.cssbởi vì tôi nghĩ rằng điều này sẽ xóa lịch sử hiện có. Có lẽ tôi đang hiểu nhầm quy trình làm việc trong git.
Greg K

4
@Greg K: git rmsẽ không xóa lịch sử. Nó chỉ xóa một mục từ chỉ mục để cam kết tiếp theo sẽ không có mục. Tuy nhiên, nó vẫn sẽ tồn tại trong các cam kết của tổ tiên. Điều bạn có thể bối rối là (ví dụ) git log -- newsẽ dừng lại ở điểm mà bạn đã cam kết git mv old new. Nếu bạn muốn theo dõi đổi tên, sử dụng git log --follow -- new.
Chris Johnsen

9

Hãy suy nghĩ về các tập tin của bạn từ quan điểm git.

Hãy nhớ rằng git không theo dõi bất kỳ siêu dữ liệu nào về các tệp của bạn

Kho lưu trữ của bạn có (trong số những người khác)

$ cd repo
$ ls
...
iphone.css
...

và nó nằm dưới sự kiểm soát của git:

$ git ls-files --error-unmatch iphone.css &>/dev/null && echo file is tracked
file is tracked

Kiểm tra điều này với:

$ touch newfile
$ git ls-files --error-unmatch newfile &>/dev/null && echo file is tracked
(no output, it is not tracked)
$ rm newfile

Khi bạn làm

$ mv iphone.css mobile.css

Từ quan điểm git,

  • không có iphone.css (nó đã bị xóa -git cảnh báo về điều đó-).
  • có một tập tin mới mobile.css .
  • Những tập tin đó hoàn toàn không liên quan.

Vì vậy, git khuyên về các tệp mà nó đã biết ( iphone.css ) và các tệp mới mà nó phát hiện ( mobile.css ) nhưng chỉ khi các tệp nằm trong chỉ mục hoặc HEAD git mới bắt đầu kiểm tra nội dung của chúng.

Tại thời điểm này, cả "xóa iphone.css" và mobile.css đều không có trong chỉ mục.

Thêm xóa iphone.css vào chỉ mục

$ git rm iphone.css

git cho bạn biết chính xác những gì đã xảy ra: ( iphone.css đã bị xóa. Không có gì nữa xảy ra)

sau đó thêm tệp mới mobile.css

$ git add mobile.css

Lần này cả xóa và tập tin mới là trên chỉ mục. Bây giờ git phát hiện bối cảnh là như nhau và phơi bày nó như là một sự đổi tên. Trong thực tế nếu các tệp giống nhau 50%, nó sẽ phát hiện ra đó là một sự đổi tên, cho phép bạn thay đổi mobile.css một chút trong khi vẫn duy trì hoạt động như một đổi tên.

Xem điều này là tái sản xuất trên git diff. Bây giờ các tập tin của bạn là trên chỉ mục bạn phải sử dụng --cached. Chỉnh sửa mobile.css một chút, thêm nó vào chỉ mục và xem sự khác biệt giữa:

$ git diff --cached 

$ git diff --cached -M

-Mlà tùy chọn "phát hiện đổi tên" cho git diff. -Mlà viết tắt của -M50%(độ tương tự 50% trở lên sẽ khiến git thể hiện nó dưới dạng đổi tên) nhưng bạn có thể giảm mức này xuống -M20%(20%) nếu bạn chỉnh sửa mobile.css rất nhiều.


8

Bước 1: đổi tên tệp từ oldfile thành newfile

git mv #oldfile #newfile

Bước2: git commit và thêm ý kiến

git commit -m "rename oldfile to newfile"

Bước 3: đẩy thay đổi này đến máy chủ từ xa

git push origin #localbranch:#remotebranch

1
Vui lòng thêm một số nhận xét để nó hữu ích cho OP
Devrath

Bước 2 là không cần thiết. Sau đó git mv, tập tin mới đã có trong chỉ mục.
Alois Mahdal

7

Git sẽ nhận ra tệp từ nội dung, thay vì xem nó như một tệp mới không bị theo dõi

Đó là nơi bạn đã đi sai.

Chỉ sau khi bạn thêm tệp, git đó sẽ nhận ra nó từ nội dung.


Chính xác. Khi dàn git sẽ hiển thị tên chính xác.
Max MacLeod

3

Bạn đã không giai đoạn kết quả của công cụ tìm kiếm của bạn di chuyển. Tôi tin rằng nếu bạn đã di chuyển qua Finder và sau đó đã thực hiện git add css/mobile.css ; git rm css/iphone.css, git sẽ tính toán hàm băm của tệp mới và chỉ sau đó nhận ra rằng giá trị băm của các tệp khớp với nhau (và do đó là đổi tên).


2

Trong trường hợp bạn thực sự phải đổi tên các tệp theo cách thủ công, ví dụ: sử dụng một tập lệnh để đổi tên một loạt các tập tin, sau đó sử dụng git add -A .làm việc cho tôi.


2

Đối với người dùng Xcode: Nếu bạn đổi tên tệp của mình trong Xcode, bạn sẽ thấy biểu tượng huy hiệu thay đổi để chắp thêm. Nếu bạn thực hiện một cam kết bằng XCode, bạn thực sự sẽ tạo một tệp mới và mất lịch sử.

Cách giải quyết rất dễ dàng nhưng bạn phải thực hiện trước khi cam kết sử dụng Xcode:

  1. Làm một trạng thái git trên thư mục của bạn. Bạn sẽ thấy rằng các thay đổi theo giai đoạn là chính xác:

đã đổi tên: Project / OldName.h -> Project / NewName.h được đổi tên: Project / OldName.m -> Project / NewName.m

  1. thực hiện cam kết -m 'thay đổi tên'

Sau đó quay trở lại XCode và bạn sẽ thấy huy hiệu được thay đổi từ A thành M và nó được lưu để cam kết thay đổi furtur khi sử dụng xcode ngay bây giờ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.