Jak przenosić pliki z jednego repozytorium git na inny (nie klon), zachowując historię


483

Nasze repozytoria Git powstały jako części jednego repozytorium SVN z potworami, w którym każdy projekt miał swoje własne drzewo:

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

Oczywiście przenoszenie plików między nimi było dość łatwe svn mv. Ale w Git każdy projekt znajduje się we własnym repozytorium, a dziś zostałem poproszony o przeniesienie podkatalogu z project2do project1. Zrobiłem coś takiego:

$ 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

Ale to wydaje się dość skomplikowane. Czy jest lepszy sposób na robienie tego w ogóle? Czy też zastosowałem właściwe podejście?

Zauważ, że obejmuje to scalenie historii w istniejące repozytorium, a nie po prostu utworzenie nowego autonomicznego repozytorium z części innego ( jak we wcześniejszym pytaniu ).


1
To brzmi dla mnie rozsądnie; Nie mogę wymyślić żadnego oczywistego sposobu na znaczne ulepszenie twojej metody. Fajnie, że Git naprawdę to ułatwia (na przykład nie chciałbym próbować przenosić katalogu plików między różnymi repozytoriami ).
— Greg Hewgill

1
@ebneter - Zrobiłem to (przeniosłem historię z jednego repozytorium svn na inny) ręcznie, używając skryptów powłoki. Zasadniczo odtworzyłem historię (różnice, komunikaty dzienników zatwierdzania) z poszczególnych plików / katalogów do drugiego repozytorium.
— Adam Monsen,

1
Zastanawiam się, dlaczego git fetch p2 && git merge p2zamiast tego nie robisz git fetch p2 && git branch .. && git merge p2? Edycja: w porządku, wygląda na to, że chcesz uzyskać zmiany w nowej gałęzi o nazwie p2, a nie w bieżącej gałęzi.
— Lekensteyn,

1
Czy nie ma sposobu, aby zapobiec zniszczeniu struktury katalogów przez --filter-branch? Ten krok „git mv” powoduje masowe zatwierdzanie pełne usuwania plików i tworzenia plików.
— Edward Falk,

1
Zauważ, że od git 2.9 łączenie niepowiązanych historii jest domyślnie zabronione. Aby działało, dodaj --allow-unrelated-historiesdo ostatniego, git mergeaby działało.
— Scott Berrevoets,

Odpowiedzi:


55

Tak, trafiając na --subdirectory-filterod filter-branchbył klucz. Fakt, że użyłeś go zasadniczo, dowodzi, że nie ma prostszego sposobu - nie miałeś innego wyboru, jak przepisać historię, ponieważ chciałeś skończyć tylko z (zmienioną nazwą) podzbiorem plików, a to z definicji zmienia skróty. Ponieważ żadne ze standardowych poleceń (np. pull) Nie przepisuje historii, nie ma możliwości użycia ich do osiągnięcia tego celu.

Można oczywiście dopracować szczegóły - niektóre z klonowania i rozgałęziania nie były absolutnie konieczne - ale ogólne podejście jest dobre! Szkoda, że ​​to skomplikowane, ale oczywiście celem git nie jest ułatwienie przepisywania historii.


1
co jeśli plik został przeniesiony przez kilka katalogów, a teraz znajduje się w jednym - czy filtr podkatalogów nadal będzie działał? (tj Jestem zakładając, że jeśli po prostu chcesz przenieść jeden plik, mogę go przenieść do własnego podkatalogu i tej pracy?)
— rogerdpack

1
@rogerdpack: Nie, to nie spowoduje zmiany nazwy pliku. Wydaje mi się, że zostanie utworzony w momencie przeniesienia do wybranego podkatalogu. Jeśli chcesz wybrać tylko jeden plik, zajrzyj na --index-filterstronę filter-branchpodręcznika.
— Cascabel,

8
Czy jest jakiś przepis na to, jak mogę śledzić zmiany nazw?
— Night Warrier

Myślę, że utrzymywanie i kurowanie historii jest jednym z głównych punktów git.
— artburkart

287

Jeśli twoja historia jest zdrowa, możesz wziąć zatwierdzenia jako łatkę i zastosować je w nowym repozytorium:

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 

Lub w jednej linii

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)

(Zaczerpnięte z dokumentów Exherbo )


