Làm cách nào để tạo dấu git bị xóa và một tệp mới khi tệp di chuyển?


505

Tôi đã di chuyển một tệp bằng tay và sau đó tôi đã sửa đổi nó. Theo Git, đó là một tệp mới và một tệp bị xóa. Có cách nào để buộc Git xử lý nó như là một tập tin di chuyển không?


3
Đối với một tập tin được old_file.txt, sau đó git mv old_file.txt new_file.txtlà tương đương với git rm --cached old_file.txt, mv old_file.txt new_file.txt, git add new_file.txt.
Jarl

3
Jarl: không, không phải vậy. Nếu cũng có những thay đổi trong tệp, git mvsẽ không thêm chúng vào bộ đệm, nhưng git addsẽ. Tôi thích di chuyển tệp trở lại để tôi có thể sử dụng git mv, sau đó git add -pxem lại bộ thay đổi của mình.
dhardy

vui lòng kiểm tra tập lệnh của tôi github.com/Deathangel908/python-tricks/blob/master/iêu
deathangel908


Git đã được cải thiện trong 8 năm qua, nếu chỉ là một tệp, câu trả lời hàng đầu stackoverflow.com/a/433142/459 không làm gì cả nhưng bạn có thể theo dõi stackoverflow.com/a/1541072/459 để cập nhật rm / add đến trạng thái mv / sửa đổi.
dlamblin

Câu trả lời:


435

Git sẽ tự động phát hiện di chuyển / đổi tên nếu sửa đổi của bạn không quá nghiêm trọng. Chỉ cần git addtập tin mới, và git rmtập tin cũ. git statussau đó sẽ hiển thị cho dù nó đã phát hiện đổi tên.

Ngoài ra, để di chuyển xung quanh các thư mục, bạn có thể cần phải:

  1. cd vào đầu cấu trúc thư mục đó.
  2. Chạy git add -A .
  3. Chạy git statusđể xác minh rằng "tệp mới" hiện là tệp "đã đổi tên"

Nếu trạng thái git vẫn hiển thị "tệp mới" và không "đổi tên", bạn cần làm theo lời khuyên của Hank Gay, và thực hiện di chuyển và sửa đổi trong hai cam kết riêng biệt.


75
Làm rõ: 'không quá nghiêm trọng' có nghĩa là tệp mới và tệp cũ> 50% 'tương tự' dựa trên một số chỉ mục tương tự mà git sử dụng.
pjz

151
Một cái gì đó khiến tôi nôn nao trong vài phút: nếu các tệp đã đổi tên và các tệp bị xóa không được tổ chức để cam kết thì chúng sẽ hiển thị dưới dạng xóa và một tệp mới. Khi bạn thêm chúng vào chỉ mục dàn, nó sẽ nhận ra nó là một sự đổi tên.
marczych

19
Điều đáng nói là khi nói về " Git sẽ tự động phát hiện di chuyển / đổi tên ". Nó làm như vậy tại thời điểm bạn sử dụng git status, git loghoặc git diff, không phải tại thời điểm bạn làm git add, git mvhoặc git rm. Nói thêm về việc phát hiện đổi tên, điều đó chỉ có ý nghĩa đối với các tập tin được dàn dựng. Vì vậy, git mvtheo sau là một thay đổi trong tệp có thể trông git statusnhư thể nó coi nó là một rename, nhưng khi bạn sử dụng git stage(giống như git add) trên tệp, có thể thấy rõ rằng thay đổi quá lớn để được phát hiện là đổi tên.
Jarl

5
Chìa khóa thực sự dường như là thêm cả hai vị trí mới và cũ vào cùng git add, như @ jrhorn424 gợi ý, có lẽ chỉ nên là toàn bộ repo cùng một lúc.
brianary

4
Điều này không thể sai được, đặc biệt là nếu tệp thay đổi và nhỏ. Có một phương pháp để nói cụ thể với git về việc di chuyển không?
Hoàn trả

112

Thực hiện di chuyển và sửa đổi trong các cam kết riêng biệt.


12
Tôi hiểu rằng Git có thể xử lý các động thái và sửa đổi cùng một lúc. Khi lập trình bằng Java và sử dụng IDE, đổi tên một lớp vừa là sửa đổi vừa là một động thái. Tôi hiểu rằng Git thậm chí có thể tự động tìm ra nó khi có di chuyển (ra khỏi một loại bỏ và một sáng tạo).
Pupeno

