Como mover arquivos de um repositório Git para outro (não um clone), preservando o histórico

484

Nossos repositórios Git começaram como partes de um único repositório SVN monstro, onde os projetos individuais tinham sua própria árvore da seguinte maneira:

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

Obviamente, era muito fácil mover arquivos de um para outro svn mv. Mas no Git, cada projeto está em seu próprio repositório, e hoje me pediram para mover um subdiretório de project2para project1. Eu fiz algo parecido com isto:

$ 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

Mas isso parece bastante complicado. Existe uma maneira melhor de fazer esse tipo de coisa em geral? Ou adotei a abordagem correta?

Observe que isso envolve mesclar o histórico em um repositório existente, em vez de simplesmente criar um novo repositório independente a partir de parte de outro ( como em uma pergunta anterior ).

ebneter
fonte
1
Isso soa como uma abordagem razoável para mim; Não consigo pensar em nenhuma maneira óbvia de melhorar significativamente seu método. É bom que o Git realmente facilite isso (eu não gostaria de tentar mover um diretório de arquivos entre repositórios diferentes no Subversion, por exemplo).
Greg Hewgill 02/09/09
1
@ bneter - Eu fiz isso (movi o histórico de um repositório svn para outro) manualmente, usando scripts de shell. Basicamente, reproduzi o histórico (diffs, confirme as mensagens dos logs) de determinados arquivos / diretórios em um segundo repositório.
Adam Monsen
1
Eu me pergunto por que você não faz, em git fetch p2 && git merge p2vez de git fetch p2 && git branch .. && git merge p2? Edit: tudo bem, parece que você deseja obter as alterações em um novo ramo chamado p2, não no ramo atual.
precisa saber é o seguinte
1
Não há como impedir que --filter-branch destrua a estrutura de diretórios? Essa etapa do "git mv" resulta em um commit maciço, cheio de exclusões e criações de arquivos.
Edward Falk
1
Observe que, a partir do git 2.9, mesclar históricos não relacionados é desabilitado por padrão. Para fazê-lo funcionar, adicione --allow-unrelated-historieso último git mergepara fazê-lo funcionar.
Scott Berrevoets

Respostas:

55

Sim, bater no --subdirectory-filterde filter-branchera a chave. O fato de você usá-lo essencialmente prova que não há uma maneira mais fácil - você não teve escolha a não ser reescrever o histórico, pois queria terminar com apenas um subconjunto (renomeado) dos arquivos, e isso por definição altera os hashes. Como nenhum dos comandos padrão (por exemplo pull) reescreve o histórico, não há como usá-los para fazer isso.

Você pode refinar os detalhes, é claro - algumas clonagens e ramificações não eram estritamente necessárias - mas a abordagem geral é boa! É uma pena que seja complicado, mas é claro que o objetivo do git não é facilitar a reescrita da história.

Cascabel
fonte
1
e se o seu arquivo passou por vários diretórios e agora reside em um - o filtro de subdiretório ainda funcionará? (ou seja, eu estou supondo que se eu quiser mover um arquivo, eu posso movê-lo para seu próprio subdiretório e este trabalho?)
rogerdpack
1
@rogerdpack: Não, isso não seguirá o arquivo através de renomeação. Eu acredito que parecerá ter sido criado no ponto em que foi movido para o subdiretório selecionado. Se você deseja selecionar apenas um arquivo, dê uma olhada --index-filterna página de filter-branchmanual.
Cascabel
8
Existe alguma receita sobre como eu posso seguir os renomeados?
Night Warrier
Eu acho que manter e curar a história é um dos principais pontos do git.
artburkart
288

Se seu histórico estiver correto, você pode retirar os commits como patches e aplicá-los no novo repositório:

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 

Ou em uma linha

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)

(Extraído dos documentos de Exherbo )

Smar
fonte
21
Para os três ou quatro arquivos que eu precisava mover, essa era uma solução muito mais simples que a resposta aceita. Acabei aparando os caminhos no arquivo de correção com find-replace para ajustá-lo à estrutura de diretórios do meu novo repositório.
Rian Sanderson
8
Eu adicionei opções para que os arquivos binários (como imagens) também são migrados corretamente: git log --pretty=email --patch-with-stat --full-index --binary --reverse -- client > patch. Funciona sem problemas AFAICT.
Emmanuel Touzery
35
Na etapa de aplicação, usei a --committer-date-is-author-dateopção para preservar a data de confirmação original em vez da data em que os arquivos foram movidos.
Darrenmc # 29/14
6
os commits de mesclagem no histórico quebram o comando "am". Você pode adicionar "-m --first-parent" ao comando git log acima, e funcionou para mim.
Gábor Lipták
6
@ Daniel Golden Consegui corrigir o problema com os arquivos que foram movidos (o que é conseqüência de um bug git log, para que ele não funcione com os dois --followe --reversecorretamente). Eu usei esta resposta , e aqui é um script completo que eu uso agora aos arquivos de movimento
tsayen
75