21
W przypadku trzech lub czterech plików, które musiałem przenieść, było to znacznie prostsze rozwiązanie niż zaakceptowana odpowiedź. Skończyłem przycinanie ścieżek w pliku łatki za pomocą funkcji find-replace, aby dopasować ją do struktury katalogów mojego nowego repozytorium.
— Rian Sanderson

8
Dodałem opcje tak, że pliki binarne (takie jak obrazy) są również prawidłowo przeniesione: git log --pretty=email --patch-with-stat --full-index --binary --reverse -- client > patch. Działa bez problemów AFAICT.
— Emmanuel Touzery

35
W kroku zastosowałem --committer-date-is-author-dateopcję zachowania pierwotnej daty zatwierdzenia zamiast daty przeniesienia plików.
— darrenmc

6
scalanie zatwierdzeń w historii złamanie polecenia „jestem”. Możesz dodać „-m --first-parent” do powyższej komendy git log, a potem zadziałało to dla mnie.
— Gábor Lipták,

6
@Daniel Golden Udało mi się naprawić problem z przeniesionymi plikami (co jest konsekwencją błędu git log, aby nie działał z obydwoma --followi --reversepoprawnie). Użyłem tej odpowiedzi , a oto kompletny skrypt, którego używam teraz do przenoszenia plików
— tsayen 14.04.16

75

Po wypróbowaniu różnych metod przenoszenia pliku lub folderu z jednego repozytorium Git do drugiego, jedyne, które wydaje się działać niezawodnie, zostało przedstawione poniżej.

Polega on na klonowaniu repozytorium, z którego chcesz przenieść plik lub folder, przenoszeniu tego pliku lub folderu do katalogu głównego, przepisywaniu historii Git, klonowaniu docelowego repozytorium i pobieraniu pliku lub folderu z historią bezpośrednio do tego docelowego repozytorium.

Scena pierwsza

  1. Stwórz kopię repozytorium A, ponieważ poniższe kroki wprowadzają poważne zmiany w tej kopii, których nie powinieneś wypychać!

    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. cd do tego

    cd <git repository A directory>
    #  eg. cd /c/Working/GIT/myprojects
    
  3. Usuń link do oryginalnego repozytorium, aby uniknąć przypadkowego wprowadzenia jakichkolwiek zdalnych zmian (np. Przez popchnięcie)

    git remote rm origin
    
  4. Przejrzyj swoją historię i pliki, usuwając wszystko, czego nie ma w katalogu 1. Wynikiem jest zawartość katalogu 1 wyrzucona do bazy repozytorium A.

    git filter-branch --subdirectory-filter <directory> -- --all
    # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all
    
  5. Tylko w przypadku przenoszenia pojedynczego pliku: przejrzyj to, co zostało i usuń wszystko oprócz żądanego pliku. (Może być konieczne usunięcie niepotrzebnych plików o tej samej nazwie i zatwierdzenie).

    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
    

Etap drugi

  1. Krok czyszczenia

    git reset --hard
    
  2. Krok czyszczenia

    git gc --aggressive
    
  3. Krok czyszczenia

    git prune
    

Możesz zaimportować te pliki do repozytorium B w katalogu innym niż katalog główny:

  1. Utwórz ten katalog

    mkdir <base directory>             eg. mkdir FOLDER_TO_KEEP
    
  2. Przenieś pliki do tego katalogu

    git mv * <base directory>          eg. git mv * FOLDER_TO_KEEP
    
  3. Dodaj pliki do tego katalogu

    git add .
    
  4. Zatwierdź zmiany i jesteśmy gotowi scalić te pliki w nowym repozytorium

    git commit
    

Etap trzeci

  1. Zrób kopię repozytorium B, jeśli jeszcze go nie masz

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

    (zakładając, że FOLDER_TO_KEEP to nazwa nowego repozytorium, do którego kopiujesz)

  2. cd do tego

    cd <git repository B directory>
    #  eg. cd /c/Working/GIT/FOLDER_TO_KEEP
    
  3. Utwórz zdalne połączenie z repozytorium A jako gałąź w repozytorium B

    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. Wyciągnij z tej gałęzi (zawierającej tylko katalog, który chcesz przenieść) do repozytorium B.

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

    Pull kopiuje zarówno pliki, jak i historię. Uwaga: Możesz użyć scalenia zamiast pull, ale pull działa lepiej.

  5. Wreszcie, prawdopodobnie chcesz trochę posprzątać, usuwając zdalne połączenie z repozytorium A.

    git remote rm repo-A-branch
    
  6. Naciśnij i wszystko gotowe.

    git push
    

