Come spostare i file da un repository git a un altro (non un clone), preservando la cronologia


484

I nostri repository Git sono nati come parti di un repository SVN a mostro singolo in cui i singoli progetti avevano il proprio albero in questo modo:

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

Ovviamente, è stato abbastanza facile spostare i file da uno all'altro svn mv. Ma in Git, ogni progetto è nel proprio repository e oggi mi è stato chiesto di spostare una sottodirectory da project2a project1. Ho fatto qualcosa del genere:

$ 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

Ma sembra piuttosto contorto. C'è un modo migliore per fare questo genere di cose in generale? O ho adottato l'approccio giusto?

Si noti che ciò comporta la fusione della cronologia in un repository esistente, piuttosto che semplicemente la creazione di un nuovo repository autonomo da parte di un altro ( come in una domanda precedente ).


1
Sembra un approccio ragionevole per me; Non riesco a pensare a un modo ovvio per migliorare significativamente il tuo metodo. È bello che Git lo renda davvero facile (non vorrei provare a spostare una directory di file tra diversi repository in Subversion, per esempio).
Greg Hewgill,

1
@ebneter: l'ho fatto manualmente (ho spostato la cronologia da un repository svn a un altro) manualmente, usando gli script di shell. Fondamentalmente ho riprodotto la cronologia (diff, commit log dei messaggi) da particolari file / directory in un secondo repository.
Adam Monsen,

1
Mi chiedo perché non lo fai git fetch p2 && git merge p2invece di git fetch p2 && git branch .. && git merge p2? Modifica: va bene, sembra che tu voglia ottenere le modifiche in un nuovo ramo chiamato p2, non nel ramo corrente.
Lekensteyn,

1
Non c'è modo di impedire a --filter-branch di distruggere la struttura delle directory? Quel passaggio "git mv" si traduce in un enorme impegno pieno di eliminazioni di file e creazioni di file.
Edward Falk,

1
Nota che a partire da git 2.9 la fusione di storie non correlate è vietata per impostazione predefinita. Per farlo funzionare, aggiungi --allow-unrelated-historiesl'ultimo git mergeper farlo funzionare.
Scott Berrevoets il

Risposte:


55

Sì, colpire il --subdirectory-filterdi filter-branchera la chiave. Il fatto che tu l'abbia usato essenzialmente dimostra che non c'è modo più semplice: non avevi altra scelta se non quella di riscrivere la cronologia, dal momento che volevi finire con solo un sottoinsieme (rinominato) dei file, e questo per definizione cambia gli hash. Poiché nessuno dei comandi standard (ad es. pull) Riscrive la cronologia, non è possibile usarli per farlo.

Potresti perfezionare i dettagli, ovviamente - alcune delle tue clonazioni e ramificazioni non erano strettamente necessarie - ma l'approccio generale è buono! È un peccato che sia complicato, ma ovviamente il punto cruciale non è quello di semplificare la riscrittura della storia.


1
cosa succede se il file è stato spostato in più directory e ora risiede in uno - il filtro sottodirectory funzionerà comunque? (cioè suppongo che se voglio solo spostare un file, posso spostarlo nella sua sottodirectory e questo funzionerà?)
rogerdpack

1
@rogerdpack: No, questo non seguirà il file attraverso i nomi. Credo che sembrerà essere stato creato nel punto in cui è stato spostato nella sottodirectory selezionata. Se si desidera selezionare un solo file, dare un'occhiata --index-filteralla filter-branchpagina di manuale.
Cascabel,

8
Esiste una ricetta su come posso seguire i nomi?
Night Warrier

Penso che mantenere e curare la storia sia uno dei punti principali di Git.
artburkart

288

Se la tua cronologia è sana, puoi eliminare i commit come patch e applicarli nel nuovo repository:

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 

O in una riga

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)

(Tratto dai documenti di Exherbo )


21
Per i tre o 4 file che dovevo spostare, questa era una soluzione molto più semplice della risposta accettata. Ho finito per tagliare i percorsi nel file patch con find-replace per farlo rientrare nella struttura di directory del mio nuovo repository.
Rian Sanderson,

8
Ho opzioni aggiunto in modo che i file binari (come le immagini) sono anche eseguita correttamente: git log --pretty=email --patch-with-stat --full-index --binary --reverse -- client > patch. Funziona senza problemi AFAICT.
Emmanuel Touzery,

35
Nella fase di applicazione ho usato l' --committer-date-is-author-dateopzione per conservare la data di commit originale invece della data in cui i file sono stati spostati.
darrenmc,

6
unione commette nella cronologia rompere il comando "am". Puoi aggiungere "-m --first-parent" al comando git log sopra, quindi ha funzionato per me.
Gábor Lipták,