Tendo tentado várias abordagens para mover um arquivo ou pasta de um repositório Git para outro, o único que parece funcionar de maneira confiável é descrito abaixo.

Envolve clonar o repositório do qual você deseja mover o arquivo ou pasta, movê-lo para a raiz, reescrever o histórico do Git, clonar o repositório de destino e puxar o arquivo ou pasta com o histórico diretamente para esse repositório de destino.

Estágio um

  1. Faça uma cópia do repositório A, pois as etapas a seguir fazem grandes alterações nessa cópia que você não deve enviar!

    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 nele

    cd <git repository A directory>
    #  eg. cd /c/Working/GIT/myprojects
    
  3. Exclua o link do repositório original para evitar alterações acidentais remotas (por exemplo, pressionando)

    git remote rm origin
    
  4. Percorra seu histórico e arquivos, removendo qualquer coisa que não esteja no diretório 1. O resultado é o conteúdo do diretório 1 espalhado na base do repositório A.

    git filter-branch --subdirectory-filter <directory> -- --all
    # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all
    
  5. Apenas para mover um arquivo: percorra o que resta e remova tudo, exceto o arquivo desejado. (Pode ser necessário excluir arquivos que você não deseja com o mesmo nome e confirmar.)

    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
    

Estágio Dois

  1. Etapa de limpeza

    git reset --hard
    
  2. Etapa de limpeza

    git gc --aggressive
    
  3. Etapa de limpeza

    git prune
    

Você pode importar esses arquivos para o repositório B em um diretório que não seja a raiz:

  1. Crie esse diretório

    mkdir <base directory>             eg. mkdir FOLDER_TO_KEEP
    
  2. Mover arquivos para esse diretório

    git mv * <base directory>          eg. git mv * FOLDER_TO_KEEP
    
  3. Adicionar arquivos a esse diretório

    git add .
    
  4. Confirme suas alterações e estamos prontos para mesclar esses arquivos no novo repositório

    git commit
    

Estágio Três

  1. Faça uma cópia do repositório B se você ainda não tiver um

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

    (assumindo que FOLDER_TO_KEEP é o nome do novo repositório para o qual você está copiando)

  2. cd nele

    cd <git repository B directory>
    #  eg. cd /c/Working/GIT/FOLDER_TO_KEEP
    
  3. Crie uma conexão remota para o repositório A como uma ramificação no repositório 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. Puxe desta ramificação (contendo apenas o diretório que você deseja mover) para o repositório B.

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

    O pull copia os arquivos e o histórico. Nota: Você pode usar uma mesclagem em vez de um pull, mas o pull funciona melhor.

  5. Por fim, você provavelmente deseja limpar um pouco removendo a conexão remota ao repositório A

    git remote rm repo-A-branch
    
  6. Empurre e está tudo pronto.

    git push
    
mcarans
fonte
Eu já passei pela maioria das etapas descritas aqui, mas parece copiar apenas o histórico de confirmação do arquivo ou dir do mestre (e não de outros ramos). Isso está certo?
Bao-Long Nguyen-Trong
Eu acho que é certo e que você teria que passar por etapas semelhantes para todos os ramos a partir dos quais você deseja mover arquivos ou pastas, ou seja. mudar para o ramo, por exemplo. MyBranch em um repositório, filter-branch etc. Você, então, "git pull repo-A-ramo MyBranch" em B. repositório
mcarans
Obrigado pela resposta. Você sabe se as tags nas ramificações também serão migradas?
Bao-Long Nguyen-Trong
Receio não saber, mas acho que sim.
Mcarans
1
@ mcarans Infelizmente, essa não é uma maneira confiável, embora pareça ser. Ele sofre do mesmo problema que todas as outras soluções - não retém a renomeação do histórico. No meu caso, a primeira confirmação foi quando eu renomeei o diretório / arquivo. Tudo além disso está perdido.
XZero
20

Achei isso muito útil. É uma abordagem muito simples em que você cria patches que são aplicados ao novo repositório. Veja a página vinculada para mais detalhes.