Przeszedłem większość opisanych tutaj kroków, jednak wydaje się, że kopiują tylko historię zatwierdzeń pliku lub katalogu z mastera (a nie z innych gałęzi). Czy to prawda?
— Bao-Long Nguyen-Trong

Myślę, że to prawda i że musisz wykonać podobne kroki dla wszystkich gałęzi, z których chcesz przenieść pliki lub foldery, tj. przejść do oddziału np. MyBranch w repozytorium A, filtrze oddziału itp. W takim razie „git pull repo-A-Branch MyBranch” w repozytorium B.
— mcarans

Dziękuję za odpowiedź. Czy wiesz, czy migrowane będą również tagi na gałęziach?
— Bao-Long Nguyen-Trong

Obawiam się, że nie wiem, ale zgaduję, że tak będzie.
— mcarans

1
@mcarans Niestety, NIE jest to niezawodny sposób, choć wydaje się, że tak jest. Ma ten sam problem, co wszystkie inne rozwiązania - nie zachowuje historii po zmianie nazwy. W moim przypadku pierwsze zatwierdzenie następuje, gdy zmieniam nazwę katalogu / pliku. Wszystko poza tym zostaje utracone.
— xZero

20

Uważam to za bardzo przydatne. Jest to bardzo proste podejście, w którym tworzysz łatki, które są stosowane do nowego repozytorium. Zobacz link do strony po więcej szczegółów.

Zawiera tylko trzy kroki (skopiowane z bloga):

# 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

Jedynym problemem, jaki miałem, było to, że nie mogłem zastosować wszystkich łatek jednocześnie

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

W systemie Windows wystąpił błąd InvalidArgument. Musiałem więc nakładać wszystkie łatki jedna po drugiej.


Nie działało dla mnie, ponieważ w pewnym momencie brakowało sha-hashes. Pomogło mi to: stackoverflow.com/questions/17371150/…
— dr0i

W przeciwieństwie do podejścia „git log” ta opcja działała dla mnie idealnie! dzięki!
— AlejandroVD

1
Wypróbowałem różne podejścia do przenoszenia projektów do nowej repozytorium. To jedyny, który dla mnie działał. Nie mogę uwierzyć, że tak powszechne zadanie musi być tak skomplikowane.
— Chris_D_Turk

Dzięki za udostępnienie bloga Rossa Hendricksona . To podejście zadziałało dla mnie.
— Kaushik Acharya

1
Jest to bardzo eleganckie rozwiązanie, jednak znowu cierpi z powodu tego samego problemu, co wszystkie inne rozwiązania - NIE zachowa historii po zmianie nazwy.
— xZero

6

UTRZYMANIE NAZWY KATALOGU

Filtr podkatalogów (lub krótsze polecenie git poddrzewo) działa dobrze, ale nie działał dla mnie, ponieważ usuwają nazwę katalogu z informacji o zatwierdzeniu. W moim scenariuszu chcę po prostu połączyć części jednego repozytorium w inne i zachować historię Z pełną nazwą ścieżki.

Moim rozwiązaniem było użycie filtru drzewa i usunięcie niechcianych plików i katalogów z tymczasowego klonu źródłowego repozytorium, a następnie pobranie tego klonu do mojego docelowego repozytorium w 5 prostych krokach.

# 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

Ten skrypt nie wprowadza żadnych zmian w oryginalnym repozytorium. Jeśli rep rep dest określone w pliku mapy nie istnieje, ten skrypt spróbuje go utworzyć.
— Chetabahana

1
Myślę też, że utrzymanie nienaruszonych nazw katalogów jest niezwykle ważne. W przeciwnym razie otrzymasz dodatkowe zmiany nazw do repozytorium docelowego.
— ipuustin


5

Ta odpowiedź dostarcza ciekawych poleceń opartych na git amprzykładach i krok po kroku.

Cel

  • Chcesz przenieść niektóre lub wszystkie pliki z jednego repozytorium do drugiego.
  • Chcesz zachować ich historię.
  • Ale nie dbasz o utrzymanie tagów i gałęzi.
  • Akceptujesz ograniczoną historię plików o zmienionych nazwach (i plików w katalogach o zmienionych nazwach).

