Bagaimana cara membuat git menandai file yang dihapus dan baru sebagai pemindahan file?

505

Saya telah memindahkan file secara manual dan kemudian saya memodifikasinya. Menurut Git, ini adalah file baru dan file yang dihapus. Apakah ada cara untuk memaksa Git memperlakukannya sebagai pemindahan file?

pupeno
sumber
3
Untuk file yang diberikan old_file.txt, maka git mv old_file.txt new_file.txtsetara dengan git rm --cached old_file.txt, mv old_file.txt new_file.txt, git add new_file.txt.
Jarl
3
Jarl: bukan. Jika ada juga perubahan di dalam file, git mvtidak akan menambahkannya ke cache, tetapi git addakan. Saya lebih suka memindahkan file kembali sehingga saya dapat menggunakan git mv, kemudian git add -puntuk meninjau set perubahan saya.
dhardy
silakan cek
deathangel908
Git telah meningkat dalam 8 tahun terakhir, jika hanya satu file, jawaban teratas stackoverflow.com/a/433142/459 tidak melakukan apa-apa ... tetapi Anda dapat mengikuti stackoverflow.com/a/1541072/459 untuk mendapatkan rm / tambahkan pembaruan ke mv / ubah status.
dlamblin

Jawaban:

435

Git akan secara otomatis mendeteksi pemindahan / penggantian nama jika modifikasi Anda tidak terlalu parah. Hanya git addfile baru, dan git rmfile lama. git statuskemudian akan menunjukkan apakah ia telah mendeteksi nama.

selain itu, untuk bergerak di sekitar direktori, Anda mungkin perlu:

  1. cd ke atas struktur direktori itu.
  2. Lari git add -A .
  3. Jalankan git statusuntuk memverifikasi bahwa "file baru" sekarang menjadi file "berganti nama"

Jika status git masih menunjukkan "file baru" dan tidak "diganti nama", Anda harus mengikuti saran Hank Gay dan melakukan langkah dan memodifikasi dalam dua komitmen terpisah.

Bombe
sumber
75
Klarifikasi: 'tidak terlalu parah' berarti bahwa file baru dan file lama>> 50% 'mirip' berdasarkan beberapa indeks kesamaan yang digunakan git.
pjz
151
Sesuatu yang menggantung saya selama beberapa menit: jika file yang diubah namanya dan file yang dihapus tidak dipentaskan karena melakukan maka mereka akan muncul sebagai delete dan file baru. Setelah Anda menambahkannya ke indeks pementasan, itu akan mengenalinya sebagai nama baru.
marczych
19
Perlu disebutkan bahwa ketika berbicara tentang " Git akan secara otomatis mendeteksi langkah / ganti nama ". Itu melakukannya pada saat Anda menggunakan git status, git logatau git diff, tidak pada saat Anda melakukannya git add, git mvatau git rm. Bicara lebih lanjut tentang mendeteksi nama, itu hanya masuk akal untuk file yang dipentaskan. Jadi git mvdiikuti oleh perubahan dalam file mungkin terlihat git statusseolah-olah menganggapnya sebagai rename, tetapi ketika Anda menggunakan git stage(sama seperti git add) pada file, menjadi jelas bahwa perubahan itu terlalu besar untuk dideteksi sebagai perubahan nama.
Jarl
5
Kunci sebenarnya tampaknya menambahkan kedua lokasi baru dan lama di tempat yang sama git add, yang seperti yang disarankan @ jrhorn424, mungkin hanya seluruh repo sekaligus.
brianary
4
Ini tidak sempurna, terutama jika file berubah dan kecil. Apakah ada metode untuk secara khusus memberi tahu git tentang langkah tersebut?
Rebs
112

Lakukan pemindahan dan modifikasi dalam komit terpisah.

