Geçmişi koruyarak dosyaları bir git deposundan diğerine (klon değil) taşıma


483

Git depolarımız, her bir projenin kendi ağacına sahip olduğu tek bir canavar SVN deposunun parçaları olarak başladı:

project1/branches
        /tags
        /trunk
project2/branches
        /tags
        /trunk

Açıkçası, dosyaları birinden diğerine taşımak oldukça kolaydı svn mv. Ancak Git'te her proje kendi deposunda ve bugün bir alt dizini 'den' project2e taşımam istendi project1. Ben böyle bir şey yaptım:

$ git clone project2 
$ cd project2
$ git filter-branch --subdirectory-filter deeply/buried/java/source/directory/A -- --all
$ git remote rm origin  # so I don't accidentally overwrite the repo ;-)
$ mkdir -p deeply/buried/different/java/source/directory/B
$ for f in *.java; do 
>  git mv $f deeply/buried/different/java/source/directory/B
>  done
$ git commit -m "moved files to new subdirectory"
$ cd ..
$
$ git clone project1
$ cd project1
$ git remote add p2 ../project2
$ git fetch p2
$ git branch p2 remotes/p2/master
$ git merge p2 # --allow-unrelated-histories for git 2.9+
$ git remote rm p2
$ git push

Ama bu oldukça kıvrımlı görünüyor. Genel olarak bu tür şeyleri yapmanın daha iyi bir yolu var mı? Yoksa doğru yaklaşımı mı benimsedim?

Bunun, yalnızca bir önceki bölümün bir bölümünden (daha önceki bir soruda olduğu gibi ) yeni bir bağımsız havuz oluşturmak yerine, geçmişi mevcut bir havuzda birleştirmeyi içerdiğini unutmayın .


1
Bu bana makul bir yaklaşım gibi geliyor; Yönteminizi önemli ölçüde iyileştirmenin açık bir yolunu düşünemiyorum. Git'in aslında bunu kolaylaştırması güzel ( örneğin, Subversion'daki farklı depolar arasında bir dosya dizini taşımak istemezdim ).
Greg Hewgill

1
@ebneter - Kabuk komut dosyalarını kullanarak (geçmiş bir svn deposundan diğerine taşındı) bunu yaptım. Temelde belirli bir dosya / dirs geçmiş (diffs, taahhüt günlükleri mesajları) ikinci bir depoya tekrar oynadı.
Adam Monsen

1
Ben yapmıyorum merak git fetch p2 && git merge p2yerine git fetch p2 && git branch .. && git merge p2? Düzenleme: tamam, geçerli dalda değil, p2 adlı yeni bir dalda değişiklikleri almak istediğiniz gibi görünüyor.
Lekensteyn

1
--Filter-branch'un dizin yapısını yok etmesini önlemenin bir yolu yok mu? Bu "git mv" adımı, dosya silme ve dosya oluşturma işlemleriyle dolu büyük bir işlemle sonuçlanır.
Edward Falk

1
Git 2.9'dan itibaren ilgisiz geçmişlerin birleştirilmesinin varsayılan olarak izin verilmediğine dikkat edin. İşe yaraması için, işe yaraması --allow-unrelated-historiesiçin sonuncuya ekleyin git merge.
Scott Berrevoets

Yanıtlar:


55

Evet, asılıyor --subdirectory-filterait filter-branchanahtar oldu. Bunu kullandığınız gerçeği aslında daha kolay bir yol olmadığını kanıtlıyor - dosyaları yeniden yazmak (yeniden adlandırılmış) bir alt kümesiyle sonuçlanmak istediğiniz için geçmişi yeniden yazmaktan başka seçeneğiniz yoktu ve tanım gereği karmaları değiştiriyor. Standart komutların hiçbiri (ör. pull) Geçmişi yeniden yazmadığından, bunları gerçekleştirmek için bunları kullanmanın bir yolu yoktur.

Elbette ayrıntıları daraltabilirsiniz - klonlama ve dallanmalarınızdan bazıları kesinlikle gerekli değildi - ancak genel yaklaşım iyi! Bu karmaşık bir utanç, ama elbette, git noktası tarihi yeniden yazmayı kolaylaştırmak değil.