Ele contém apenas três etapas (copiadas do 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

O único problema que tive foi que não era possível aplicar todos os patches de uma só vez usando

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

No Windows, recebi um erro InvalidArgument. Então eu tive que aplicar todos os patches um após o outro.

anhoppe
fonte
Não funcionou para mim, pois em algum momento os sha-hashes estavam faltando. Isso me ajudou: stackoverflow.com/questions/17371150/…
dr0i
Diferente da abordagem "git log", essa opção funcionou perfeitamente para mim! obrigado!
AlejandroVD
1
Tentei abordagens diferentes para mover projetos para um novo repositório. Este é o único que funcionou para mim. Não posso acreditar que uma tarefa tão comum deva ser tão complicada.
2141717
Obrigado por compartilhar o blog de Ross Hendrickson . Essa abordagem funcionou para mim.
Kaushik Acharya
1
Essa é uma solução muito elegante; no entanto, novamente, ela sofre do mesmo problema que todas as outras soluções - NÃO reterá a renomeação do histórico.
XZero
6

MANTER O NOME DO DIRETÓRIO

O filtro de subdiretório (ou o comando mais curto subárvore git) funciona bem, mas não funcionou para mim, pois remove o nome do diretório das informações de confirmação. No meu cenário, só quero mesclar partes de um repositório em outro e manter o histórico com o nome do caminho completo.

Minha solução foi usar o filtro em árvore e simplesmente remover os arquivos e diretórios indesejados de um clone temporário do repositório de origem e puxá-lo para o meu repositório de destino em 5 etapas simples.

# 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
fonte
Este script não fará nenhuma modificação no seu repositório original. Se o repositório de destino especificado no arquivo de mapa não existir, esse script tentará criá-lo.
Chetabahana
1
Eu também acho que manter intactos os nomes dos diretórios é extremamente importante. Caso contrário, você obterá confirmações de renomeação extras no repositório de destino.
Ipuustin
6

O que eu sempre uso é aqui http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ . Simples e rápido.

Para conformidade com os padrões stackoverflow, aqui está o procedimento:

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
fonte
5

Esta resposta fornece comandos interessantes baseados git ame apresentados usando exemplos, passo a passo.

Objetivo

  • Você deseja mover alguns ou todos os arquivos de um repositório para outro.
  • Você quer manter a história deles.
  • Mas você não se preocupa em manter tags e ramificações.
  • Você aceita histórico limitado para arquivos renomeados (e arquivos em diretórios renomeados).

Procedimento

  1. Extrair histórico no formato de email usando
    git log --pretty=email -p --reverse --full-index --binary
  2. Reorganize a árvore de arquivos e atualize a alteração do nome do arquivo no histórico [opcional]
  3. Aplique novo histórico usando git am

1. Extrair histórico no formato de email

Exemplo: história Extrato de file3, file4efile5

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

Limpe o destino do diretório temporário

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

Limpe sua fonte de 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

Extrair histórico de cada arquivo no formato de email

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

Infelizmente opção --followou --find-copies-hardernão pode ser combinada com --reverse. É por isso que o histórico é cortado quando o arquivo é renomeado (ou quando um diretório pai é renomeado).

Depois: histórico temporário no formato de email

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

2. Reorganize a árvore de arquivos e atualize a alteração do nome do arquivo no histórico [opcional]

Suponha que você queira mover esses três arquivos neste outro repositório (pode ser o mesmo repositório).

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

Portanto, reorganize seus arquivos:

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

Seu histórico temporário é agora:

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

Altere também nomes de arquivos no histórico:

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

Nota: Isso reescreve o histórico para refletir a alteração de caminho e nome do arquivo.
      (ou seja, a alteração do novo local / nome no novo repositório)


3. Aplique novo histórico

Seu outro repo é:

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

Aplicar confirmações de arquivos de histórico temporários:

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

Seu outro repo é agora:

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

Use git statuspara ver a quantidade de confirmações prontas para serem enviadas :-)

Nota: Como o histórico foi reescrito para refletir a alteração do caminho e do nome do arquivo:
      (por exemplo, comparado ao local / nome no repositório anterior)

  • Não há necessidade de git mvalterar o local / nome do arquivo.
  • Não há necessidade de git log --followacessar o histórico completo.

Truque extra: detecte arquivos renomeados / movidos em seu repositório

Para listar os arquivos que foram renomeados:

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

Mais personalizações: você pode concluir o comando git logusando as opções --find-copies-harderou --reverse. Você também pode remover as duas primeiras colunas usando cut -f3-e grepping o padrão completo '{. * =>. *}'.

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

Tendo tido uma coceira semelhante ao zero (embora apenas para alguns arquivos de um determinado repositório), este script provou ser realmente útil: git-import

A versão curta é que ele cria arquivos de correção do arquivo ou diretório ( $object) fornecido a partir do repositório existente:

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

que são aplicadas a um novo repositório:

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

Para mais detalhes, consulte:

ViToni
fonte
2

Tente isto

cd repo1

Isso removerá todos os diretórios, exceto os mencionados, preservando o histórico apenas para esses diretórios

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

Agora você pode adicionar seu novo repositório ao seu git remote e enviá-lo para esse

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

adicionar -fpara substituir