Hank Gay
sumber
12
Saya mengerti bahwa Git dapat menangani gerakan dan modifikasi pada saat yang bersamaan. Saat memprogram di Java dan menggunakan IDE, mengubah nama kelas adalah modifikasi sekaligus perpindahan. Saya mengerti bahwa Git bahkan harus dapat secara otomatis mencari tahu ketika ada gerakan (dari penghapusan dan kreasi).
pupeno
1
Perl memerlukan ini juga, dan saya tidak pernah memiliki Git mendeteksi langkah / ganti nama.
jrockway
10
@ jembatan, aku punya. Terjadi dengan mudah dengan file-file kecil, saya kira mereka "menjadi 'terlalu berbeda' untuk berarti pindah".
ANeves
2
Apakah ada cara untuk melakukan ini secara otomatis? Saya memiliki banyak file dalam indeks, ingin melakukan langkah pertama, dan kemudian perubahan, tetapi sulit dilakukan secara manual
ReDetection
5
Satu hal yang perlu diperhatikan adalah bahwa jika git difftidak mengenali nama di satu komit, itu tidak akan mengenalinya di dua komit. Anda perlu menggunakan -Malias --find-renamesuntuk melakukan itu. Jadi, jika motivasi yang mengarahkan Anda ke pertanyaan ini adalah untuk melihat penggantian nama dalam permintaan tarik (berbicara dari pengalaman), membaginya menjadi dua komitmen tidak akan membuat Anda mencapai tujuan itu.
mattliu
44

Itu semua adalah persepsi. Git pada umumnya agak pandai mengenali gerakan, karena GIT adalah pelacak konten

Yang sangat tergantung adalah bagaimana "stat" Anda menampilkannya. Satu-satunya perbedaan di sini adalah flag -M.

git log --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(-)

git log --stat

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 help log

   -M
       Detect renames.

   -C
       Detect copies as well as renames. See also --find-copies-harder.
Kent Fredric
sumber
76
Maaf jika ini tampaknya agak terlalu berlebihan, tetapi "Git umumnya cukup baik dalam mengenali gerakan, karena GIT adalah pelacak konten" sepertinya seperti non-sequitur bagi saya. Ini pelacak konten, ya, dan mungkin itu baik untuk mendeteksi gerakan, tetapi satu pernyataan tidak benar-benar mengikuti dari yang lain. Hanya karena itu adalah pelacak konten, deteksi gerakan tidak selalu baik. Bahkan, pelacak konten tidak dapat mendeteksi deteksi sama sekali.
Laurence Gonsalves
1
@ WarrenSeine ketika Anda mengganti nama direktori, SHA1 file di direktori itu tidak berubah. Yang Anda miliki hanyalah objek TREE baru dengan SHA1 yang sama. Tidak ada alasan mengganti nama direktori akan menyebabkan perubahan data yang signifikan.
Kent Fredric
1
@KentFredric Anda menunjukkan bahwa menggunakan -M dengan "git log" kita dapat memeriksa apakah file diubah namanya atau tidak dan saya mencobanya dan berfungsi dengan baik, tetapi ketika saya memposting kode saya untuk ditinjau dan melihat file itu di gerrit ( gerritcodereview.com ) di sana itu menunjukkan file baru ditambahkan dan sebelumnya sudah dihapus. Jadi apakah ada opsi di "git commit" menggunakan yang saya lakukan komit dan gerrit menunjukkannya dengan benar.
Patrick
1
Tidak. Saya tidak menunjukkan itu sama sekali. Saya menunjukkan Git mampu berpura - pura menambahkan + menghapus adalah mengubah nama karena kontennya sama. Tidak mungkin untuk mengetahui yang terjadi, dan itu tidak peduli. Semua ingat git "ditambahkan" dan "dihapus". "Berganti nama" tidak pernah direkam.
Kent Fredric
1
Sebagai contoh, saya melakukan banyak "Salin + Edit" pada repo belakangan ini. Sebagian besar cara melihatnya hanya melihat "file baru", tetapi jika Anda lulus git log -M1 -C1 -B1 -D --find-copies-harder, git dapat "menemukan" bahwa file baru mungkin telah disalin terlebih dahulu. Kadang-kadang melakukan ini dengan benar, di lain waktu, ia menemukan file yang sama sekali tidak terkait yang kebetulan memiliki konten yang identik.
Kent Fredric
36

git diff -Matau git log -Mharus secara otomatis mendeteksi perubahan tersebut sebagai perubahan nama dengan perubahan kecil selama memang demikian. Jika perubahan minor Anda tidak kecil, Anda dapat mengurangi kesamaan threashold, mis

$ git log -M20 -p --stat

untuk menguranginya dari default 50% hingga 20%.

Simon Timur
sumber
13
Apakah mungkin untuk mendefinisikan ambang batas dalam konfigurasi?
ReDetection
34

Berikut adalah solusi cepat dan kotor untuk satu, atau beberapa, nama file yang diubah dan dimodifikasi yang tidak dikomit.

Katakanlah file itu dinamai foodan sekarang dinamai bar:

  1. Ganti nama barmenjadi nama temp:

    mv bar side
    
  2. Checkout foo:

    git checkout HEAD foo
    
  3. Ganti nama foomenjadi bardengan Git:

    git mv foo bar
    
  4. Sekarang ganti nama file sementara Anda kembali ke bar.

    mv side bar
    