1
dosyanız birkaç dizin içinde taşındıysa ve şimdi bir klasörde bulunuyorsa, alt dizin filtresi yine de çalışır mı? (yani, sadece bir dosyayı taşımak istiyorsam, kendi alt dizinine taşıyabilir ve bu işe yarayacağını
varsayalım

1
@rogerdpack: Hayır, bu yeniden adlandırmalarla dosyayı izlemez. Seçilen alt dizine taşındığı noktada yaratıldığına inanıyorum. Yalnızca bir dosya seçmek istiyorsanız --index-filter, filter-branchman sayfasına bir göz atın .
Cascabel

8
İsimleri nasıl takip edebileceğime dair bir tarif var mı?
Gece Savaşçısı

Bence tarihi korumak ve iyileştirmek git'in ana noktalarından biri.
artburkart

287

Geçmişiniz aklı başındaysa, taahhütleri yama olarak çıkarabilir ve yeni depoya uygulayabilirsiniz:

cd repository
git log --pretty=email --patch-with-stat --reverse --full-index --binary -- path/to/file_or_folder > patch
cd ../another_repository
git am --committer-date-is-author-date < ../repository/patch 

Veya bir satırda

git log --pretty=email --patch-with-stat --reverse -- path/to/file_or_folder | (cd /path/to/new_repository && git am --committer-date-is-author-date)

( Exherbo'nun belgelerinden alınmıştır )


21
Taşımam gereken üç veya 4 dosya için bu, kabul edilen yanıttan çok daha basit bir çözümdü. Yama dosyasındaki yolları yeni depomun dizin yapısına sığdırmak için find-replace ile sonlandırdım.
Rian Sanderson

8
(Resimler gibi) o ikili dosyaları da düzgün geçirilmeden bu yüzden seçenekleri ekledik: git log --pretty=email --patch-with-stat --full-index --binary --reverse -- client > patch. Sorunsuz çalışır AFAICT.
Emmanuel Touzery

35
Uygulama adımında --committer-date-is-author-date, dosyaların taşındığı tarih yerine orijinal teslim tarihini koruma seçeneğini kullandım .
darrenmc

6
tarihte birleştirme taahhütleri "am" komutunu kırıyor. Yukarıdaki git log komutuna "-m --first-parent" ekleyebilirsiniz, sonra benim için çalıştı.
Gábor Lipták

6
@Daniel Altın I (bir hata bir sonucudur taşındı dosyalarla sorunu çözmek için başardınız git loghem çalışmaz böylece, --followve --reversedoğru). Bu yanıtı kullandım ve dosyaları taşımak için şimdi kullandığım tam bir komut dosyası
tsayen

75

Bir dosyayı veya klasörü bir Git deposundan diğerine taşımak için çeşitli yaklaşımlar denedikten sonra, güvenilir şekilde çalışan tek dosya aşağıda özetlenmiştir.

Dosya veya klasörü taşımak istediğiniz deponun klonlanmasını, bu dosyayı veya klasörü kök dizinine taşımayı, Git geçmişini yeniden yazmayı, hedef deposu klonlamayı ve geçmişi olan dosya veya klasörü doğrudan bu hedef depoya çekmeyi içerir.

Birinci aşama

  1. Aşağıdaki adımlar, bu kopya üzerinde itmemeniz gereken önemli değişiklikler yaptığı için A havuzunun bir kopyasını oluşturun!

    git clone --branch <branch> --origin origin --progress \
      -v <git repository A url>
    # eg. git clone --branch master --origin origin --progress \
    #   -v https://username@giturl/scm/projects/myprojects.git
    # (assuming myprojects is the repository you want to copy from)
    
  2. içine cd

    cd <git repository A directory>
    #  eg. cd /c/Working/GIT/myprojects
    
  3. Yanlışlıkla uzaktan değişiklik yapılmasını önlemek için orijinal havuzun bağlantısını silin (örneğin, iterek)

    git remote rm origin
    
  4. Dizin 1'de olmayan her şeyi kaldırarak geçmişinizi ve dosyalarınızı gözden geçirin. Sonuç, dizin 1'in içeriğinin A havuzunun tabanına yazılmasıdır.

    git filter-branch --subdirectory-filter <directory> -- --all
    # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all
    
  5. Yalnızca tek dosya taşıma için: kalanları gözden geçirin ve istenen dosya dışındaki her şeyi kaldırın. (Aynı ad ve taahhütte bulunmasını istemediğiniz dosyaları silmeniz gerekebilir.)

    git filter-branch -f --index-filter \
    'git ls-files -s | grep $'\t'FILE_TO_KEEP$ |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
    git update-index --index-info && \
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || echo "Nothing to do"' --prune-empty -- --all
    # eg. FILE_TO_KEEP = pom.xml to keep only the pom.xml file from FOLDER_TO_KEEP
    

İkinci Aşama

  1. Temizleme adımı

    git reset --hard
    
  2. Temizleme adımı

    git gc --aggressive
    
  3. Temizleme adımı

    git prune
    

Bu dosyaları kök değil, bir dizin içindeki B havuzuna almak isteyebilirsiniz:

  1. Bu dizini yap

    mkdir <base directory>             eg. mkdir FOLDER_TO_KEEP
    
  2. Dosyaları bu dizine taşıyın

    git mv * <base directory>          eg. git mv * FOLDER_TO_KEEP
    
  3. Bu dizine dosya ekle

    git add .
    
  4. Değişikliklerinizi yapın, bu dosyaları yeni depoda birleştirmeye hazırız

    git commit
    

Üçüncü Aşama

  1. Zaten bir dosyanız yoksa, B deposunun bir kopyasını oluşturun

    git clone <git repository B url>
    # eg. git clone https://username@giturl/scm/projects/FOLDER_TO_KEEP.git
    

    (FOLDER_TO_KEEP'in kopyaladığınız yeni deponun adı olduğu varsayılarak)

  2. içine cd

    cd <git repository B directory>
    #  eg. cd /c/Working/GIT/FOLDER_TO_KEEP
    
  3. Depo B'de dal olarak A havuzuna uzak bağlantı oluşturma

    git remote add repo-A-branch <git repository A directory>
    # (repo-A-branch can be anything - it's just an arbitrary name)
    
    # eg. git remote add repo-A-branch /c/Working/GIT/myprojects
    
  4. Bu daldan (yalnızca taşımak istediğiniz dizini içeren) B havuzuna çekin.

    git pull repo-A-branch master --allow-unrelated-histories
    

    Çekme hem dosyaları hem de geçmişi kopyalar. Not: Çekme yerine birleştirme kullanabilirsiniz, ancak çekme daha iyi çalışır.

  5. Son olarak, A deposuna uzak bağlantıyı kaldırarak muhtemelen biraz temizlemek istiyorsunuz

    git remote rm repo-A-branch
    
  6. İtin ve hazırsınız.

    git push
    

Burada özetlenen çoğu adımdan geçtim, ancak yalnızca dosyanın veya dir'ün ana dosyadan (ve diğer şubelerden değil) taahhüt geçmişini kopyalamak gibi görünüyor. Bu doğru mu?
Bao-Long Nguyen-Trong

Bence bu doğru ve dosya veya klasörleri taşımak istediğiniz herhangi bir dal için benzer adımlar atmanız gerekir. şubeye geçme örn. A havuzunda MyBranch, filtre dalı vb. Daha sonra B deposunda "git pull repo-A-branch MyBranch" olur
mcarans

Yanıtınız için teşekkürler. Dallardaki etiketlerin de geçirilip geçirilmeyeceğini biliyor musunuz?
Bao-Long Nguyen-Trong

Korkarım bilmiyorum, ama olacağını tahmin ediyorum.
mcarans

1
@mcarans Ne yazık ki, öyle görünse de, bu güvenilir bir yol DEĞİLDİR. Diğer tüm çözümlerle aynı problemden muzdariptir - Geçmişin yeniden adlandırılmasının ötesinde kalmaz. Benim durumumda, ilk taahhüt dizini / dosyayı yeniden adlandırmaktır. Bunun ötesindeki her şey kaybolur.
xZero

20

Bulduğum bu çok yararlı. Yeni repoya uygulanan yamalar oluşturduğunuz çok basit bir yaklaşımdır. Daha fazla ayrıntı için bağlantılı sayfaya bakın.

Yalnızca üç adım içerir (blogdan kopyalanır):

# Setup a directory to hold the patches
mkdir <patch-directory>

# Create the patches
git format-patch -o <patch-directory> --root /path/to/copy

# Apply the patches in the new repo using a 3 way merge in case of conflicts
# (merges from the other repo are not turned into patches). 
# The 3way can be omitted.
git am --3way <patch-directory>/*.patch

Sahip olduğum tek sorun, tüm yamaları bir kerede kullanarak uygulayamadığım oldu

git am --3way <patch-directory>/*.patch

Windows altında InvalidArgument hatası aldım. Bu yüzden tüm yamaları birbiri ardına uygulamak zorunda kaldım.


Bir ara sha-hashes eksik olduğu için benim için çalışmadı. Bu bana yardımcı oldu: stackoverflow.com/questions/17371150/…
dr0i

"Git log" yaklaşımının aksine, bu seçenek benim için mükemmel çalıştı! Teşekkürler!
AlejandroVD

1
Projeleri yeni repoya taşımak için farklı yaklaşımlar denedim. Benim için çalışan tek kişi bu. Böyle ortak bir görevin bu kadar karmaşık olması gerektiğine inanamıyorum.
Chris_D_Turk

Ross Hendrickson'un blogunu paylaştığın için teşekkürler . Bu yaklaşım benim için çalıştı.
Kaushik Acharya

1
Bu çok zarif bir çözümdür, ancak yine de, diğer tüm çözümlerle aynı sorundan muzdariptir - Geçmişi yeniden adlandırmadan geçmişte kalmayacaktır.
xZero

6

REHBERİNİN ADINI TUTMAK

Alt dizin filtresi (veya kısa komut git subtree) iyi çalışır, ancak dizin adını taahhüt bilgisinden kaldırdığından benim için çalışmadı. Senaryomda, sadece bir deponun parçalarını başka bir havuzla birleştirmek ve tam yol adı İLE geçmişi tutmak istiyorum.

Benim çözümüm, ağaç filtresini kullanmak ve istenmeyen dosyaları ve dizinleri kaynak deponun geçici bir klonundan kaldırmak, sonra bu klondan 5 basit adımda hedef depomun içine çekmekti.

# 1. clone the source
git clone ssh://<user>@<source-repo url>
cd <source-repo>
# 2. remove the stuff we want to exclude
git filter-branch --tree-filter "rm -rf <files to exclude>" --prune-empty HEAD
# 3. move to target repo and create a merge branch (for safety)
cd <path to target-repo>
git checkout -b <merge branch>
# 4. Add the source-repo as remote 
git remote add source-repo <path to source-repo>
# 5. fetch it
git pull source-repo master
# 6. check that you got it right (better safe than sorry, right?)
gitk

Bu komut dosyası , orijinal deponuzda herhangi bir değişiklik yapmaz. Harita dosyasında belirtilen hedef repo yoksa, bu komut dosyası onu oluşturmaya çalışır.
Chetabahana

1
Ayrıca dizin adlarını sağlam tutmanın da son derece önemli olduğunu düşünüyorum. Aksi takdirde, hedef depoya yeniden adlandırma işlemleri yapılır.
ipuustin


5

Bu cevap git am, adım adım örneklerle dayalı ve sunulan ilginç komutlar sağlar .

Amaç

  • Dosyaların bir kısmını veya tamamını bir havuzdan diğerine taşımak istiyorsunuz.
  • Onların geçmişini korumak istiyorsun.
  • Ancak etiketleri ve dalları tutmayı umursamazsınız.
  • Yeniden adlandırılmış dosyalar (ve yeniden adlandırılmış dizinlerdeki dosyalar) için sınırlı geçmişi kabul edersiniz.

prosedür

  1. Kullanarak geçmişi e-posta biçiminde ayıklayın
    git log --pretty=email -p --reverse --full-index --binary
  2. Dosya ağacını yeniden düzenleyin ve geçmişte dosya adı değişikliğini güncelleyin [isteğe bağlı]
  3. Kullanarak yeni geçmiş uygula git am

1. Geçmişi e-posta biçiminde ayıklayın

Örnek: hulasası geçmişi file3, file4vefile5

my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

Geçici dizin hedefini temizleme

export historydir=/tmp/mail/dir  # Absolute path
rm -rf "$historydir"             # Caution when cleaning

Repo kaynağınızı temizleyin

git commit ...           # Commit your working files
rm .gitignore            # Disable gitignore
git clean -n             # Simulate removal
git clean -f             # Remove untracked file
git checkout .gitignore  # Restore gitignore

Her dosyanın geçmişini e-posta biçiminde ayıklayın

cd my_repo/dirB
find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "$0" > "$historydir/$0"' {} ';'

Ne yazık ki seçenek --followveya --find-copies-harderbirleştirilemez --reverse. Dosya yeniden adlandırıldığında (veya bir üst dizin yeniden adlandırıldığında) geçmişin kesilmesinin nedeni budur.

Sonra: E-posta biçiminde geçici geçmiş

/tmp/mail/dir
    ├── subdir
    │   ├── file3
    │   └── file4
    └── file5

2. Dosya ağacını yeniden düzenleyin ve geçmişte dosya adı değişikliğini güncelleyin [isteğe bağlı]

Bu üç dosyayı başka bir repoda taşımak istediğinizi varsayalım (aynı repo olabilir).

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB              # New tree
│   ├── dirB1         # was subdir
│   │   ├── file33    # was file3
│   │   └── file44    # was file4
│   └── dirB2         # new dir
│        └── file5    # = file5
└── dirH
    └── file77

Bu nedenle dosyalarınızı yeniden düzenleyin:

cd /tmp/mail/dir
mkdir     dirB
mv subdir dirB/dirB1
mv dirB/dirB1/file3 dirB/dirB1/file33
mv dirB/dirB1/file4 dirB/dirB1/file44
mkdir    dirB/dirB2
mv file5 dirB/dirB2

Geçici geçmişiniz şimdi:

/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

Geçmişteki dosya adlarını da değiştirin:

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'

Not: Bu, yol ve dosya adı değişikliğini yansıtacak şekilde geçmişi yeniden yazar.
      (örneğin, yeni repodaki yeni konumun / adın değiştirilmesi)


3. Yeni geçmiş uygulayın

Diğer repo:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
└── dirH
    └── file77

Geçici geçmiş dosyalarından taahhütleri uygula:

cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am 

Diğer repo şimdi:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB            ^
│   ├── dirB1       | New files
│   │   ├── file33  | with
│   │   └── file44  | history
│   └── dirB2       | kept
│        └── file5  v
└── dirH
    └── file77

kullanım git status hazır taahhüt miktarını görmek için :-)

Not: Geçmiş, yolu ve dosya adı değişikliğini yansıtacak şekilde yeniden yazıldığı için:
      (yani önceki repodaki konum / adla karşılaştırıldığında)

  • git mvKonum / dosya adını değiştirmenize gerek yoktur .
  • git log --followTam geçmişe erişmeye gerek yok .

Ek numara: Reponuz içindeki yeniden adlandırılmış / taşınmış dosyaları tespit edin

Yeniden adlandırılan dosyaları listelemek için:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

Daha fazla özelleştirme: veya git logseçeneklerini kullanarak komutu tamamlayabilirsiniz . İlk iki sütunu, '{. * =>. *}' Deseninin tamamını kullanarak ve selamlayarak da kaldırabilirsiniz .--find-copies-harder--reversecut -f3-

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'

3

Çizilmeye benzer bir kaşıntıya sahip olan (sadece belirli bir havuzdaki bazı dosyalar için) bu komut dosyasının gerçekten yararlı olduğunu kanıtladı: git-import

Kısa sürüm, $objectvarolan dosya veya dizinin ( ) yama dosyalarını varolan depodan oluşturmasıdır:

cd old_repo
git format-patch --thread -o "$temp" --root -- "$object"

daha sonra yeni bir depoya uygulanır:

cd new_repo
git am "$temp"/*.patch 

Ayrıntılar için lütfen bakınız:


2

Bunu dene

cd repo1

Bu, belirtilenler dışındaki tüm dizinleri kaldıracak ve yalnızca bu dizinler için geçmişi koruyacaktır

git filter-branch --index-filter 'git rm --ignore-unmatch --cached -qr -- . && git reset -q $GIT_COMMIT -- dir1/ dir2/ dir3/ ' --prune-empty -- --all

Artık yeni deponuzu git uzaktan kumandanıza ekleyebilir ve

git remote remove origin <old-repo>
git remote add origin <new-repo>
git push origin <current-branch>

-füzerine yazmak için ekle


UYARI: git-filter-branch'da karışık geçmiş yeniden yazma işlemleri yapan bir dizi var. İptal etmeye devam etmeden önce Ctrl-C tuşlarına basın, ardından 'git filter-repo' ( github.com/newren/git-filter-repo ) gibi alternatif bir filtreleme aracı kullanın. Daha fazla ayrıntı için filtre kolu kılavuz sayfasına bakın; bu uyarıyı susturmak için FILTER_BRANCH_SQUELCH_WARNING = 1 olarak ayarlayın.
Colin

1

Http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ adresinden ilham alarak , bu Powershell işlevini aynı şeyi yapmak için oluşturdum. benim için şimdiye kadar harika çalıştı:

# Migrates the git history of a file or directory from one Git repo to another.
# Start in the root directory of the source repo.
# Also, before running this, I recommended that $destRepoDir be on a new branch that the history will be migrated to.
# Inspired by: http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/
function Migrate-GitHistory
{
    # The file or directory within the current Git repo to migrate.
    param([string] $fileOrDir)
    # Path to the destination repo
    param([string] $destRepoDir)
    # A temp directory to use for storing the patch file (optional)
    param([string] $tempDir = "\temp\migrateGit")

    mkdir $tempDir

    # git log $fileOrDir -- to list commits that will be migrated
    Write-Host "Generating patch files for the history of $fileOrDir ..." -ForegroundColor Cyan
    git format-patch -o $tempDir --root -- $fileOrDir

    cd $destRepoDir
    Write-Host "Applying patch files to restore the history of $fileOrDir ..." -ForegroundColor Cyan
    ls $tempDir -Filter *.patch  `
        | foreach { git am $_.FullName }
}

Bu örnek için kullanım:

git clone project2
git clone project1
cd project1
# Create a new branch to migrate to
git checkout -b migrate-from-project2
cd ..\project2
Migrate-GitHistory "deeply\buried\java\source\directory\A" "..\project1"

Bunu yaptıktan sonra, migrate-from-project2birleştirmeden önce şubedeki dosyaları yeniden düzenleyebilirsiniz .


1

Sağlam ve tekrar kullanılabilir bir şey istedim (one-command-and-go + undo işlevi) bu yüzden aşağıdaki bash betiğini yazdım. Birkaç kez benim için çalıştı, bu yüzden burada paylaşacağımı düşündüm.

Keyfi bir klasörü taşımak yapabiliyor /path/to/foogelen repo1içine /some/other/folder/barkadarrepo2 (kök klasörü mesafe farklı olabilir, aynı ya da farklı olabilir, klasör yolları).

Yalnızca giriş klasöründeki dosyalara (kaynak repo'nun tüm taahhütlerine göre değil) dokunan taahhütlerin üzerinden geçtiği için, büyük kaynak depolarında bile oldukça hızlı olmalı, eğer her birine dokunulmamış derin iç içe bir alt klasör çıkarırsanız taahhüt.

Bunun yaptığı tüm eski repo geçmişine sahip bir öksüz dal oluşturmak ve daha sonra KAFA ile birleştirmek olduğundan, dosya adı çakışmaları durumunda bile çalışacaktır (o zaman elbette bir birleştirmeyi çözmeniz gerekir) .

Dosya adı çakışması yoksa, yapmanız gereken git commit , birleştirme işlemini tamamlamak için sonunda .

Dezavantajı, REWRITE_FROMkaynak repo çekme isteklerinde dosya adlarını ( klasör dışında ) izlemeyecek olması için GitHub'a hoş geldiniz.

GitHub bağlantısı: git-move-folder-between-repos-keep-history

#!/bin/bash

# Copy a folder from one git repo to another git repo,
# preserving full history of the folder.

SRC_GIT_REPO='/d/git-experimental/your-old-webapp'
DST_GIT_REPO='/d/git-experimental/your-new-webapp'
SRC_BRANCH_NAME='master'
DST_BRANCH_NAME='import-stuff-from-old-webapp'
# Most likely you want the REWRITE_FROM and REWRITE_TO to have a trailing slash!
REWRITE_FROM='app/src/main/static/'
REWRITE_TO='app/src/main/static/'

verifyPreconditions() {
    #echo 'Checking if SRC_GIT_REPO is a git repo...' &&
      { test -d "${SRC_GIT_REPO}/.git" || { echo "Fatal: SRC_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO is a git repo...' &&
      { test -d "${DST_GIT_REPO}/.git" || { echo "Fatal: DST_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if REWRITE_FROM is not empty...' &&
      { test -n "${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM is empty"; exit; } } &&
    #echo 'Checking if REWRITE_TO is not empty...' &&
      { test -n "${REWRITE_TO}" || { echo "Fatal: REWRITE_TO is empty"; exit; } } &&
    #echo 'Checking if REWRITE_FROM folder exists in SRC_GIT_REPO' &&
      { test -d "${SRC_GIT_REPO}/${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if SRC_GIT_REPO has a branch SRC_BRANCH_NAME' &&
      { cd "${SRC_GIT_REPO}"; git rev-parse --verify "${SRC_BRANCH_NAME}" || { echo "Fatal: SRC_BRANCH_NAME does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO has a branch DST_BRANCH_NAME' &&
      { cd "${DST_GIT_REPO}"; git rev-parse --verify "${DST_BRANCH_NAME}" || { echo "Fatal: DST_BRANCH_NAME does not exist inside DST_GIT_REPO"; exit; } } &&
    echo '[OK] All preconditions met'
}

# Import folder from one git repo to another git repo, including full history.
#
# Internally, it rewrites the history of the src repo (by creating
# a temporary orphaned branch; isolating all the files from REWRITE_FROM path
# to the root of the repo, commit by commit; and rewriting them again
# to the original path).
#
# Then it creates another temporary branch in the dest repo,
# fetches the commits from the rewritten src repo, and does a merge.
#
# Before any work is done, all the preconditions are verified: all folders
# and branches must exist (except REWRITE_TO folder in dest repo, which
# can exist, but does not have to).
#
# The code should work reasonably on repos with reasonable git history.
# I did not test pathological cases, like folder being created, deleted,
# created again etc. but probably it will work fine in that case too.
#
# In case you realize something went wrong, you should be able to reverse
# the changes by calling `undoImportFolderFromAnotherGitRepo` function.
# However, to be safe, please back up your repos just in case, before running
# the script. `git filter-branch` is a powerful but dangerous command.
importFolderFromAnotherGitRepo(){
    SED_COMMAND='s-\t\"*-\t'${REWRITE_TO}'-'

    verifyPreconditions &&
    cd "${SRC_GIT_REPO}" &&
      echo "Current working directory: ${SRC_GIT_REPO}" &&
      git checkout "${SRC_BRANCH_NAME}" &&
      echo 'Backing up current branch as FILTER_BRANCH_BACKUP' &&
      git branch -f FILTER_BRANCH_BACKUP &&
      SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
      echo "Creating temporary branch '${SRC_BRANCH_NAME_EXPORTED}'..." &&
      git checkout -b "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo 'Rewriting history, step 1/2...' &&
      git filter-branch -f --prune-empty --subdirectory-filter ${REWRITE_FROM} &&
      echo 'Rewriting history, step 2/2...' &&
      git filter-branch -f --index-filter \
       "git ls-files -s | sed \"$SED_COMMAND\" |
        GIT_INDEX_FILE=\$GIT_INDEX_FILE.new git update-index --index-info &&
        mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE" HEAD &&
    cd - &&
    cd "${DST_GIT_REPO}" &&
      echo "Current working directory: ${DST_GIT_REPO}" &&
      echo "Adding git remote pointing to SRC_GIT_REPO..." &&
      git remote add old-repo ${SRC_GIT_REPO} &&
      echo "Fetching from SRC_GIT_REPO..." &&
      git fetch old-repo "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo "Checking out DST_BRANCH_NAME..." &&
      git checkout "${DST_BRANCH_NAME}" &&
      echo "Merging SRC_GIT_REPO/" &&
      git merge "old-repo/${SRC_BRANCH_NAME}-exported" --no-commit &&
    cd -
}

# If something didn't work as you'd expect, you can undo, tune the params, and try again
undoImportFolderFromAnotherGitRepo(){
  cd "${SRC_GIT_REPO}" &&
    SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
    git checkout "${SRC_BRANCH_NAME}" &&
    git branch -D "${SRC_BRANCH_NAME_EXPORTED}" &&
  cd - &&
  cd "${DST_GIT_REPO}" &&
    git remote rm old-repo &&
    git merge --abort
  cd -
}

importFolderFromAnotherGitRepo
#undoImportFolderFromAnotherGitRepo

0

Benim durumumda, göç ettiğim repoyu veya daha önceki bir tarihi korumam gerekmiyordu. Farklı bir uzaktan kumandadan aynı daldan bir yama aldım

#Source directory
git remote rm origin
#Target directory
git remote add branch-name-from-old-repo ../source_directory

Bu iki adımda, diğer repo şubesinin aynı repoda görünmesini sağlayabildim.

Son olarak, bu dalı (diğer repodan ithal ettiğim) hedef repo'nun ana hattını takip edecek şekilde ayarladım (böylece onları doğru bir şekilde ayırabilirim)

git br --set-upstream-to=origin/mainline

Şimdi sanki aynı repoya karşı ittiğim başka bir dalmış gibi davrandı.


0

Söz konusu dosyaların yolları iki depoda aynıysa ve yalnızca bir dosya veya küçük bir ilgili dosya kümesi getirmek istiyorsanız, bunu yapmanın kolay bir yolu kullanmaktır git cherry-pick.

İlk adım, diğer repodaki taahhütleri kullanarak kendi yerel repolarınıza getirmektir git fetch <remote-url>. Bu FETCH_HEAD, diğer repodan baş taahhütlerine işaret edecek; diğer getirmeleri yaptıktan sonra söz konusu taahhüdün bir referansını korumak istiyorsanız, etiketlemek isteyebilirsiniz git tag other-head FETCH_HEAD.

Daha sonra, o dosya için bir başlangıç ​​taahhüdü (yoksa) veya dosyayı getirmek istediğiniz diğer repo'dan ilk taahhüde eklenmiş bir duruma getirme taahhüdü oluşturmanız gerekir. bir ile bunu mümkün git cherry-pick <commit-0>olmadığını commit-0istediğiniz dosyaları tanıtıldı veya 'elle' taahhüt inşa etmek gerekebilir. -nBaşlangıçtaki taahhüdünü değiştirmeniz gerekiyorsa, örneğin getirmek istemediğiniz söz konusu taahhütten dosyaları bırakmanız gerekiyorsa kiraz toplama seçeneklerine ekleyin .

Bundan sonra git cherry-pick, yine -ngerektiğinde kullanarak sonraki taahhütlere devam edebilirsiniz . En basit durumda, kiraz almak komut satırında kaydedilmesini tam listesini verebilir (bütün kaydedilmesini istediğiniz ve temiz bir şekilde uygulamak tam olarak ne olduğu): git cherry-pick <commit-1> <commit-2> <commit-3> ....


0

Git-filter-repo kullanarak bu daha basit hale gelir.

Taşımak project2/sub/diriçin project1/sub/dir:

# Create a new repo containing only the subdirectory:
git clone project2 project2_subdir
cd project2_subdir
git filter-repo --force --path sub/dir

# Merge the new repo:
cd ../project1
git remote add project2_subdir ../project2_subdir/
git merge remotes/project2_subdir/master --allow-unrelated-histories
git remote remove project2_subdir

Aracı basitçe yüklemek için: pip3 install git-filter-repo ( README'de daha fazla ayrıntı ve seçenek )

# Before: (root)
.
|-- project1
|   `-- 3
`-- project2
    |-- 1
    `-- sub
        `-- dir
            `-- 2

# After: (project1)
.
├── 3
└── sub
    └── dir
        └── 2

-2

Tüm yöntemleri koruyarak ve geçmişi koruyarak GIT Stash'ımı GitLab'a geçirmek için aşağıdaki yöntem.

Eski depoyu yerel klonlayın.

git clone --bare <STASH-URL>

GitLab'de boş bir havuz oluşturun.

git push --mirror <GitLab-URL>

Yukarıdaki kodumuzu stash'dan GitLab'a geçirdiğimizde gerçekleştirdim ve çok iyi çalıştı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.