Chetan Basutkar
fonte
AVISO: O git-filter-branch possui muitas dicas que geram reescritas de histórico mutiladas. Pressione Ctrl-C antes de prosseguir para abortar e, em vez disso, use uma ferramenta de filtragem alternativa, como 'git filter-repo' ( github.com/newren/git-filter-repo ). Consulte a página de manual da ramificação do filtro para obter mais detalhes; para reprimir esse aviso, defina FILTER_BRANCH_SQUELCH_WARNING = 1.
Colin
1

Usando a inspiração de http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ , criei essa função do Powershell para fazer o mesmo, que tem funcionou muito bem para mim até agora:

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

Uso para este exemplo:

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"

Depois de fazer isso, você pode reorganizar os arquivos na migrate-from-project2ramificação antes de mesclá-los.

crimbo
fonte
1

Eu queria algo robusto e reutilizável (função de comando-e-ir + desfazer), então escrevi o seguinte script bash. Trabalhou para mim em várias ocasiões, então pensei em compartilhar aqui.

É capaz de mover uma pasta arbitrária /path/to/foode repo1para /some/other/folder/barpara repo2(os caminhos da pasta podem ser iguais ou diferentes, a distância da pasta raiz pode ser diferente).

Como ele só repassa as confirmações que tocam os arquivos na pasta de entrada (nem todas as confirmações do repositório de origem), deve ser bastante rápido, mesmo nos repositórios de fontes grandes, se você extrair uma subpasta profundamente aninhada que não foi tocada em todas as confirmar.

Como o que isso faz é criar uma ramificação órfã com todo o histórico do repositório antigo e depois mesclá-lo com o HEAD, ele funcionará mesmo em caso de conflito de nome de arquivo (você precisará resolver uma mesclagem no final do curso) .

Se não houver conflitos de nome de arquivo, basta git commitfinalizar a mesclagem.

A desvantagem é que ele provavelmente não seguirá as renomeações de arquivos (fora da REWRITE_FROMpasta) nas solicitações de repo-pull de origem bem-vindas no GitHub para acomodar isso.

Link do 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
fonte
0

No meu caso, não precisei preservar o repositório do qual estava migrando ou preservar qualquer histórico anterior. Eu tinha um patch do mesmo ramo, de um controle remoto diferente

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

Nessas duas etapas, consegui que a ramificação do outro repositório aparecesse no mesmo repositório.

Por fim, configurei esse ramo (importado do outro repositório) para seguir a linha principal do repositório de destino (para que eu os diferencie com precisão)

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

Agora ele se comportava como se fosse apenas mais um ramo que eu havia pressionado contra o mesmo repositório.

Jason D
fonte
0

Se os caminhos para os arquivos em questão forem os mesmos nos dois repositórios e você desejar trazer apenas um arquivo ou um pequeno conjunto de arquivos relacionados, uma maneira fácil de fazer isso é usar git cherry-pick.

O primeiro passo é trazer os commits do outro repositório para o seu próprio repositório local usando git fetch <remote-url>. Isso deixará FETCH_HEADapontando para o commit principal do outro repositório; se você quiser preservar uma referência a esse commit depois de fazer outras buscas, convém marcá-lo git tag other-head FETCH_HEAD.

Você precisará criar um commit inicial para esse arquivo (se ele não existir) ou um commit para trazer o arquivo para um estado que possa ser corrigido com o primeiro commit do outro repositório que você deseja trazer. Você pode poderá fazer isso com a git cherry-pick <commit-0>se commit-0os arquivos desejados forem apresentados ou poderá ser necessário construir o commit 'manualmente'. Adicione -nàs opções de seleção de cereja se precisar modificar o commit inicial para, por exemplo, soltar arquivos desse commit que você não deseja incluir.

Depois disso, você pode continuar com git cherry-pickas confirmações subsequentes, usando novamente sempre -nque necessário. No caso mais simples (todos os commits são exatamente o que você quer e aplicar de forma limpa) você pode dar a lista completa de commits na linha de comando cherry-pick: git cherry-pick <commit-1> <commit-2> <commit-3> ....

cjs
fonte
0

Isso se torna mais simples usando o git-filter-repo.

Para passar project2/sub/dirpara 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

Para instalar a ferramenta simplesmente: pip3 install git-filter-repo ( mais detalhes e opções no README )

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

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

O método abaixo para migrar meu GIT Stash para o GitLab, mantendo todas as ramificações e preservando o histórico.

Clone o repositório antigo para local.

git clone --bare <STASH-URL>

Crie um repositório vazio no GitLab.

git push --mirror <GitLab-URL>

O que fiz acima quando migramos nosso código do stash para o GitLab e funcionou muito bem.

Roopkumar Akubathini
fonte