

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

# Migrar um repositório de forma incremental
<a name="how-to-push-large-repositories"></a>

Ao migrar para AWS CodeCommit, considere enviar seu repositório em incrementos ou partes para reduzir as chances de um problema de rede intermitente ou um desempenho de rede degradado fazer com que todo o push falhe. Ao usar pushes em incrementos com um script como o que está incluído aqui, você poderá reiniciar a migração e enviar por push somente as confirmações que não obtiveram êxito na tentativa anterior.

Os procedimentos neste tópico mostram como criar e executar um script que migra o repositório em incrementos e reenvia por push somente os incrementos que não obtiveram êxito até a migração estar concluída.

Essas instruções foram escritas presumindo que você já tenha concluído as etapas em [Configurando ](setting-up.md) e [Criar um repositório](how-to-create-repository.md). 

**Topics**
+ [Etapa 0: determinar se a migração será incremental](#how-to-push-large-repositories-determine)
+ [Etapa 1: instalar os pré-requisitos e adicionar o CodeCommit repositório como remoto](#how-to-push-large-repositories-prereq)
+ [Etapa 2: criar o script a ser usado na migração em incrementos](#how-to-push-large-repositories-createscript)
+ [Etapa 3: Executar o script e migrar incrementalmente para CodeCommit](#how-to-push-large-repositories-runscript)
+ [Apêndice: Amostra de script `incremental-repo-migration.py`](#how-to-push-large-repositories-sample)

## Etapa 0: determinar se a migração será incremental
<a name="how-to-push-large-repositories-determine"></a>

Há vários fatores a considerar para determinar o tamanho total do seu repositório e se migrará de maneira incremental. O mais óbvio é o tamanho total dos artefatos no repositório. Fatores como histórico acumulado do repositório também podem contribuir para o tamanho. Um repositório com anos de histórico e ramificações pode ser muito grande, mesmo que os recursos individuais não sejam. Há uma série de estratégias que você pode utilizar para tornar a migração desses repositórios mais simples e mais eficiente. Por exemplo, você pode usar uma estratégia de clonagem superficial ao clonar um repositório com um longo histórico de desenvolvimento, ou pode desativar a compactação delta para grandes arquivos binários. Você pode pesquisar opções consultando a documentação do Git ou optar pela configuração de push em incrementos para migrar o seu repositório usando a amostra de script incluída neste tópico, `incremental-repo-migration.py` 

Você pode configurar pushes em incrementos se uma ou mais das seguintes condições forem verdadeiras:
+ O repositório que você quer migrar tem um histórico de mais de cinco anos.
+ Sua conexão com a Internet está sujeita a interrupções intermitentes, queda de pacotes, resposta lenta ou outras interrupções no serviço.
+ O tamanho total do repositório é maior que 2 GB e você pretende migrá-lo por inteiro.
+ O repositório contém grandes artefatos ou binários que não são bem compactados, como arquivos grandes de imagem de mais de cinco versões controladas.
+ Você já tentou migrar CodeCommit e recebeu uma mensagem de “Erro de serviço interno”. 

Mesmo se nenhuma das condições acima for verdadeira, você ainda poderá escolher enviar pushes de maneira incremental.

## Etapa 1: instalar os pré-requisitos e adicionar o CodeCommit repositório como remoto
<a name="how-to-push-large-repositories-prereq"></a>

É possível criar o seu próprio script personalizado que tenha pré-requisitos próprios. Se você usar os exemplos incluídos neste tópico, deverá:
+ Instalar seus pré-requisitos.
+ Clonar o repositório para o computador local.
+ Adicione o CodeCommit repositório como remoto para o repositório que você deseja migrar.

**Configure para executar incremental-repo-migration o.py**

1.  No computador local, instale o Python 2.6 ou posterior. Para obter mais informações sobre as versões mais recentes, consulte [o site da Python](https://www.python.org/downloads/).

1. No mesmo computador, instale GitPython, que é uma biblioteca Python usada para interagir com repositórios Git. Para obter mais informações, consulte a [a documentação do GitPython](http://gitpython.readthedocs.org/en/stable/).

1.  Use o comando **git clone --mirror** para clonar o repositório que você deseja migrar para o seu computador local. No terminal (Linux, macOS ou Unix) ou no prompt de comando (Windows), use o comando **git clone --mirror** para criar um repositório local para o repositório, incluindo o diretório onde você deseja criá-lo. Por exemplo, para clonar um repositório Git {{MyMigrationRepo}} chamado com uma URL {{https://example.com/my-repo/}} de para um diretório chamado: {{my-repo}}

   ```
   git clone --mirror https://example.com/my-repo/MyMigrationRepo.git my-repo
   ```

   Você verá uma saída semelhante à seguinte, que indica que o repositório foi clonado em um repositório local bare chamado my-repo:

   ```
   Cloning into bare repository 'my-repo'...
   remote: Counting objects: 20, done.
   remote: Compressing objects: 100% (17/17), done.
   remote: Total 20 (delta 5), reused 15 (delta 3)
   Unpacking objects: 100% (20/20), done.
   Checking connectivity... done.
   ```

1. Altere os diretórios para o repositório local do repositório que você acabou de clonar (por exemplo,). {{my-repo}} Desse diretório, use o comando **git remote add {{DefaultRemoteName}} {{RemoteRepositoryURL}}** para adicionar o repositório CodeCommit como um repositório remoto para o repositório local.
**nota**  
Ao enviar grandes repositórios por push, considere utilizar SSH em vez de HTTPS. Ao enviar por push uma grande alteração, um grande número de alterações ou um grande repositório, as conexões HTTPS de execução prolongada costumam ser encerradas prematuramente devido a problemas de rede ou configurações do firewall. Para obter mais informações sobre a configuração CodeCommit do SSH, consulte [Para conexões SSH no Linux, macOS ou Unix](setting-up-ssh-unixes.md) ou[Para conexões SSH no Windows](setting-up-ssh-windows.md).

    Por exemplo, use o comando a seguir para adicionar o endpoint SSH para um CodeCommit repositório chamado MyDestinationRepo como repositório remoto para o remoto chamado: `codecommit` 

   ```
   git remote add codecommit ssh://git-codecommit.us-east-2.amazonaws.com/v1/repos/MyDestinationRepo
   ```
**dica**  
Por ser um clone, o nome do repositório remoto padrão (`origin`) já está em uso. Você deverá usar outro nome de repositório remoto. Embora o exemplo use `codecommit`, você pode usar o nome que quiser. Use o comando **git remote show** para analisar a lista de conjunto de repositórios remotos do seu repositório local.

1. Use o comando **git remote -v** para exibir as configurações de busca e push do seu repositório local e confirmar se estão corretas. Por exemplo:

   ```
   codecommit  ssh://git-codecommit.us-east-2.amazonaws.com/v1/repos/MyDestinationRepo (fetch)
   codecommit  ssh://git-codecommit.us-east-2.amazonaws.com/v1/repos/MyDestinationRepo (push)
   ```
**dica**  
Se você ainda vir entradas de busca e push de um repositório remoto diferente (por exemplo, entradas para origin), use o comando **git remote set-url --delete** para removê-las.

## Etapa 2: criar o script a ser usado na migração em incrementos
<a name="how-to-push-large-repositories-createscript"></a>

Estas etapas foram escritas presumindo que você esteja usando o script de exemplo `incremental-repo-migration.py`. 

1. Abra um editor de texto e cole os conteúdos da [amostra de script](#how-to-push-large-repositories-sample) em um documento vazio.

1. Salve o documento em um diretório de documentos (não o diretório de trabalho do seu repositório local) e nomeie-o `incremental-repo-migration.py`. Verifique se o diretório escolhido está configurado no ambiente local ou nas variáveis PATH, para que seja possível executar o script Python a partir de uma linha de comando ou um terminal.

## Etapa 3: Executar o script e migrar incrementalmente para CodeCommit
<a name="how-to-push-large-repositories-runscript"></a>

 Agora que você criou seu `incremental-repo-migration.py` script, você pode usá-lo para migrar incrementalmente um repositório local para um repositório. CodeCommit Por padrão, o script envia confirmações por push em lotes de 1.000 confirmações e tenta usar as configurações do Git para o diretório de onde é executado como configurações para os repositórios local e remoto. Você pode usar as opções incluídas em `incremental-repo-migration.py` para definir outras configurações, se necessário.

1. No terminal ou na linha de comando, altere os diretórios para o repositório local que você deseja migrar.

1. Nesse diretório, execute o seguinte comando:

   ```
   python incremental-repo-migration.py
   ```

1. O script executa e mostra progresso no terminal ou na linha de comando. Alguns repositórios grandes demoram para mostrar o progresso. O script será interrompido se um único push falhar três vezes. Então, você poderá executar novamente o script, que começará pelo lote que falhou. Execute o script novamente até que todos os pushes obtenham êxito e a migração seja concluída.

**dica**  
Você pode executar `incremental-repo-migration.py` a partir de qualquer diretório desde que utilize as opções `-l` e `-r` para especificar as configurações locais e remotas a serem usadas. Por exemplo, para usar o script de qualquer diretório para migrar um repositório local localizado em /tmp/ {{my-repo}} para um remoto apelidado: {{codecommit}}  

```
python incremental-repo-migration.py -l "/tmp/my-repo" -r "{{codecommit}}" 
```
 Você também pode querer usar a opção `-b` para alterar o tamanho do lote padrão usado ao enviar por push de forma incremental. Por exemplo, se você estiver enviando por push regularmente um repositório de arquivos binários muito grandes que costumam mudar frequentemente e trabalham de um local com largura de banda da rede restrita, use a opção `-b` para alterar o tamanho do lote para 500 em vez de 1.000. Por exemplo:  

```
python incremental-repo-migration.py -b 500
```
Isso envia o repositório local por push de maneira incremental em lotes de 500 confirmações. Se você decidir alterar o tamanho do lote novamente ao migrar o repositório (por exemplo, se você decidir reduzir o tamanho do lote após uma tentativa falha), lembre-se de usar a opção `-c` para remover as tags do lote antes de redefinir o tamanho do lote com `-b`:  

```
python incremental-repo-migration.py -c
python incremental-repo-migration.py -b 250
```

**Importante**  
Não use a opção `-c` se desejar executar novamente o script após uma falha. A opção `-c` elimina as tags usadas para acomodar as confirmações em lote. Use a opção `-c` somente se desejar alterar o tamanho do lote e iniciar novamente ou se decidir que não quer mais usar o script.

## Apêndice: Amostra de script `incremental-repo-migration.py`
<a name="how-to-push-large-repositories-sample"></a>

Para a sua conveniência, desenvolvemos um amostra de script Python, `incremental-repo-migration.py`, para enviar um repositório por push de maneira incremental. Esse script é um exemplo de código aberto e é fornecido como se encontra.

```
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. Licensed under the Amazon Software License (the "License").
# You may not use this file except in compliance with the License. A copy of the License is located at
#    http://aws.amazon.com/asl/
# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for
# the specific language governing permissions and limitations under the License.

#!/usr/bin/env python

import os
import sys
from optparse import OptionParser
from git import Repo, TagReference, RemoteProgress, GitCommandError


class PushProgressPrinter(RemoteProgress):
    def update(self, op_code, cur_count, max_count=None, message=""):
        op_id = op_code & self.OP_MASK
        stage_id = op_code & self.STAGE_MASK
        if op_id == self.WRITING and stage_id == self.BEGIN:
            print("\tObjects: %d" % max_count)


class RepositoryMigration:
    MAX_COMMITS_TOLERANCE_PERCENT = 0.05
    PUSH_RETRY_LIMIT = 3
    MIGRATION_TAG_PREFIX = "codecommit_migration_"

    def migrate_repository_in_parts(
        self, repo_dir, remote_name, commit_batch_size, clean
    ):
        self.next_tag_number = 0
        self.migration_tags = []
        self.walked_commits = set()
        self.local_repo = Repo(repo_dir)
        self.remote_name = remote_name
        self.max_commits_per_push = commit_batch_size
        self.max_commits_tolerance = (
            self.max_commits_per_push * self.MAX_COMMITS_TOLERANCE_PERCENT
        )

        try:
            self.remote_repo = self.local_repo.remote(remote_name)
            self.get_remote_migration_tags()
        except (ValueError, GitCommandError):
            print(
                "Could not contact the remote repository. The most common reasons for this error are that the name of the remote repository is incorrect, or that you do not have permissions to interact with that remote repository."
            )
            sys.exit(1)

        if clean:
            self.clean_up(clean_up_remote=True)
            return

        self.clean_up()

        print("Analyzing repository")
        head_commit = self.local_repo.head.commit
        sys.setrecursionlimit(max(sys.getrecursionlimit(), head_commit.count()))

        # tag commits on default branch
        leftover_commits = self.migrate_commit(head_commit)
        self.tag_commits([commit for (commit, commit_count) in leftover_commits])

        # tag commits on each branch
        for branch in self.local_repo.heads:
            leftover_commits = self.migrate_commit(branch.commit)
            self.tag_commits([commit for (commit, commit_count) in leftover_commits])

        # push the tags
        self.push_migration_tags()

        # push all branch references
        for branch in self.local_repo.heads:
            print("Pushing branch %s" % branch.name)
            self.do_push_with_retries(ref=branch.name)

        # push all tags
        print("Pushing tags")
        self.do_push_with_retries(push_tags=True)

        self.get_remote_migration_tags()
        self.clean_up(clean_up_remote=True)

        print("Migration to CodeCommit was successful")

    def migrate_commit(self, commit):
        if commit in self.walked_commits:
            return []

        pending_ancestor_pushes = []
        commit_count = 1

        if len(commit.parents) > 1:
            # This is a merge commit
            # Ensure that all parents are pushed first
            for parent_commit in commit.parents:
                pending_ancestor_pushes.extend(self.migrate_commit(parent_commit))
        elif len(commit.parents) == 1:
            # Split linear history into individual pushes
            next_ancestor, commits_to_next_ancestor = self.find_next_ancestor_for_push(
                commit.parents[0]
            )
            commit_count += commits_to_next_ancestor
            pending_ancestor_pushes.extend(self.migrate_commit(next_ancestor))

        self.walked_commits.add(commit)

        return self.stage_push(commit, commit_count, pending_ancestor_pushes)

    def find_next_ancestor_for_push(self, commit):
        commit_count = 0

        # Traverse linear history until we reach our commit limit, a merge commit, or an initial commit
        while (
            len(commit.parents) == 1
            and commit_count < self.max_commits_per_push
            and commit not in self.walked_commits
        ):
            commit_count += 1
            self.walked_commits.add(commit)
            commit = commit.parents[0]

        return commit, commit_count

    def stage_push(self, commit, commit_count, pending_ancestor_pushes):
        # Determine whether we can roll up pending ancestor pushes into this push
        combined_commit_count = commit_count + sum(
            ancestor_commit_count
            for (ancestor, ancestor_commit_count) in pending_ancestor_pushes
        )

        if combined_commit_count < self.max_commits_per_push:
            # don't push anything, roll up all pending ancestor pushes into this pending push
            return [(commit, combined_commit_count)]

        if combined_commit_count <= (
            self.max_commits_per_push + self.max_commits_tolerance
        ):
            # roll up everything into this commit and push
            self.tag_commits([commit])
            return []

        if commit_count >= self.max_commits_per_push:
            # need to push each pending ancestor and this commit
            self.tag_commits(
                [
                    ancestor
                    for (ancestor, ancestor_commit_count) in pending_ancestor_pushes
                ]
            )
            self.tag_commits([commit])
            return []

        # push each pending ancestor, but roll up this commit
        self.tag_commits(
            [ancestor for (ancestor, ancestor_commit_count) in pending_ancestor_pushes]
        )
        return [(commit, commit_count)]

    def tag_commits(self, commits):
        for commit in commits:
            self.next_tag_number += 1
            tag_name = self.MIGRATION_TAG_PREFIX + str(self.next_tag_number)

            if tag_name not in self.remote_migration_tags:
                tag = self.local_repo.create_tag(tag_name, ref=commit)
                self.migration_tags.append(tag)
            elif self.remote_migration_tags[tag_name] != str(commit):
                print(
                    "Migration tags on the remote do not match the local tags. Most likely your batch size has changed since the last time you ran this script. Please run this script with the --clean option, and try again."
                )
                sys.exit(1)

    def push_migration_tags(self):
        print("Will attempt to push %d tags" % len(self.migration_tags))
        self.migration_tags.sort(
            key=lambda tag: int(tag.name.replace(self.MIGRATION_TAG_PREFIX, ""))
        )
        for tag in self.migration_tags:
            print(
                "Pushing tag %s (out of %d tags), commit %s"
                % (tag.name, self.next_tag_number, str(tag.commit))
            )
            self.do_push_with_retries(ref=tag.name)

    def do_push_with_retries(self, ref=None, push_tags=False):
        for i in range(0, self.PUSH_RETRY_LIMIT):
            if i == 0:
                progress_printer = PushProgressPrinter()
            else:
                progress_printer = None

            try:
                if push_tags:
                    infos = self.remote_repo.push(tags=True, progress=progress_printer)
                elif ref is not None:
                    infos = self.remote_repo.push(
                        refspec=ref, progress=progress_printer
                    )
                else:
                    infos = self.remote_repo.push(progress=progress_printer)

                success = True
                if len(infos) == 0:
                    success = False
                else:
                    for info in infos:
                        if (
                            info.flags & info.UP_TO_DATE
                            or info.flags & info.NEW_TAG
                            or info.flags & info.NEW_HEAD
                        ):
                            continue
                        success = False
                        print(info.summary)

                if success:
                    return
            except GitCommandError as err:
                print(err)

        if push_tags:
            print("Pushing all tags failed after %d attempts" % (self.PUSH_RETRY_LIMIT))
        elif ref is not None:
            print("Pushing %s failed after %d attempts" % (ref, self.PUSH_RETRY_LIMIT))
            print(
                "For more information about the cause of this error, run the following command from the local repo: 'git push %s %s'"
                % (self.remote_name, ref)
            )
        else:
            print(
                "Pushing all branches failed after %d attempts"
                % (self.PUSH_RETRY_LIMIT)
            )
        sys.exit(1)

    def get_remote_migration_tags(self):
        remote_tags_output = self.local_repo.git.ls_remote(
            self.remote_name, tags=True
        ).split("\n")
        self.remote_migration_tags = dict(
            (tag.split()[1].replace("refs/tags/", ""), tag.split()[0])
            for tag in remote_tags_output
            if self.MIGRATION_TAG_PREFIX in tag
        )

    def clean_up(self, clean_up_remote=False):
        tags = [
            tag
            for tag in self.local_repo.tags
            if tag.name.startswith(self.MIGRATION_TAG_PREFIX)
        ]

        # delete the local tags
        TagReference.delete(self.local_repo, *tags)

        # delete the remote tags
        if clean_up_remote:
            tags_to_delete = [":" + tag_name for tag_name in self.remote_migration_tags]
            self.remote_repo.push(refspec=tags_to_delete)


parser = OptionParser()
parser.add_option(
    "-l",
    "--local",
    action="store",
    dest="localrepo",
    default=os.getcwd(),
    help="The path to the local repo. If this option is not specified, the script will attempt to use current directory by default. If it is not a local git repo, the script will fail.",
)
parser.add_option(
    "-r",
    "--remote",
    action="store",
    dest="remoterepo",
    default="codecommit",
    help="The name of the remote repository to be used as the push or migration destination. The remote must already be set in the local repo ('git remote add ...'). If this option is not specified, the script will use 'codecommit' by default.",
)
parser.add_option(
    "-b",
    "--batch",
    action="store",
    dest="batchsize",
    default="1000",
    help="Specifies the commit batch size for pushes. If not explicitly set, the default is 1,000 commits.",
)
parser.add_option(
    "-c",
    "--clean",
    action="store_true",
    dest="clean",
    default=False,
    help="Remove the temporary tags created by migration from both the local repo and the remote repository. This option will not do any migration work, just cleanup. Cleanup is done automatically at the end of a successful migration, but not after a failure so that when you re-run the script, the tags from the prior run can be used to identify commit batches that were not pushed successfully.",
)

(options, args) = parser.parse_args()

migration = RepositoryMigration()
migration.migrate_repository_in_parts(
    options.localrepo, options.remoterepo, int(options.batchsize), options.clean
)
```