6
@Daniel Golden Sono riuscito a risolvere il problema con i file che sono stati spostati (che è una conseguenza di un bug in git log, in modo che non funzioni con entrambi --followe --reversecorrettamente). Ho usato questa risposta , ed ecco uno script completo che uso ora per spostare i file
tsayen,

75

Dopo aver provato vari approcci per spostare un file o una cartella da un repository Git a un altro, l'unico che sembra funzionare in modo affidabile è delineato di seguito.

Implica la clonazione del repository da cui si desidera spostare il file o la cartella, lo spostamento del file o della cartella nella radice, la riscrittura della cronologia di Git, la clonazione del repository di destinazione e il pull del file o della cartella con la cronologia direttamente in questo repository di destinazione.

Fase uno

  1. Fai una copia del repository A poiché i seguenti passaggi apportano importanti modifiche a questa copia che non dovresti spingere!

    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 in esso

    cd <git repository A directory>
    #  eg. cd /c/Working/GIT/myprojects
    
  3. Elimina il collegamento al repository originale per evitare di apportare accidentalmente modifiche remote (ad es. Premendo)

    git remote rm origin
    
  4. Scorri la cronologia e i file, rimuovendo tutto ciò che non si trova nella directory 1. Il risultato è il contenuto della directory 1 che viene diffuso nella base del repository A.

    git filter-branch --subdirectory-filter <directory> -- --all
    # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all
    
  5. Solo per lo spostamento di un singolo file: passa attraverso ciò che resta e rimuovi tutto tranne il file desiderato. (Potrebbe essere necessario eliminare i file che non si desidera con lo stesso nome e commit.)

    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
    

Seconda fase

  1. Fase di pulizia

    git reset --hard
    
  2. Fase di pulizia

    git gc --aggressive
    
  3. Fase di pulizia

    git prune
    

Potresti voler importare questi file nel repository B all'interno di una directory non root:

  1. Crea quella directory

    mkdir <base directory>             eg. mkdir FOLDER_TO_KEEP
    
  2. Sposta i file in quella directory

    git mv * <base directory>          eg. git mv * FOLDER_TO_KEEP
    
  3. Aggiungi file a quella directory

    git add .
    
  4. Effettua il commit delle modifiche e siamo pronti a unire questi file nel nuovo repository

    git commit
    

Terza fase

  1. Crea una copia del repository B se non ne hai già uno

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

    (supponendo che FOLDER_TO_KEEP sia il nome del nuovo repository in cui si sta copiando)

  2. cd in esso

    cd <git repository B directory>
    #  eg. cd /c/Working/GIT/FOLDER_TO_KEEP
    
  3. Creare una connessione remota al repository A come ramo nel repository 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. Estrai da questo ramo (contenente solo la directory che desideri spostare) nel repository B.

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

    Il pull copia sia i file che la cronologia. Nota: è possibile utilizzare un'unione anziché un pull, ma pull funziona meglio.

  5. Infine, probabilmente desideri ripulire un po 'rimuovendo la connessione remota al repository A

    git remote rm repo-A-branch
    
  6. Spingi e sei pronto.

    git push
    

Ho seguito la maggior parte dei passaggi descritti qui, tuttavia sembra copiare solo la cronologia di commit del file o della directory dal master (e non da altri rami). È giusto?
Bao-Long Nguyen-Trong,

Penso che sia giusto e che dovresti seguire passaggi simili per tutti i rami da cui vuoi spostare file o cartelle. passare alla filiale ad es. MyBranch nel repository A, filtro-ramo, ecc. Quindi "git pull repo-A-branch MyBranch" nel repository B.
mcarans

Grazie per la risposta. Sai se verranno migrati anche i tag sui rami?
Bao-Long Nguyen-Trong,

Temo di non saperlo, ma immagino che lo sarebbero.
mcarans,

1
@mcarans Purtroppo, questo NON è un modo affidabile, anche se sembra esserlo. Soffre dello stesso problema di tutte le altre soluzioni - Non mantiene la storia rinominata in passato. Nel mio caso, il primo commit è quando ho rinominato la directory / file. Tutto al di là di questo è perso.
xZero

20

Ho trovato questo molto utile. È un approccio molto semplice in cui si creano patch che vengono applicate al nuovo repository. Vedi la pagina collegata per maggiori dettagli.

Contiene solo tre passaggi (copiati dal blog):

# 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

L'unico problema che avevo era che non potevo applicare tutte le patch contemporaneamente usando

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

Sotto Windows ho ricevuto un errore InvalidArgument. Quindi ho dovuto applicare tutte le patch una dopo l'altra.


Non ha funzionato per me perché a un certo punto mancavano gli hashish. Questo mi ha aiutato: stackoverflow.com/questions/17371150/...
dr0i