Langkah terakhir ini adalah apa yang membuat konten Anda yang diubah kembali ke file.

Meskipun ini dapat bekerja, jika file yang dipindahkan terlalu berbeda dalam konten dari git asli akan menganggapnya lebih efisien untuk memutuskan ini adalah objek baru. Biarkan saya menunjukkan:

$ 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.
dlamblin
sumber
27
Proses ini tidak memiliki tujuan. git tidak menambahkan metadata apa pun untuk penggantian nama, git mvhanya kenyamanan untuk a git rm/ git addpair. Jika Anda sudah melakukan 'mv bar foo', maka yang harus Anda lakukan adalah memastikan bahwa Anda telah git add foodan git rm barsebelum membuat komit. Ini bisa dilakukan sebagai git add -Aperintah tunggal , atau mungkin git add foo; git commit -aurutan.
CB Bailey
17
Yang saya tahu adalah bahwa sebelum saya melakukannya, Git tidak mengenalinya sebagai suatu langkah. Setelah saya melakukan ini, Git mengenalinya sebagai suatu langkah.
8
Itu mengenalinya sebagai sebuah langkah. Tetapi itu juga memiliki perubahan sebagai perubahan yang tidak dipentaskan sekarang. Setelah Anda addfile lagi, git memecah langkah / memodifikasi menjadi yang dihapus / ditambahkan lagi.
Michael Piefel
4
Proses ini akan berhasil jika Anda git commitsetelah langkah 3, jika tidak maka tidak benar. Juga, @CharlesBailey benar, Anda dapat dengan mudah melakukan normal mv blah foopada langkah 3 diikuti oleh komit dan mendapatkan hasil yang sama.
DaFlame
5
"Proses ini tidak ada gunanya." - Ini melayani tujuan ketika git menjadi bingung dan berpikir file telah dihapus, dan file lain telah ditambahkan. Ini membersihkan kebingungan Git dan menandai file sebagai dipindahkan secara eksplisit tanpa harus melakukan komitmen perantara.
Danack
20

Jika Anda berbicara tentang git statustidak menunjukkan nama, coba git commit --dry-run -asebagai gantinya

Direksi
sumber
11

Jika Anda menggunakan TortoiseGit, penting untuk dicatat bahwa deteksi ganti nama otomatis Git terjadi selama komit, tetapi fakta bahwa ini akan terjadi tidak selalu ditampilkan oleh perangkat lunak sebelumnya. Saya telah memindahkan dua file ke direktori yang berbeda dan melakukan sedikit pengeditan. Saya menggunakan TortoiseGit sebagai alat komit saya dan daftar Perubahan yang dibuat menunjukkan file yang dihapus dan ditambahkan, tidak dipindahkan. Menjalankan status git dari baris perintah menunjukkan situasi yang sama. Namun setelah melakukan file, mereka muncul untuk diganti namanya di log. Jadi jawaban untuk pertanyaan Anda adalah, selama Anda belum melakukan sesuatu yang terlalu drastis, Git harus mengambil nama itu secara otomatis.

Sunting: Rupanya jika Anda menambahkan file baru dan kemudian melakukan status git dari baris perintah, nama harus muncul sebelum melakukan.

Sunting 2: Selain itu, di TortoiseGit, tambahkan file baru dalam dialog komit tetapi jangan komit. Kemudian jika Anda masuk ke perintah Tampilkan Log dan melihat direktori yang berfungsi, Anda akan melihat apakah Git telah mendeteksi nama sebelum melakukan.

Pertanyaan yang sama muncul di sini: https://tortoisegit.org/issue/1389 dan telah dicatat sebagai bug untuk diperbaiki di sini: https://tortoisegit.org/issue/1440 Ternyata ini merupakan masalah tampilan dengan komitmen TortoiseGit dialog dan juga jenis ada dalam status git jika Anda belum menambahkan file baru.

Giles Roberts
sumber
2
Anda benar, bahkan jika TortoiseGit menunjukkan delete + add, bahkan jika status git menunjukkan delete + add bahkan jika git commit --dry-run show delete + add, setelah git commit saya melihat rename dan tidak menghapus + add.
Tomas Kubes
1
Saya cukup yakin deteksi penggantian nama otomatis terjadi selama pencarian riwayat ; di komit itu selalu tambah + hapus. Itu juga menjelaskan perilaku skizofrenik yang Anda gambarkan. Oleh karena itu opsi di sini: stackoverflow.com/a/434078/155892
Mark Sowul
10

