-
1. Primeiros Passos
- 1.1 Sobre Controle de Versão
- 1.2 Uma Breve História do Git
- 1.3 O que é Git?
- 1.4 A Linha de Comando
- 1.5 Instalando o Git
- 1.6 Configuração inicial do Git
- 1.7 Obtendo Ajuda
- 1.8 Resumo
-
2. Fundamentos do Git
-
3. Ramificação (Branching) no Git
-
4. Git no Servidor
- 4.1 Os Protocolos
- 4.2 Colocando o Git em um Servidor
- 4.3 Gerando a sua Chave Pública SSH
- 4.4 Configurando o Servidor
- 4.5 Git Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Opções de Hospedagem de Terceiros
- 4.10 Resumo
-
5. Git Distribuído
-
6. GitHub
-
7. Ferramentas do Git
- 7.1 Seleção de Revisão
- 7.2 Área de Stage Interativa
- 7.3 Fazendo Stash e Limpando
- 7.4 Assinando Seu Trabalho
- 7.5 Pesquisando (Searching)
- 7.6 Reescrevendo o Histórico
- 7.7 Reset Desmistificado
- 7.8 Merging Avançado
- 7.9 Rerere
- 7.10 Depurando com Git
- 7.11 Submódulos
- 7.12 Empacotando (Bundling)
- 7.13 Substituir (Replace)
- 7.14 Armazenamento de Credenciais
- 7.15 Resumo
-
8. Customizando o Git
- 8.1 Configuração do Git
- 8.2 Atributos do Git
- 8.3 Hooks do Git
- 8.4 Uma Política de Exemplo Imposta pelo Git
- 8.5 Resumo
-
9. Git e Outros Sistemas
- 9.1 Git como Cliente
- 9.2 Migrando para o Git
- 9.3 Resumo
-
10. Git Internals (Por Dentro do Git)
- 10.1 Encanamento e Porcelana (Plumbing and Porcelain)
- 10.2 Objetos do Git
- 10.3 Referências do Git
- 10.4 Arquivos de pacote (Packfiles)
- 10.5 Refspec
- 10.6 Protocolos de Transferência
- 10.7 Manutenção e Recuperação de Dados
- 10.8 Variáveis de Ambiente
- 10.9 Resumo
-
A1. Appendix A: O Git em Outros Ambientes
- A1.1 Interfaces Gráficas
- A1.2 Git no Visual Studio
- A1.3 Git no Visual Studio Code
- A1.4 Git no IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.5 Git no Sublime Text
- A1.6 Git no Bash
- A1.7 Git no Zsh
- A1.8 Git no PowerShell
- A1.9 Resumo
-
A2. Appendix B: Incorporando o Git nos seus Aplicativos
- A2.1 Linha de Comando do Git
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Appendix C: Comandos do Git
- A3.1 Configuração e Ajustes
- A3.2 Obtendo e Criando Projetos
- A3.3 Snapshots Básicos
- A3.4 Branches e Merges
- A3.5 Compartilhando e Atualizando Projetos
- A3.6 Inspeção e Comparação
- A3.7 Depuração
- A3.8 Patches
- A3.9 E-mail
- A3.10 Sistemas Externos
- A3.11 Administração
- A3.12 Comandos de Encanamento
9.2 Git e Outros Sistemas - Migrando para o Git
Migrando para o Git
Se você possui uma base de código existente em outro VCS, mas decidiu começar a usar o Git, deve migrar seu projeto de uma maneira ou de outra. Esta seção aborda alguns importadores para sistemas comuns e demonstra como desenvolver seu próprio importador personalizado. Você aprenderá como importar dados de vários dos maiores sistemas SCM usados profissionalmente, porque eles compõem a maioria dos usuários que estão mudando e porque é fácil encontrar ferramentas de alta qualidade para eles.
Subversion
Se você leu a seção anterior sobre o uso do git svn, poderá facilmente usar essas instruções para fazer o git svn clone de um repositório; em seguida, pare de usar o servidor Subversion, dê um push para um novo servidor Git e comece a usá-lo.
Se você quiser o histórico, você pode fazer isso o mais rápido que puder extraindo os dados do servidor Subversion (o que pode demorar um pouco).
No entanto, a importação não é perfeita; e como demorará muito, você pode fazer isso da maneira correta.
O primeiro problema é a informação do autor.
No Subversion, cada pessoa submetida tem um usuário no sistema que é gravado nas informações de commit.
Os exemplos na seção anterior mostram schacon em alguns lugares, como a saída blame e o git svn log.
Se você deseja mapear isso para obter melhores dados de autor do Git, é necessário um mapeamento dos usuários do Subversion para os autores do Git.
Crie um arquivo chamado users.txt que possua esse mapeamento em um formato como este:
schacon = Scott Chacon <schacon@geemail.com>
selse = Someo Nelse <selse@geemail.com>
Para obter uma lista dos nomes de autores que o SVN usa, você pode executar o seguinte:
$ svn log --xml --quiet | grep author | sort -u | \
perl -pe 's/.*>(.*?)<.*/$1 = /'
Isso gera a saída do log no formato XML, mantém apenas as linhas com informações do autor, descarta duplicatas e retira as tags XML.
Obviamente, isso só funciona em uma máquina com o grep, sort e perl instalados.
Em seguida, redirecione a saída para o arquivo users.txt, para que você possa adicionar os dados equivalentes do usuário do Git ao lado de cada entrada.
|
Note
|
Se você está tentando fazer isso em uma máquina Windows, este é o ponto em que terá problemas. A Microsoft forneceu bons conselhos e exemplos em https://learn.microsoft.com/en-us/azure/devops/repos/git/perform-migration-from-svn-to-git. |
Você pode fornecer esse arquivo ao git svn para ajudá-lo a mapear os dados do autor com mais precisão.
Você também pode informar o git svn a não incluir os metadados que o Subversion importa normalmente, passando o --no-metadata para o comando clone ou init.
Os metadados incluem um git-svn-id dentro de cada mensagem de commit que o Git irá gerar durante a importação.
Isso pode inchar o seu log do Git e torná-lo um pouco incerto.
|
Note
|
Você precisa manter os metadados quando quiser espelhar commits feitos no repositório do Git de volta no repositório original do SVN.
Se você não deseja que a sincronização em seu log de commit ocorra, fique à vontade para omitir o parâmetro |
Isso faz o seu comando de import parecer com isto:
$ git svn clone http://my-project.googlecode.com/svn/ \
--authors-file=users.txt --no-metadata --prefix "" -s my_project
$ cd my_project
Agora você deve ter uma importação (import) do Subversion melhor em seu diretório my_project.
Em vez dos commits que se parecem com isto:
commit 37efa680e8473b615de980fa935944215428a35a
Author: schacon <schacon@4c93b258-373f-11de-be05-5f7a86268029>
Date: Sun May 3 00:12:22 2009 +0000
fixed install - go to trunk
git-svn-id: https://my-project.googlecode.com/svn/trunk@94 4c93b258-373f-11de-
be05-5f7a86268029
eles se parecem assim:
commit 03a8785f44c8ea5cdb0e8834b7c8e6c469be2ff2
Author: Scott Chacon <schacon@geemail.com>
Date: Sun May 3 00:12:22 2009 +0000
fixed install - go to trunk
Não apenas o campo Autor (Author) parece muito melhor, mas o git-svn-id não está mais lá também.
Você também deve fazer um pouco de limpeza pós-importação (post-import cleanup).
Por um lado, você deve limpar as referências estranhas que o git svn configurou.
Primeiro, você moverá as tags para que sejam de fato tags, em vez de ramificações (branches) remotas estranhas e, em seguida, moverá o restante das filiais para que sejam locais.
Para mover as tags para se tornarem em tags adequadas do Git, execute:
$ for t in $(git for-each-ref --format='%(refname:short)' refs/remotes/tags); do git tag ${t/tags\//} $t && git branch -D -r $t; done
Isso utiliza as referências (refs) que eram filiais (branches) remotas que começavam com refs/remotes/tags/ e as torna verdadeiras tags (mais leves).
Em seguida, mova o restante das referências sob o refs/remotes para que se tornem em ramificações (branches) locais:
$ for b in $(git for-each-ref --format='%(refname:short)' refs/remotes); do git branch $b refs/remotes/$b && git branch -D -r $b; done
Pode acontecer que você veja alguns branches extras com o sufixo @xxx (onde xxx é um número), enquanto no Subversion você vê apenas uma filial.
Na verdade, trata-se de um recurso do Subversion chamado “peg-revisions” (revisões vinculadas), que é algo pelo qual o Git simplesmente não possui contrapartida sintática.
Por isso, o git svn simplesmente adiciona o número da versão SVN ao nome da branch, da mesma forma que você o escreveria no SVN para abordar a peg-revision daquele branch.
Se você não se importa mais com as peg-revisions, simplesmente as remova:
$ for p in $(git for-each-ref --format='%(refname:short)' | grep @); do git branch -D $p; done
Agora, todos os branches antigos são realmente branches do Git e todas as tags antigas são as do Git.
Há uma última coisa para limpar.
Infelizmente, o git svn cria um branch extra chamado trunk, no qual mapeia para a branch padrão do Subversion, mas o trunk aponta ao mesmo lugar que o master.
Desde que o master é mais idomaticamente Git, aqui está o modo de como se remover o branch a mais:
$ git branch -d trunk
A última coisa a fazer é adicionar o seu novo servidor Git como um arquivo remoto e fazer um push a ele. Aqui está um exemplo sobre como adicionar seu servidor como um controle remoto:
$ git remote add origin git@my-git-server:myrepository.git
Como você deseja que todas os seus branches e tags se levantem, agora você pode executar isso:
$ git push origin --all
$ git push origin --tags
Todos as suas filiais (branches) e tags devem estar no seu novo servidor Git em uma importação limpa e agradável.
Mercurial
Uma vez que o Mercurial e o Git têm modelos bastante parecidos para a representação de versões, e visto que o Git é um pouco mais flexível, converter de um repositório no Mercurial para o Git é bem simples, utilizando-se a ferramenta chamada "hg-fast-export", da qual você precisará uma cópia:
$ git clone https://github.com/frej/fast-export.git
O primeiro passo para a conversão é efetuar um clone por completo do repositório no Mercurial a que se queira converter:
$ hg clone <remote repo URL> /tmp/hg-repo
O próximo passo é a criação de um arquivo para o mapeamento dos autores.
O Mercurial é mais tolerante do que o Git em virtude do que colocará no campo dos autores em relação aos conjuntos de alterações (changesets); sendo assim, essa é a hora de limpar a casa.
A geração dele é na base de um comando de linha única no shell do bash:
$ cd /tmp/hg-repo
$ hg log | grep user: | sort | uniq | sed 's/user: *//' > ../authors
Isto deve levar alguns segundos, a depender em quão longo seja a história do seu projeto, e logo após, o arquivo /tmp/authors se parecerá com algo do tipo:
bob
bob@localhost
bob <bob@company.com>
bob jones <bob <AT> company <DOT> com>
Bob Jones <bob@company.com>
Joe Smith <joe@company.com>
Nesse exemplo, a mesma pessoa (Bob) criou os changesets com quatro nomes diferentes, com o qual somente um deles pareça correto, e onde um deles seja completamente inválido por um commit no Git.
A hg-fast-export nos permite consertar a fazer com que cada linha se torne uma regra: "<input>"="<output>", ao mapear um <input> em favor de um <output>.
Dentro das sequências de strings (strings) <input> e <output>, todas as saídas das sequências codificadas do string_escape no Python são suportadas.
Se o arquivo de mapeamento do autor não conter uma correspondência com o <input>, o mesmo autor será enviado ao Git sem sofrer as alterações.
Se todos os nomes dos usuários parecem bem, não será necessário a sua presença num arquivo para eles em momento nenhum.
No exemplo, nós queremos que o nosso arquivo pareça assim:
"bob"="Bob Jones <bob@company.com>"
"bob@localhost"="Bob Jones <bob@company.com>"
"bob <bob@company.com>"="Bob Jones <bob@company.com>"
"bob jones <bob <AT> company <DOT> com>"="Bob Jones <bob@company.com>"
O mesmo tipo do arquivo de mapeamento pode ser usado na renomeação dos branches e das tags quando o nome no Mercurial não for permitido pelo Git.
O próximo passo é criar o nosso novo repositório no Git e rodar o script de exportação:
$ git init /tmp/converted
$ cd /tmp/converted
$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors
A bandeira (flag) -r diz ao hg-fast-export onde encontrar o repositório do Mercurial que desejamos converter, e a bandeira -A nos diz onde encontrar o arquivo para os mapeamentos de autoria (os arquivos de mapeamentos das branches e das tags são especificados com as flags -B e -T, respectivamente).
O script faz a análise sintática (parses) aos changesets do Mercurial e os converte em um script voltado para o recurso do Git, "fast-import" (do qual discutiremos detalhadamente mais à frente).
Isto toma um pouco do seu tempo (apesar de ser muito mais rápido do que numa base em rede (network)), e a saída (output) costuma ser muito prolixa:
$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors
Loaded 4 authors
master: Exporting full revision 1/22208 with 13/0/0 added/changed/removed files
master: Exporting simple delta revision 2/22208 with 1/1/0 added/changed/removed files
master: Exporting simple delta revision 3/22208 with 0/1/0 added/changed/removed files
[…]
master: Exporting simple delta revision 22206/22208 with 0/4/0 added/changed/removed files
master: Exporting simple delta revision 22207/22208 with 0/2/0 added/changed/removed files
master: Exporting thorough delta revision 22208/22208 with 3/213/0 added/changed/removed files
Exporting tag [0.4c] at [hg r9] [git :10]
Exporting tag [0.4d] at [hg r16] [git :17]
[…]
Exporting tag [3.1-rc] at [hg r21926] [git :21927]
Exporting tag [3.1] at [hg r21973] [git :21974]
Issued 22315 commands
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects: 120000
Total objects: 115032 ( 208171 duplicates )
blobs : 40504 ( 205320 duplicates 26117 deltas of 39602 attempts)
trees : 52320 ( 2851 duplicates 47467 deltas of 47599 attempts)
commits: 22208 ( 0 duplicates 0 deltas of 0 attempts)
tags : 0 ( 0 duplicates 0 deltas of 0 attempts)
Total branches: 109 ( 2 loads )
marks: 1048576 ( 22208 unique )
atoms: 1952
Memory total: 7860 KiB
pools: 2235 KiB
objects: 5625 KiB
---------------------------------------------------------------------
pack_report: getpagesize() = 4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit = 8589934592
pack_report: pack_used_ctr = 90430
pack_report: pack_mmap_calls = 46771
pack_report: pack_open_windows = 1 / 1
pack_report: pack_mapped = 340852700 / 340852700
---------------------------------------------------------------------
$ git shortlog -sn
369 Bob Jones
365 Joe Smith
É quase tudo o que há nisto. Todas as tags do Mercurial foram convertidas para as tags do Git, e as filiais (branches) e os favoritos (bookmarks) do Mercurial foram convertidos para as branches do Git. Agora você se encontra pronto para subir o seu repositório num push no lado dos servidores da sua casa (server-side home):
$ git remote add origin git@my-git-server:myrepository.git
$ git push origin --all
Perforce
O próximo sistema ao qual você abordará para importações é o Perforce. Conforme o que foi discutido anteriormente, existem duas maneiras das quais se possa fazer com que o Git e o Perforce conversem um com o outro: git-p4 e o Perforce Git Fusion.
Perforce Git Fusion
O Git Fusion torna esse processo quase indolor. Apenas configure as propriedades, mapeamento dos usuários, e os branches do seu projeto utilizando-se de um arquivo de configuração (conforme já foi discutido em Git Fusion), e efetue o clone do repositório. O Git Fusion fará com que pareça que você tem um repositório nativo do Git, do qual então, já se encontra pronto a ser empurrado (push) rumo ao seu host (hospedeiro) nativo no Git, se você assim quiser. Você poderá inclusive, fazer com que o Perforce se torne o seu host (hospedeiro) no Git se desejar.
Git-p4
O Git-p4 também pode atuar como uma ferramenta à importação. Como exemplo disto, iremos importar o projeto Jam a partir de um Depósito Público do Perforce (Perforce Public Depot). A fim de configurar o seu cliente, você deverá exportar a variável do ambiente P4PORT à apontar no depósito do Perforce:
$ export P4PORT=public.perforce.com:1666
|
Note
|
Para que se possa prosseguir, você necessitará de um depósito Perforce para se conectar com ele. Usaremos o depósito público no site public.perforce.com como nosso exemplo, mas você também pode usar qualquer um ao qual você possua acesso. |
Rode o comando git p4 clone na intenção de importar o projeto Jam do servidor Perforce, ao suprir o caminho (path) ao depósito e projeto e no caminho onde se tem o interesse em importar o seu projeto:
$ git-p4 clone //guest/perforce_software/jam@all p4import
Importing from //guest/perforce_software/jam@all into p4import
Initialized empty Git repository in /private/tmp/p4import/.git/
Import destination: refs/remotes/p4/master
Importing revision 9957 (100%)
Este projeto em particular só possui um branch, mas se você conter branches configurados em visualizações nos branches (ou somente numa configuração com diretórios), você poderá utilizar o marcador (flag) --detect-branches à função git p4 clone para importar a todas os branches deste projeto também.
Verifique Ramificação (Branching) para um pouco mais de detalhes acerca disto.
Neste ponto em questão, você quase finalizou.
Se você for ao diretório p4import e rodar a opção git log, você poderá ver o seu trabalho que foi importado:
$ git log -2
commit e5da1c909e5db3036475419f6379f2c73710c4e6
Author: giles <giles@giles@perforce.com>
Date: Wed Feb 8 03:13:27 2012 -0800
Correction to line 355; change </UL> to </OL>.
[git-p4: depot-paths = "//public/jam/src/": change = 8068]
commit aa21359a0a135dda85c50a7f7cf249e4f7b8fd98
Author: kwirth <kwirth@perforce.com>
Date: Tue Jul 7 01:35:51 2009 -0800
Fix spelling error on Jam doc page (cummulative -> cumulative).
[git-p4: depot-paths = "//public/jam/src/": change = 7304]
Você poderá ver que o git-p4 deixou um identificador em cada mensagem de commit.
Estará perfeitamente bem ao manter aquele identificador lá, para caso em que você necessite dar referências no número da mudança do Perforce de forma posterior.
Ainda assim, se você desejar remover aquele identificador, a hora certa de fazê-lo é agora – antes que você comece com o seu trabalho no novo repositório.
Você pode usar git filter-branch a remover o identificador de sequências num formato em massa (en masse):
$ git filter-branch --msg-filter 'sed -e "/^\[git-p4:/d"'
Rewrite e5da1c909e5db3036475419f6379f2c73710c4e6 (125/125)
Ref 'refs/heads/master' was rewritten
Se você rodar a opção git log, poderá ver que todos as somas de verificação (checksums) na forma SHA-1 destinadas as suas comissões (commits), houveram mudanças, mas que as sequências na git-p4 não se encontram mais no formato de mensagens às comissões:
$ git log -2
commit b17341801ed838d97f7800a54a6f9b95750839b7
Author: giles <giles@giles@perforce.com>
Date: Wed Feb 8 03:13:27 2012 -0800
Correction to line 355; change </UL> to </OL>.
commit 3e68c2e26cd89cb983eb52c024ecdfba1d6b3fff
Author: kwirth <kwirth@perforce.com>
Date: Tue Jul 7 01:35:51 2009 -0800
Fix spelling error on Jam doc page (cummulative -> cumulative).
O seu documento importado se encontra apto para ser lançado (push up) de encontro ao seu novo servidor Git.
Um Importador Personalizado
Se o seu sistema não estiver entre nenhum dos listados acima, você deve procurar por um importador de forma online - os importadores de qualidade estão disponíveis em muitos outros sistemas, incluindo os de CVS, Clear Case, Visual Source Safe e até mesmo o diretório de arquivos.
Se nenhuma dessas ferramentas funcionar para você, se você tiver uma ferramenta mais obscura ou se precisar de um processo de importação mais personalizado, use o git fast-import.
O comando em questão ler as simples instruções do stdin ao escrever na especificação de dados do Git.
Nesta maneira, criar objetos no Git é muito mais fácil do que se você estivesse rodando os comandos no Git em formatos crus, ou no caso da tentativa em redigir aos objetos neste mesmo formato (confira o Git Internals (Por Dentro do Git) para mais informações).
Neste sentido, você pode escrever o script de um importador que ler as informações necessárias do lado de fora do sistema no qual você fará a importação, e fará a impressão sem rodeios das informações para o stdout.
Em seguida, você pode executar esse programa e encaminhar sua saída para o git fast-import por meio de um pipe.
Para demonstrarmos mais rápido, você redigirá um importador simples.
Suponha que você trabalhe em current, faça backups ocasionais do projeto copiando o diretório para um diretório de backup com data no formato back_YYYY_MM_DD e queira importar esse conteúdo para o Git.
A estrutura do seu diretório se assemelha com isto:
$ ls /opt/import_from
back_2014_01_02
back_2014_01_04
back_2014_01_14
back_2014_02_03
current
Ao se importar um diretório no Git, você terá de fazer as revisões na maneira de como o Git guarda a base dos seus dados.
Como você deve se lembrar, o Git é fundamentalmente uma lista encadeada de objetos commit que apontam para um snapshot do conteúdo.
Tudo o que se deve fazer é contar ao fast-import que tipo de conteúdo os snapshots são, quais dados de commits lhes direcionam, e na ordem aos quais neles entram.
Sua estratégia deve ser seguir pelos snapshots a um nível de cada vez, e criar os seus commits pelo conteúdo em cada um de seus diretórios, se ligando em todo e cada submetimento de volta ao de modo antecedente.
Assim como fizemos no Uma Política de Exemplo Imposta pelo Git, nós redigiremos isso em Ruby, já que este é pelo qual trabalhamos no modo geral, bem como tem a tendência em possuir uma leitura mais fácil.
Você pode redigir a este exemplo de uma maneira muito simples sobre qualquer coisa na qual possua maior familiaridade – isto só necessita de sua impressão das devidas informações em prol do stdout.
Se estiver usando o Windows, isso significa que você deverá tomar um cuidado especial para não inserir retornos de carro no final das linhas – o git fast-import exige apenas avanços de linha (LF), e não a combinação de retorno de carro e avanço de linha (CRLF) usada pelo Windows.
Para começar, entre no diretório de destino e identifique cada subdiretório; cada um deles é um snapshot que você deseja importar como um commit. Você fará a troca por entre cada um dos subdiretórios e imprimir os comandos que sejam necessários à finalidade de os exportar. O laço principal básico será semelhante a este:
last_mark = nil
# loop through the directories
Dir.chdir(ARGV[0]) do
Dir.glob("*").each do |dir|
next if File.file?(dir)
# move into the target directory
Dir.chdir(dir) do
last_mark = print_export(dir, last_mark)
end
end
end
Você executa print_export dentro de cada diretório; o método recebe o manifesto e a marca do snapshot anterior e retorna o manifesto e a marca do snapshot atual, permitindo encadeá-los corretamente.
“Mark” é o termo usado pelo fast-import para um identificador atribuído a um commit; conforme cria os commits, você atribui uma marca a cada um deles para poder referenciá-lo a partir de outros commits.
Assim, a primeira coisa que deva ser feita no seu método de print_export é gerar uma marca advinda do nome de seus diretórios:
mark = convert_dir_to_mark(dir)
Você fará isso criando um array de diretórios e usando o valor do índice como marca, pois uma marca precisa ser um número inteiro. Seu método tem a aparência de:
$marks = []
def convert_dir_to_mark(dir)
if !$marks.include?(dir)
$marks << dir
end
($marks.index(dir) + 1).to_s
end
Agora que você tem uma representação inteira do commit, precisa de uma data para os metadados dele.
Como a data está expressa no nome do diretório, você fará sua análise sintática.
A linha seguinte no seu arquivo em print_export será:
date = convert_dir_to_date(dir)
onde convert_dir_to_date (converter diretório para data) se defina a partir de:
def convert_dir_to_date(dir)
if dir == 'current'
return Time.now().to_i
else
dir = dir.gsub('back_', '')
(year, month, day) = dir.split('_')
return Time.local(year, month, day).to_i
end
end
Isso retorna um valor inteiro para a data de cada diretório. A última metainformação necessária para cada commit são os dados do autor do commit, definidos diretamente em uma variável global:
$author = 'John Doe <john@example.com>'
Agora você está pronto para começar a imprimir os dados de commit para o importador. As informações iniciais indicam que você está definindo um objeto commit e em qual branch ele se encontra, seguidas pela marca gerada, pelos dados e pela mensagem do autor do commit e, por fim, pelo commit anterior, se houver. Os códigos tendem a ter essa visualização:
# print the import information
puts 'commit refs/heads/master'
puts 'mark :' + mark
puts "committer #{$author} #{date} -0700"
export_data('imported from ' + dir)
puts 'from :' + last_mark if last_mark
Você define diretamente o fuso horário (-0700), pois isso é simples. Se estiver importando de outro sistema, deverá especificar o fuso horário como um deslocamento. As mensagens dos envios têm de se expressar por um formato em especial:
data (size)\n(contents)
O formato consiste na palavra data, no tamanho dos dados que serão lidos, em uma quebra de linha e, por fim, nos dados.
Como mais adiante você precisará usar o mesmo formato para especificar o conteúdo dos arquivos, crie um método auxiliar, export_data:
def export_data(string)
print "data #{string.size}\n#{string}"
end
Resta apenas especificar o conteúdo dos arquivos de cada snapshot.
Isso é fácil, pois cada snapshot está em um diretório – você pode imprimir o comando deleteall, seguido pelo conteúdo de cada arquivo do diretório.
O Git logo fará as gravações de cada snapshot da maneira em que se julgar como sendo a mais apropriada:
puts 'deleteall'
Dir.glob("**/*").each do |file|
next if !File.file?(file)
inline_data(file)
end
Observação: Como muitos sistemas tratam suas revisões como alterações de um commit para outro, o fast-import também aceita, em cada commit, comandos que especificam quais arquivos foram adicionados, removidos ou modificados e qual é o novo conteúdo.
Você poderia calcular as diferenças entre os snapshots e fornecer apenas esses dados, mas isso é mais complexo – é mais simples fornecer todos os dados ao Git e deixar que ele descubra as diferenças.
Se essa abordagem for mais adequada aos seus dados, consulte a página de manual do fast-import para saber como fornecê-los dessa maneira.
O formato para listar o conteúdo de um novo arquivo ou especificar um arquivo modificado com seu novo conteúdo é o seguinte:
M 644 inline path/to/file
data (size)
(file contents)
Aqui, 644 é o modo (se houver arquivos executáveis, você precisará detectá-los e especificar 755), e inline indica que o conteúdo será listado imediatamente depois dessa linha.
O seu método em conformidade do inline_data deve remeter num teor destas bases:
def inline_data(file, code = 'M', mode = '644')
content = File.read(file)
puts "#{code} #{mode} inline #{file}"
export_data(content)
end
Você reutiliza o método export_data definido anteriormente, pois o formato é o mesmo usado para especificar os dados da mensagem de commit.
Por último, você precisa retornar a marca atual para que ela possa ser passada à próxima iteração:
return mark
|
Note
|
Se estiver usando o Windows, você precisará acrescentar uma etapa.
Como mencionado anteriormente, o Windows usa CRLF para quebras de linha, enquanto o
|
Isto é tudo. Este é o script completo:
#!/usr/bin/env ruby
$stdout.binmode
$author = "John Doe <john@example.com>"
$marks = []
def convert_dir_to_mark(dir)
if !$marks.include?(dir)
$marks << dir
end
($marks.index(dir)+1).to_s
end
def convert_dir_to_date(dir)
if dir == 'current'
return Time.now().to_i
else
dir = dir.gsub('back_', '')
(year, month, day) = dir.split('_')
return Time.local(year, month, day).to_i
end
end
def export_data(string)
print "data #{string.size}\n#{string}"
end
def inline_data(file, code='M', mode='644')
content = File.read(file)
puts "#{code} #{mode} inline #{file}"
export_data(content)
end
def print_export(dir, last_mark)
date = convert_dir_to_date(dir)
mark = convert_dir_to_mark(dir)
puts 'commit refs/heads/master'
puts "mark :#{mark}"
puts "committer #{$author} #{date} -0700"
export_data("imported from #{dir}")
puts "from :#{last_mark}" if last_mark
puts 'deleteall'
Dir.glob("**/*").each do |file|
next if !File.file?(file)
inline_data(file)
end
mark
end
# Loop through the directories
last_mark = nil
Dir.chdir(ARGV[0]) do
Dir.glob("*").each do |dir|
next if File.file?(dir)
# move into the target directory
Dir.chdir(dir) do
last_mark = print_export(dir, last_mark)
end
end
end
Se você executar esse script, obterá um conteúdo semelhante a este:
$ ruby import.rb /opt/import_from
commit refs/heads/master
mark :1
committer John Doe <john@example.com> 1388649600 -0700
data 29
imported from back_2014_01_02deleteall
M 644 inline README.md
data 28
# Hello
This is my readme.
commit refs/heads/master
mark :2
committer John Doe <john@example.com> 1388822400 -0700
data 29
imported from back_2014_01_04from :1
deleteall
M 644 inline main.rb
data 34
#!/bin/env ruby
puts "Hey there"
M 644 inline README.md
(...)
Para executar o importador, estando no diretório Git para o qual você deseja importar, encaminhe essa saída para o git fast-import por meio de um pipe.
Como ponto de partida, você pode criar um diretório, executar git init nele e depois executar seu script:
$ git init
Initialized empty Git repository in /opt/import_to/.git/
$ ruby import.rb /opt/import_from | git fast-import
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects: 5000
Total objects: 13 ( 6 duplicates )
blobs : 5 ( 4 duplicates 3 deltas of 5 attempts)
trees : 4 ( 1 duplicates 0 deltas of 4 attempts)
commits: 4 ( 1 duplicates 0 deltas of 0 attempts)
tags : 0 ( 0 duplicates 0 deltas of 0 attempts)
Total branches: 1 ( 1 loads )
marks: 1024 ( 5 unique )
atoms: 2
Memory total: 2344 KiB
pools: 2110 KiB
objects: 234 KiB
---------------------------------------------------------------------
pack_report: getpagesize() = 4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit = 8589934592
pack_report: pack_used_ctr = 10
pack_report: pack_mmap_calls = 5
pack_report: pack_open_windows = 2 / 2
pack_report: pack_mapped = 1457 / 1457
---------------------------------------------------------------------
Como você pode ver, quando a execução é concluída com sucesso, o programa apresenta várias estatísticas sobre o que realizou.
Nesse caso, você importou um total de 13 objetos de 4 commits para 1 branch.
Agora, execute git log para ver seu novo histórico:
$ git log -2
commit 3caa046d4aac682a55867132ccdfbe0d3fdee498
Author: John Doe <john@example.com>
Date: Tue Jul 29 19:39:04 2014 -0700
imported from current
commit 4afc2b945d0d3c8cd00556fbe2e8224569dc9def
Author: John Doe <john@example.com>
Date: Mon Feb 3 01:00:00 2014 -0700
imported from back_2014_02_03
Pronto: um repositório Git organizado e limpo.
É importante observar que nenhum checkout foi feito – inicialmente, não há nenhum arquivo no seu diretório de trabalho.
De se obtê-las, a obrigação será de fato às restaurações da forma contida no branch da onde a master se situa neste respectivo momento:
$ ls
$ git reset --hard master
HEAD is now at 3caa046 imported from current
$ ls
README.md main.rb
Você pode fazer muito mais com a ferramenta fast-import: lidar com modos diferentes, dados binários, várias branches e merges, tags, indicadores de progresso e muito mais.
Há diversos exemplos de cenários mais complexos no diretório contrib/fast-import do código-fonte do Git.