A differenza dell'approccio "git log", questa opzione ha funzionato perfettamente per me! Grazie!
AlejandroVD,

1
Ho provato diversi approcci per spostare i progetti in nuovi repository. Questo è l'unico che ha funzionato per me. Non riesco a credere che un compito così comune debba essere così complicato.
Chris_D_Turk,

Grazie per aver condiviso il blog di Ross Hendrickson . Questo approccio ha funzionato per me.
Kaushik Acharya,

1
Questa è una soluzione molto elegante, tuttavia, ancora una volta, soffre dello stesso problema di tutte le altre soluzioni - NON manterrà la storia rinominata in passato.
xZero

6

MANTENERE IL NOME DELLA DIRECTORY

Il filtro sottodirectory (o il comando più breve git subtree) funziona bene ma non ha funzionato per me poiché rimuovono il nome della directory dalle informazioni di commit. Nel mio scenario, voglio solo unire parti di un repository in un altro e conservare la cronologia CON il nome completo del percorso.

La mia soluzione era usare il filtro ad albero e rimuovere semplicemente i file e le directory indesiderate da un clone temporaneo del repository di origine, quindi estrarre da quel clone nel mio repository di destinazione in 5 semplici passaggi.

# 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

Questo script non apporterà alcuna modifica al repository originale. Se il repository dest specificato nel file della mappa non esiste, questo script tenterà di crearlo.
Chetabahana,

1
Penso anche che mantenere intatti i nomi delle directory sia tremendamente importante. In caso contrario, si otterranno ulteriori commit di ridenominazione nel repository di destinazione.
ipuustin,


5

Questa risposta fornisce comandi interessanti basati git ame presentati usando esempi, passo dopo passo.

Obbiettivo

  • Si desidera spostare alcuni o tutti i file da un repository all'altro.
  • Vuoi mantenere la loro storia.
  • Ma non ti interessa conservare tag e rami.
  • Accetti una cronologia limitata per i file rinominati (e i file nelle directory rinominate).

Procedura

  1. Estrarre la cronologia in formato e-mail utilizzando
    git log --pretty=email -p --reverse --full-index --binary
  2. Riorganizza l'albero dei file e aggiorna la modifica del nome file nella cronologia [opzionale]
  3. Applica nuova cronologia utilizzando git am

1. Estrarre la cronologia in formato e-mail

Esempio: la storia di estratto file3, file4efile5

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

Pulisci la destinazione della directory temporanea

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

Pulisci la tua fonte repo

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

Estrai la cronologia di ogni file in formato 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"' {} ';'

Purtroppo l'opzione --followo --find-copies-hardernon può essere combinata con --reverse. Questo è il motivo per cui la cronologia viene tagliata quando il file viene rinominato (o quando viene rinominata una directory padre).

Dopo: Cronologia temporanea in formato e-mail

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

2. Riorganizza l'albero dei file e aggiorna la modifica del nome file nella cronologia [opzionale]

Supponiamo di voler spostare questi tre file in questo altro repository (può essere lo stesso repository).

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

Quindi riorganizza i tuoi file:

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

La tua cronologia temporanea è ora:

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

Cambia anche i nomi dei file nella cronologia:

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

Nota: questo riscrive la cronologia per riflettere il cambio di percorso e nome file.
      (ovvero la modifica della nuova posizione / nome all'interno del nuovo repository)


3. Applica nuova cronologia

L'altro repository è:

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

Applica commit da file cronologici temporanei:

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

L'altro repository è ora:

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

Utilizzare git statusper visualizzare la quantità di commit pronti per essere inviati :-)