1
Perl cũng yêu cầu điều này và tôi chưa bao giờ Git phát hiện di chuyển / đổi tên.
jrockway

10
@jrockway, tôi đã có. Xảy ra dễ dàng với các tệp nhỏ, tôi đoán chúng "trở nên" quá khác biệt "có nghĩa là một động thái".
ANeves

2
Có cách nào để làm điều này tự động? Tôi có nhiều tệp trong chỉ mục, trước tiên muốn thực hiện di chuyển và sau đó thay đổi, nhưng thật khó để thực hiện thủ công
ReDetection

5
Một điều cần lưu ý là nếu git diffkhông nhận ra việc đổi tên qua một cam kết, thì nó sẽ không nhận ra nó qua hai lần xác nhận. Bạn sẽ cần sử dụng -Maka --find-renamesđể làm điều đó. Vì vậy, nếu động lực dẫn bạn đến câu hỏi này là để xem việc đổi tên trong yêu cầu kéo (nói từ kinh nghiệm), thì việc chia nó thành hai lần cam kết sẽ không giúp bạn tiếp tục mục tiêu đó.
mattliu

44

Đó là tất cả một điều nhận thức. Git thường khá giỏi trong việc nhận ra các động thái, bởi vì GIT là một công cụ theo dõi nội dung

Tất cả những gì thực sự phụ thuộc là cách "stat" của bạn hiển thị nó. Sự khác biệt duy nhất ở đây là cờ -M.

nhật ký git --stat -M

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300


        Category Restructure

     lib/Gentoo/Repository.pm                |   10 +++++-----
     lib/Gentoo/{ => Repository}/Base.pm     |    2 +-
     lib/Gentoo/{ => Repository}/Category.pm |   12 ++++++------
     lib/Gentoo/{ => Repository}/Package.pm  |   10 +++++-----
     lib/Gentoo/{ => Repository}/Types.pm    |   10 +++++-----
     5 files changed, 22 insertions(+), 22 deletions(-)

đăng nhập git

commit 9c034a76d394352134ee2f4ede8a209ebec96288
Author: Kent Fredric
Date:   Fri Jan 9 22:13:51 2009 +1300

    Category Restructure

 lib/Gentoo/Base.pm                |   36 ------------------------
 lib/Gentoo/Category.pm            |   51 ----------------------------------
 lib/Gentoo/Package.pm             |   41 ---------------------------
 lib/Gentoo/Repository.pm          |   10 +++---
 lib/Gentoo/Repository/Base.pm     |   36 ++++++++++++++++++++++++
 lib/Gentoo/Repository/Category.pm |   51 ++++++++++++++++++++++++++++++++++
 lib/Gentoo/Repository/Package.pm  |   41 +++++++++++++++++++++++++++
 lib/Gentoo/Repository/Types.pm    |   55 +++++++++++++++++++++++++++++++++++++
 lib/Gentoo/Types.pm               |   55 -------------------------------------
 9 files changed, 188 insertions(+), 188 deletions(-)

git giúp đăng nhập

   -M
       Detect renames.

   -C
       Detect copies as well as renames. See also --find-copies-harder.

76
Xin lỗi nếu điều này có vẻ hơi khoa trương, nhưng "Git nói chung khá giỏi trong việc nhận ra các động thái, bởi vì GIT là một trình theo dõi nội dung" có vẻ như không phải là một trình tự đối với tôi. Đó là một trình theo dõi nội dung, vâng, và có lẽ nó rất tốt trong việc phát hiện các động thái, nhưng một tuyên bố không thực sự theo sau từ khác. Chỉ vì đó là một trình theo dõi nội dung, phát hiện di chuyển không nhất thiết phải tốt. Trong thực tế, một trình theo dõi nội dung có thể không có phát hiện di chuyển nào cả.
Laurence Gonsalves

1
@WarrenSeine khi bạn đổi tên một thư mục, SHA1 của các tệp trong thư mục đó không thay đổi. Tất cả những gì bạn có là một đối tượng TREE mới có cùng SHA1. Không có lý do gì việc đổi tên thư mục sẽ gây ra thay đổi dữ liệu quan trọng.
Kent Fredric