Procedura

  1. Wyodrębnij historię w formacie wiadomości e-mail za pomocą
    git log --pretty=email -p --reverse --full-index --binary
  2. Zreorganizuj drzewo plików i zaktualizuj zmianę nazwy pliku w historii [opcjonalnie]
  3. Zastosuj nową historię za pomocą git am

1. Wyodrębnij historię w formacie wiadomości e-mail

Przykład: Historia Ekstrakt file3, file4ifile5

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

Wyczyść tymczasowy katalog docelowy

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

Wyczyść źródło repozytorium

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

Wyodrębnij historię każdego pliku w formacie e-mail

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"' {} ';'

Niestety opcja --followlub --find-copies-hardernie można jej połączyć --reverse. Dlatego historia jest wycinana, gdy nazwa pliku jest zmieniana (lub gdy nazwa katalogu nadrzędnego jest zmieniana).

Po: historia tymczasowa w formacie wiadomości e-mail

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

2. Zreorganizuj drzewo plików i zaktualizuj zmianę nazwy pliku w historii [opcjonalnie]

Załóżmy, że chcesz przenieść te trzy pliki w tym drugim repozytorium (może to być to samo repo).

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

Dlatego zreorganizuj swoje pliki:

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

Twoja tymczasowa historia jest teraz:

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

Zmień także nazwy plików w historii:

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

Uwaga: Przepisuje historię, aby odzwierciedlić zmianę ścieżki i nazwy pliku.
      (tj. zmiana nowej lokalizacji / nazwy w ramach nowego repozytorium)


3. Zastosuj nową historię

Twoje inne repo to:

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

Zastosuj zatwierdzenia z tymczasowych plików historii:

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

Twoje drugie repo to teraz:

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

Użyj, git statusaby zobaczyć liczbę zatwierdzeń gotowych do wypchnięcia :-)

Uwaga: Ponieważ historia została przepisana w celu odzwierciedlenia zmiany ścieżki i nazwy pliku:
      (tj. W porównaniu do lokalizacji / nazwy w poprzednim repozytorium)

  • Nie trzeba git mvzmieniać lokalizacji / nazwy pliku.
  • Nie ma potrzeby git log --followuzyskiwania dostępu do pełnej historii.

Dodatkowa sztuczka: wykryj pliki o zmienionej nazwie / przeniesione w repozytorium

Aby wyświetlić listę plików, których nazwy zostały zmienione:

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

Więcej dostosowań: Możesz wykonać polecenie, git logużywając opcji --find-copies-harderlub --reverse. Możesz także usunąć pierwsze dwie kolumny, używając cut -f3-i grepując pełny wzór „{. * =>. *}”.

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

3

Skrypt miał podobny świąd do zera (chociaż tylko dla niektórych plików z danego repozytorium). Ten skrypt okazał się bardzo pomocny: git-import

Krótka wersja polega na tym, że tworzy pliki łatek z danego pliku lub katalogu ( $object) z istniejącego repozytorium:

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

które następnie zostaną zastosowane do nowego repozytorium:

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

Aby uzyskać szczegółowe informacje, wyszukaj:


2

Spróbuj tego

cd repo1

Spowoduje to usunięcie wszystkich katalogów oprócz wymienionych, zachowując historię tylko dla tych katalogów

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

Teraz możesz dodać swoje nowe repozytorium do pilota git i wcisnąć je do tego

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

dodaj -fdo zastąpienia


OSTRZEŻENIE: git-filter-branch ma nadmiar gotchas generujących zniekształcone zapisy historii. Naciśnij Ctrl-C przed kontynuowaniem przerywania, a następnie użyj alternatywnego narzędzia filtrującego, takiego jak „git filter-repo” ( github.com/newren/git-filter-repo ). Aby uzyskać więcej informacji, zobacz stronę podręcznika gałęzi filtrów. aby usunąć to ostrzeżenie, ustaw FILTER_BRANCH_SQUELCH_WARNING = 1.
— Colin

1

Korzystając z inspiracji z http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ , stworzyłem tę funkcję PowerShell do robienia tego samego, która ma do tej pory działało świetnie dla mnie:

# 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 }
}

Zastosowanie w tym przykładzie:

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"

Po wykonaniu tej czynności możesz ponownie uporządkować pliki w migrate-from-project2oddziale przed scaleniem.


1

Chciałem czegoś solidnego i wielokrotnego użytku (jedna komenda-i-go + cofnij), więc napisałem następujący skrypt bash. Kilkakrotnie pracował dla mnie, więc pomyślałem, że podzielę się tutaj.

