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 ).

ebneter
źródło
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.

Cascabel
źródło
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 )

Smar
źródło
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
    
Marcarans
źródło
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.

anhoppe
źródło
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
Joachim Nilsson
źródło
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
6

Ten, którego zawsze używam, jest tutaj http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ . Prosty i szybki.

Aby zachować zgodność ze standardami przepływu stosu, oto procedura:

mkdir /tmp/mergepatchs
cd ~/repo/org
export reposrc=myfile.c #or mydir
git format-patch -o /tmp/mergepatchs $(git log $reposrc|grep ^commit|tail -1|awk '{print $2}')^..HEAD $reposrc
cd ~/repo/dest
git am /tmp/mergepatchs/*.patch
Hugh Perkins
źródło
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 '{.* => .*}'
olibre
źródło
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:

ViToni
źródło
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

Chetan Basutkar
źródło
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.

crimbo
źródło
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
jakub.g
źródło
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.

Jason D.
źródło
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> ....

cjs
źródło
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
Tapuzi
źródło
-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.

Roopkumar Akubathini
źródło