1
@KentFredric Bạn đã cho thấy rằng sử dụng -M với "git log", chúng tôi có thể kiểm tra xem tệp có được đổi tên hay không và tôi đã thử nó và nó hoạt động tốt, nhưng khi tôi đăng mã của mình để xem xét và xem tệp đó trong gerrit ( gerritcodereview.com ) ở đó nó hiển thị các tập tin mới được thêm vào và trước đó đã bị xóa. Vì vậy, có một tùy chọn trong "git commit" bằng cách sử dụng mà tôi thực hiện cam kết và gerrit hiển thị đúng.
Patrick

1
Không. Tôi không thể hiện điều đó cả. Tôi đã cho thấy Git có khả năng giả vờ thêm + xóa là đổi tên do nội dung giống nhau. Không có cách nào để nó biết chuyện gì đã xảy ra, và nó không quan tâm. Tất cả các ghi nhớ git được "thêm" và "xóa". "Đổi tên" không bao giờ được ghi lại.
Kent Fredric

1
Ví dụ, tôi đã thực hiện rất nhiều "Sao chép + Chỉnh sửa" trên một repo gần đây. Hầu hết các cách xem nó chỉ thấy "tệp mới", nhưng nếu bạn vượt qua git log -M1 -C1 -B1 -D --find-copies-harder, git có thể "phát hiện" rằng tệp mới có thể đã được sao chép trước tiên. Đôi khi, điều này đúng, đôi khi, nó tìm thấy các tệp hoàn toàn không liên quan đến tình cờ có nội dung giống hệt nhau.
Kent Fredric

36

git diff -Mhoặc git log -Msẽ tự động phát hiện những thay đổi như đổi tên với những thay đổi nhỏ miễn là chúng thực sự là. Nếu những thay đổi nhỏ của bạn không phải là nhỏ, bạn có thể giảm sự tương tự, ví dụ:

$ git log -M20 -p --stat

để giảm từ 50% xuống 20% ​​mặc định.


13
Có thể xác định ngưỡng trong cấu hình?
Tái lập

34

Đây là một giải pháp nhanh chóng và bẩn thỉu cho một hoặc một vài tệp, đã đổi tên và sửa đổi các tệp không được cam kết.

Giả sử tập tin đã được đặt tên foovà bây giờ nó có tên bar:

  1. Đổi tên barthành tên tạm thời:

    mv bar side
    
  2. Thanh toán foo:

    git checkout HEAD foo
    
  3. Đổi tên foothành barGit:

    git mv foo bar
    
  4. Bây giờ đổi tên tập tin tạm thời của bạn trở lại bar.

    mv side bar
    

Bước cuối cùng này là những gì đưa nội dung thay đổi của bạn trở lại vào tệp.

Trong khi điều này có thể hoạt động, nếu tệp di chuyển quá khác biệt về nội dung so với git ban đầu sẽ xem xét nó hiệu quả hơn để quyết định đây là một đối tượng mới. Hãy để tôi chứng minh:

$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md

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:   README.md
    modified:   work.js

$ git add README.md work.js # why are the changes unstaged, let's add them.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ git stash # what? let's go back a bit
Saved working directory and index state WIP on dir: f7a8685 update
HEAD is now at f7a8685 update
$ git status
On branch workit
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .idea/

nothing added to commit but untracked files present (use "git add" to track)
$ git stash pop
Removing README
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md

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:    README
    modified:   work.js

Dropped refs/stash@{0} (1ebca3b02e454a400b9fb834ed473c912a00cd2f)
$ git add work.js
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

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:    README

$ git add README # hang on, I want it removed
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    deleted:    README
    new file:   README.md
    modified:   work.js

$ mv README.md Rmd # Still? Try the answer I found.
$ git checkout README
error: pathspec 'README' did not match any file(s) known to git.
$ git checkout HEAD README # Ok the answer needed fixing.
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    new file:   README.md
    modified:   work.js

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:    README.md
    modified:   work.js

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

    Rmd

$ git mv README README.md
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

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:   work.js

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

    Rmd

$ mv Rmd README.md
$ git status
On branch workit
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitignore
    renamed:    README -> README.md
    modified:   work.js

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:   README.md
    modified:   work.js

$ # actually that's half of what I wanted; \
  # and the js being modified twice? Git prefers it in this case.

27
Quá trình này không phục vụ mục đích. git không thêm bất kỳ siêu dữ liệu nào cho việc đổi tên, git mvchỉ đơn giản là sự tiện lợi cho git rm/ git addcặp. Nếu bạn đã thực hiện 'mv bar foo', thì tất cả những gì bạn phải làm là đảm bảo rằng bạn git add foogit rm bartrước khi thực hiện cam kết. Điều này có thể được thực hiện như một git add -Alệnh đơn lẻ , hoặc có thể là một git add foo; git commit -achuỗi.
CB Bailey