Jest w stanie przenieść dowolny folder /path/to/fooz repo1do /some/other/folder/bardo repo2(ścieżki folderów mogą być takie same lub różne, odległość od folderu głównego może być inna).

Ponieważ dotyczy tylko zatwierdzeń dotykających plików w folderze wejściowym (nie wszystkich zatwierdzeń repozytorium źródłowego), powinno być dość szybkie nawet w przypadku dużych repozytoriów źródłowych, jeśli tylko wyodrębnisz głęboko zagnieżdżony podfolder, który nie został dotknięty w każdym popełnić.

Ponieważ polega to na utworzeniu osieroconej gałęzi z całą historią starego repozytorium, a następnie scaleniu jej z HEAD, zadziała nawet w przypadku kolizji nazw plików (wtedy oczywiście musisz rozwiązać scalenie na końcu) .

Jeśli nie ma konfliktów nazw plików, wystarczy git commitna końcu, aby zakończyć scalanie.

Minusem jest to, że prawdopodobnie nie będzie śledzić zmian nazw plików (poza REWRITE_FROMfolderem) w repozytorium źródłowym - mile widziane żądania ściągania w GitHub w celu uwzględnienia tego.

Łącze GitHub: 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

W moim przypadku nie musiałem zachowywać repozytorium, z którego migrowałem, ani żadnej wcześniejszej historii. Miałem łatkę z tej samej gałęzi, z innego pilota

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

W tych dwóch krokach udało mi się sprawić, aby gałąź drugiego repozytorium pojawiła się w tym samym repozytorium.

Na koniec ustawiłem tę gałąź (którą zaimportowałem z drugiego repozytorium), aby podążała za główną linią repozytorium docelowego (aby móc je dokładnie różnicować)

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

Teraz zachowywał się tak, jakby to była kolejna gałąź, którą naciskałem na to samo repo.


0

Jeśli ścieżki dla danych plików są takie same w dwóch repozytoriach i chcesz przenieść tylko jeden plik lub mały zestaw powiązanych plików, jednym z łatwych sposobów jest użycie git cherry-pick .

Pierwszym krokiem jest przeniesienie zatwierdzeń z drugiego repozytorium do twojego lokalnego repozytorium przy użyciu git fetch <remote-url>. Spowoduje to pozostawienie FETCH_HEADwskazywania na zatwierdzenie głowy z drugiego repozytorium; jeśli chcesz zachować odniesienie do tego zatwierdzenia po wykonaniu innych operacji pobierania, możesz je oznaczyć tagiem git tag other-head FETCH_HEAD.

Następnie musisz utworzyć wstępne zatwierdzenie dla tego pliku (jeśli nie istnieje) lub zatwierdzenie, aby doprowadzić plik do stanu, który można załatać za pomocą pierwszego zatwierdzenia z innego repozytorium, które chcesz wprowadzić. być w stanie zrobić to z git cherry-pick <commit-0>gdyby commit-0wprowadzono żądane pliki, lub być może trzeba skonstruować commit „ręcznie”. Dodaj -ndo opcji cherry-pick, jeśli chcesz zmodyfikować początkowe zatwierdzenie, np. Upuść pliki z tego zatwierdzenia, którego nie chcesz wprowadzać.

Następnie możesz przejść do git cherry-pickkolejnych zatwierdzeń, używając ponownie w -nrazie potrzeby. W najprostszym przypadku (wszystkie rewizje są dokładnie to, co chcesz i zaaplikowana) można podać pełną listę zatwierdzeń w linii poleceń cherry-pick: git cherry-pick <commit-1> <commit-2> <commit-3> ....


0

To staje się prostsze dzięki użyciu git-filter-repo.

Aby przejść project2/sub/dirdo 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

Aby zainstalować narzędzie po prostu: pip3 install git-filter-repo ( więcej szczegółów i opcji w README )

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

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

-2

Poniższa metoda migracji mojego GIT Stash do GitLab poprzez utrzymanie wszystkich gałęzi i zachowanie historii.

Sklonuj stare repozytorium do lokalnego.

git clone --bare <STASH-URL>

Utwórz puste repozytorium w GitLab.

git push --mirror <GitLab-URL>

Powyższe zrobiłem, gdy przeprowadziliśmy migrację naszego kodu ze skrytki do GitLab i działało to bardzo dobrze.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.