Atau Anda bisa mencoba jawaban untuk pertanyaan ini di sini oleh Amber ! Mengutipnya lagi:

Pertama, batalkan add bertahap Anda untuk file yang dipindahkan secara manual:

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

Kemudian, gunakan Git untuk memindahkan file:

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

Tentu saja, jika Anda sudah melakukan langkah manual, Anda mungkin ingin mengatur ulang ke revisi sebelum pindah sebagai gantinya, dan kemudian cukup git mv dari sana.

Zingam
sumber
7

Gunakan git mvperintah untuk memindahkan file, alih-alih perintah pemindahan OS: https://git-scm.com/docs/git-mv

Harap dicatat bahwa git mvperintah hanya ada di Git versi 1.8.5 dan lebih tinggi. Jadi, Anda mungkin harus memperbarui Git Anda untuk menggunakan perintah ini.

mostafa.elhoushi
sumber
3

Saya memiliki masalah ini baru-baru ini, ketika memindahkan (tetapi tidak memodifikasi) beberapa file.

Masalahnya adalah bahwa Git mengubah beberapa akhir baris ketika saya memindahkan file, dan kemudian tidak dapat mengatakan bahwa file itu sama.

Menggunakan git mvmenyelesaikan masalah, tetapi hanya bekerja pada satu file / direktori, dan saya punya banyak file di root repositori yang harus dilakukan.

Salah satu cara untuk memperbaikinya adalah dengan sihir bash / batch.

Cara lain adalah sebagai berikut

  • Pindahkan file dan git commit. Ini memperbarui akhir baris.
  • Pindahkan file kembali ke lokasi aslinya, sekarang mereka memiliki akhiran baris baru, dan git commit --amend
  • Pindahkan file lagi dan git commit --amend. Tidak ada perubahan pada akhir baris saat ini sehingga Git senang
cedd
sumber
1

Bagi saya itu berhasil menyimpan semua perubahan sebelum komit dan menghapusnya kembali. Ini membuat git menganalisis kembali file yang ditambahkan / dihapus dan ditandai dengan benar sebagai dipindahkan.

Alex Ilie
sumber
0

Mungkin ada cara "perintah" yang lebih baik untuk melakukan ini, dan saya tahu ini adalah peretasan, tapi saya tidak pernah bisa menemukan solusi yang baik.

Menggunakan TortoiseGIT: Jika Anda memiliki komit GIT di mana beberapa operasi pemindahan file muncul sebagai banyak penambahan / penghapusan daripada penggantian nama, meskipun file hanya memiliki perubahan kecil, maka lakukan ini:

  1. Periksa apa yang telah Anda lakukan secara lokal
  2. Periksa perubahan mini satu baris dalam komit ke-2
  3. Pergi ke GIT login di tortoise git
  4. Pilih dua komit, klik kanan, dan pilih "bergabung menjadi satu komit"

Komit baru sekarang akan dengan benar menunjukkan nama file ... yang akan membantu menjaga riwayat file yang tepat.

Jon Rea
sumber
0

Saat saya mengedit, mengganti nama, dan memindahkan file secara bersamaan, tidak ada solusi yang berfungsi. Solusinya adalah melakukannya dalam dua komit (edit dan ganti nama / pindahkan terpisah) dan kemudian fixupkomit kedua via git rebase -iuntuk membuatnya dalam satu komit.

Hativ
sumber
0

Cara saya memahami pertanyaan ini adalah "Bagaimana membuat git mengenali penghapusan file lama dan pembuatan file baru sebagai pemindahan file".

Ya di direktori kerja setelah Anda menghapus file lama dan memasukkan file lama, git statusakan mengatakan " deleted: old_file" dan " Untracked files: ... new_file"

Tetapi dalam staging index / level setelah Anda menambah dan menghapus file menggunakan git, itu akan dikenali sebagai pemindahan file. Untuk melakukannya, dengan asumsi Anda telah melakukan penghapusan dan pembuatan menggunakan Sistem Operasi Anda, berikan perintah berikut:

git add new_file
git rm old_file

Jika konten file 50% atau lebih mirip, git statusperintah yang berjalan akan memberi Anda:

renamed: old_file -> new_file
Harshainfo
sumber
1
git statusakan mengatakan " deleted: old_file" dan " Untracked files: ... new_file": Tidak sejak Git 2.18: stackoverflow.com/a/50573107/6309
VonC