Nota: poiché la cronologia è stata riscritta per riflettere il percorso e la modifica del nome file:
      (cioè rispetto alla posizione / nome all'interno del repository precedente)

  • Non è necessario git mvmodificare la posizione / il nome file.
  • Non è necessario git log --followaccedere alla cronologia completa.

Trucco aggiuntivo: rileva i file rinominati / spostati nel tuo repository

Per elencare i file che sono stati rinominati:

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

Altre personalizzazioni: è possibile completare il comando git logutilizzando le opzioni --find-copies-hardero --reverse. Puoi anche rimuovere le prime due colonne usando cut -f3-e grepping il modello completo '{. * =>. *}'.

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

3

Avendo avuto un simile prurito da graffiare (anche se solo per alcuni file di un determinato repository) questo script si è rivelato davvero utile: git-import

La versione breve è che crea file patch del file o della directory ( $object) forniti dal repository esistente:

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

che quindi vengono applicati a un nuovo repository:

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

Per i dettagli, consultare:


2

Prova questo

cd repo1

Ciò rimuoverà tutte le directory tranne quelle menzionate, preservando la cronologia solo per queste directory

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

Ora puoi aggiungere il tuo nuovo repository nel tuo telecomando Git e spingerlo a quello

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

aggiungi -fper sovrascrivere


ATTENZIONE: git-filter-branch ha un eccesso di gotcha che generano riscritture della storia alterate. Premi Ctrl-C prima di procedere con l'interruzione, quindi usa uno strumento di filtro alternativo come 'git filter-repo' ( github.com/newren/git-filter-repo ). Vedere la pagina del manuale di filtro-ramo per maggiori dettagli; per eliminare questo avviso, impostare FILTER_BRANCH_SQUELCH_WARNING = 1.
Colin,

1

Usando l'ispirazione da http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ , ho creato questa funzione Powershell per fare lo stesso, che ha finora ha funzionato benissimo per me:

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

Utilizzo per questo esempio:

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"

Dopo averlo fatto, puoi riorganizzare i file sul migrate-from-project2ramo prima di unirli.


1

Volevo qualcosa di robusto e riutilizzabile (funzione one-command-and-go + undo), quindi ho scritto il seguente script bash. Ha lavorato per me in diverse occasioni, quindi ho pensato di condividerlo qui.

È in grado di spostare una cartella arbitraria /path/to/fooda repo1in /some/other/folder/bara repo2(i percorsi delle cartelle possono essere uguali o diversi, la distanza dalla cartella principale può essere diversa).

Poiché va oltre i commit che toccano i file nella cartella di input (non tutti i commit del repository di origine), dovrebbe essere abbastanza veloce anche su repository di grandi dimensioni, se si estrae semplicemente una sottocartella profondamente nidificata che non è stata toccata in ogni commettere.

Dal momento che ciò che fa è creare un ramo orfano con tutta la cronologia del vecchio repository e quindi unirlo a HEAD, funzionerà anche in caso di conflitti tra i nomi di file (quindi, alla fine, dovresti risolvere un'unione) .

Se non ci sono scontri con i nomi di file, è sufficiente git commitalla fine per finalizzare l'unione.

Il rovescio della medaglia è che probabilmente non seguirà le ridenominazioni dei file (al di fuori della REWRITE_FROMcartella) nel repository di origine - pull richieste di benvenuto su GitHub per adattarle.

Collegamento 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

Nel mio caso, non avevo bisogno di conservare il repository da cui stavo migrando o di conservare alcuna cronologia precedente. Ho avuto una patch dello stesso ramo, da un telecomando diverso

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

In quei due passaggi, sono stato in grado di far apparire il ramo dell'altro repository nello stesso repository.

Infine, ho impostato questo ramo (che ho importato dall'altro repository) per seguire la linea principale del repository di destinazione (in modo da poterli differire con precisione)

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

Ora si comportava come se fosse solo un altro ramo che avevo spinto contro lo stesso repository.


0

Se i percorsi per i file in questione sono gli stessi nei due repository e si desidera trasferire solo un file o un piccolo set di file correlati, un modo semplice per farlo è utilizzare git cherry-pick.

Il primo passo è portare i commit dall'altro repository nel proprio repository locale usando git fetch <remote-url>. Ciò lascerà il FETCH_HEADpuntamento al commit principale dall'altro repository; se vuoi conservare un riferimento a quel commit dopo aver fatto altri recuperi, potresti voler taggarlo git tag other-head FETCH_HEAD.

Sarà quindi necessario creare un commit iniziale per quel file (se non esiste) o un commit per portare il file in uno stato che può essere patchato con il primo commit dall'altro repository che si desidera portare. essere in grado di farlo con un git cherry-pick <commit-0>se commit-0introdotto i file desiderati, oppure potrebbe essere necessario costruire il commit "a mano". Aggiungi -nalle opzioni cherry-pick se devi modificare il commit iniziale, ad esempio, eliminare i file da quel commit che non vuoi portare.

Dopodiché, puoi continuare con git cherry-picki commit successivi, usando nuovamente -ndove necessario. Nel caso più semplice (tutti i commit sono esattamente ciò che si vuole e si applica in modo pulito) si può dare la lista completa di commit sulla riga di comando cherry-pick: git cherry-pick <commit-1> <commit-2> <commit-3> ....


0

Questo diventa più semplice usando git-filter-repo.

Per passare project2/sub/dira 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

Per installare semplicemente lo strumento: pip3 install git-filter-repo ( maggiori dettagli e opzioni in README )

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

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

-2

Il metodo seguente per migrare il mio GIT Stash su GitLab mantenendo tutti i rami e preservando la cronologia.

Clonare il vecchio repository su local.

git clone --bare <STASH-URL>

Crea un repository vuoto in GitLab.

git push --mirror <GitLab-URL>

Quanto sopra ho eseguito quando abbiamo migrato il nostro codice da stash a GitLab e ha funzionato molto bene.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.