17
Tất cả những gì tôi biết là trước khi tôi làm điều đó, Git đã không nhận ra đó là một động thái. Sau khi tôi làm điều này, Git nhận ra đó là một động thái.

8
Nó nhận ra nó là một động thái. Nhưng nó cũng có sự thay đổi như một sự thay đổi không có căn cứ bây giờ. Khi bạn addgửi lại tệp, git sẽ chuyển di chuyển / sửa đổi thành xóa / thêm lại.
Michael Piefel

4
Quá trình này sẽ hoạt động nếu bạn git commitsau bước 3, nếu không thì không chính xác. Ngoài ra, @CharlesBailey là chính xác, bạn có thể dễ dàng làm bình thường mv blah fooở bước 3 sau đó là một cam kết và nhận được kết quả tương tự.
DaFlame

5
"Quá trình này không phục vụ mục đích." - Nó phục vụ mục đích khi git bị nhầm lẫn và nghĩ rằng một tệp đã bị xóa và một tệp khác đã được thêm vào. Điều này sẽ xóa sự nhầm lẫn của Git và đánh dấu tệp là được di chuyển rõ ràng mà không phải thực hiện các cam kết trung gian.
Danack

20

Nếu bạn đang nói về việc git statuskhông hiển thị tên, git commit --dry-run -athay vào đó hãy thử


11

Nếu bạn đang sử dụng TortoiseGit, điều quan trọng cần lưu ý là phát hiện đổi tên tự động của Git xảy ra trong quá trình cam kết nhưng thực tế điều này sẽ xảy ra không phải lúc nào cũng được phần mềm hiển thị trước. Tôi đã chuyển hai tập tin sang một thư mục khác và thực hiện một số chỉnh sửa nhỏ. Tôi sử dụng TortoiseGit làm công cụ cam kết của mình và danh sách Thay đổi được thực hiện cho thấy các tệp bị xóa và thêm, không bị di chuyển. Chạy trạng thái git từ dòng lệnh cho thấy một tình huống tương tự. Tuy nhiên, sau khi cam kết các tệp, chúng hiển thị như được đổi tên trong nhật ký. Vì vậy, câu trả lời cho câu hỏi của bạn là, miễn là bạn chưa làm gì quá quyết liệt, Git sẽ tự động đổi tên.

Chỉnh sửa: Rõ ràng nếu bạn thêm các tệp mới và sau đó thực hiện trạng thái git từ dòng lệnh, đổi tên sẽ hiển thị trước khi cam kết.

Chỉnh sửa 2: Ngoài ra, trong TortoiseGit, thêm các tệp mới trong hộp thoại cam kết nhưng không cam kết chúng. Sau đó, nếu bạn đi vào lệnh Show Log và xem thư mục làm việc, bạn sẽ thấy Git có phát hiện ra việc đổi tên trước khi cam kết hay không.

Câu hỏi tương tự cũng được đặt ra ở đây: https://tortoisegit.org/su/1389 và đã được ghi lại là một lỗi cần sửa ở đây: https://tortoisegit.org/su/1440 Hóa ra đó là vấn đề hiển thị với cam kết của TortoiseGit hộp thoại và loại tồn tại trong trạng thái git nếu bạn chưa thêm các tệp mới.


2
Bạn đã đúng, ngay cả khi TortoiseGit hiển thị xóa + thêm, ngay cả khi trạng thái git hiển thị xóa + thêm ngay cả khi git commit --dry-run hiển thị xóa + thêm, sau khi git commit tôi thấy đổi tên và không xóa + thêm.
Tomas Kubes

1
Tôi khá chắc chắn phát hiện đổi tên tự động xảy ra trong quá trình truy xuất lịch sử ; trong cam kết nó luôn luôn thêm + xóa. Điều đó cũng giải thích hành vi tâm thần phân liệt mà bạn mô tả. Do đó, các tùy chọn ở đây: stackoverflow.com/a/434078/155892
Mark Sowul

10

Hoặc bạn hãy thử trả lời câu hỏi này tại đây của Amber ! Để trích dẫn lại:

Đầu tiên, hủy phần bổ sung theo giai đoạn của bạn cho tệp được di chuyển thủ công:

$ git reset path/to/newfile
$ mv path/to/newfile path/to/oldfile

Sau đó, sử dụng Git để di chuyển tệp:

$ git mv path/to/oldfile path/to/newfile

Tất nhiên, nếu bạn đã thực hiện di chuyển thủ công, bạn có thể muốn đặt lại về sửa đổi trước khi di chuyển thay vào đó, và sau đó chỉ cần git mv từ đó.


7

Sử dụng git mvlệnh để di chuyển các tệp, thay vì các lệnh di chuyển hệ điều hành: https://git-scm.com/docs/git-mv

Xin lưu ý rằng git mvlệnh chỉ tồn tại trong các phiên bản Git 1.8.5 trở lên. Vì vậy, bạn có thể phải cập nhật Git của mình để sử dụng lệnh này.


3

Tôi đã gặp vấn đề này gần đây, khi di chuyển (nhưng không sửa đổi) một số tệp.

Vấn đề là Git đã thay đổi một số kết thúc dòng khi tôi di chuyển các tệp và sau đó không thể nói rằng các tệp đó giống nhau.

Sử dụng đã git mvsắp xếp vấn đề, nhưng nó chỉ hoạt động trên các tệp / thư mục duy nhất và tôi có rất nhiều tệp trong thư mục gốc của kho lưu trữ.

Một cách để khắc phục điều này sẽ là với một số phép thuật bash / batch.

Một cách khác là như sau

  • Di chuyển các tập tin và git commit. Điều này cập nhật các kết thúc dòng.
  • Di chuyển các tệp trở lại vị trí ban đầu của chúng, bây giờ chúng có các kết thúc dòng mới và git commit --amend
  • Di chuyển các tập tin một lần nữa và git commit --amend. Không có thay đổi nào về kết thúc dòng lần này vì vậy Git rất vui

1

Đối với tôi, nó đã làm việc để lưu tất cả các thay đổi trước khi cam kết và bật chúng ra một lần nữa. Điều này làm cho git phân tích lại các tập tin được thêm / xóa và nó đánh dấu chính xác chúng là di chuyển.


0

Có lẽ có một cách tốt hơn dòng lệnh của Wap để thực hiện điều này và tôi biết đây là một hack, nhưng tôi chưa bao giờ có thể tìm ra giải pháp tốt.

Sử dụng TortoiseGIT: Nếu bạn có một cam kết GIT trong đó một số thao tác di chuyển tệp đang hiển thị dưới dạng tải thêm / xóa thay vì đổi tên, mặc dù các tệp chỉ có thay đổi nhỏ, sau đó thực hiện việc này:

  1. Kiểm tra những gì bạn đã làm tại địa phương
  2. Kiểm tra thay đổi một dòng nhỏ trong lần xác nhận thứ 2
  3. Chuyển đến nhật ký GIT trong rùa git
  4. Chọn hai cam kết, nhấp chuột phải và chọn Hợp nhất thành một cam kết

Cam kết mới bây giờ sẽ hiển thị chính xác tệp đổi tên tập tin sẽ giúp duy trì lịch sử tập tin thích hợp.


0

Khi tôi chỉnh sửa, đổi tên và di chuyển tệp cùng một lúc, không có giải pháp nào trong số này hoạt động. Giải pháp là thực hiện nó trong hai lần xác nhận (chỉnh sửa và đổi tên / di chuyển tách biệt) và sau đó fixupcam kết thứ hai thông qua git rebase -iđể có nó trong một lần xác nhận.


0

Cách tôi hiểu câu hỏi này là "Cách làm cho git nhận ra việc xóa một tệp cũ và tạo một tệp mới khi di chuyển tệp".

Có trong thư mục làm việc khi bạn xóa một tệp cũ và chèn một tệp cũ, git statussẽ nói " deleted: old_file" và " Untracked files: ... new_file"

Nhưng trong chỉ mục / cấp độ phân tầng một khi bạn thêm và xóa tệp bằng git, nó sẽ được nhận dạng là di chuyển tệp. Để làm như vậy, giả sử bạn đã thực hiện xóa và tạo bằng Hệ thống hoạt động, hãy đưa ra các lệnh sau:

git add new_file
git rm old_file

Nếu nội dung của tệp tương tự 50% trở lên, git statuslệnh chạy sẽ cung cấp cho bạn:

renamed: old_file -> new_file

1
git statussẽ nói " deleted: old_file" và " Untracked files: ... new_file": Không kể từ Git 2.18: stackoverflow.com/a/50573107/6309
VonC
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.