

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

# Migrar de forma incremental un repositorio
<a name="how-to-push-large-repositories"></a>

Al migrar a AWS CodeCommit, considere la posibilidad de enviar su repositorio en incrementos o fragmentos para reducir las posibilidades de que un problema de red intermitente o una degradación del rendimiento de la red provoque un error en toda la transferencia. Al realizar envíos incrementales con un script como el incluido aquí, puede reiniciar la migración y enviar únicamente aquellas confirmaciones que no se hayan podido enviar en el primer intento.

Los procedimientos incluidos en este tema explican cómo crear y ejecutar un script que migre su repositorio de forma incremental y envíe de nuevo solo aquellos fragmentos que no se han podido enviar hasta que se complete toda la migración.

Estas instrucciones se han redactado presuponiendo que ya ha completado los pasos indicados en [Configuración ](setting-up.md) y [Creación de un repositorio](how-to-create-repository.md). 

**Topics**
+ [Paso 0: Determinar si realizar una migración de forma incremental](#how-to-push-large-repositories-determine)
+ [Paso 1: Instale los requisitos previos y añada el CodeCommit repositorio como remoto](#how-to-push-large-repositories-prereq)
+ [Paso 2: Crear el script para migrar de forma incremental](#how-to-push-large-repositories-createscript)
+ [Paso 3: Ejecute el script y migre de forma incremental a CodeCommit](#how-to-push-large-repositories-runscript)
+ [Anexo: script de muestra `incremental-repo-migration.py`](#how-to-push-large-repositories-sample)

## Paso 0: Determinar si realizar una migración de forma incremental
<a name="how-to-push-large-repositories-determine"></a>

A la hora de determinar el tamaño total de su repositorio y decidir si migrar de forma incremental, debe considerar determinados factores. El factor más evidente es el tamaño total de los elementos del repositorio. Los factores, como el historial acumulado del repositorio, también pueden contribuir al tamaño. Un repositorio con años de historia y ramificaciones puede ser muy grande, aunque sus elementos individuales no lo sean. Puede utilizar distintas estrategias para simplificar la migración de estos repositorios y que sea más eficaz. Por ejemplo, puede utilizar una estrategia de clonación superficial para clonar un repositorio con un largo historial de desarrollo o puede desactivar la compresión delta para archivos binarios grandes. Puede buscar otras opciones en la documentación de Git o puede optar por establecer y configurar envíos incrementales para migrar el repositorio utilizando el script de muestra incluido en este tema `incremental-repo-migration.py`. 

Es posible que le interese configurar envíos incrementales en caso de que se cumpla una o más de las siguientes condiciones:
+ El repositorio que desea migrar tiene más de cinco años de historia.
+ Su conexión a Internet está sometida a interrupciones intermitentes, pérdidas de paquetes, respuestas lentas o cualquier otra interrupción del servicio.
+ El tamaño total del repositorio es superior a 2 GB e intenta migrar todo el repositorio.
+ El repositorio contiene elementos grandes o binarios que no se comprimen bien, como archivos de imágenes grandes con más de cinco versiones controladas.
+ Ya has intentado realizar una migración CodeCommit y has recibido un mensaje de «error de servicio interno». 

Aunque no se cumpla ninguna de las condiciones anteriores, también puede optar por enviar de forma incremental.

## Paso 1: Instale los requisitos previos y añada el CodeCommit repositorio como remoto
<a name="how-to-push-large-repositories-prereq"></a>

Puede crear su propio script personalizado con sus propios requisitos previos. Si utiliza el ejemplo incluido en este tema, debe:
+ Instalar los requisitos previos.
+ Clonar el repositorio en su equipo local.
+ Añada el CodeCommit repositorio como remoto al repositorio que desee migrar.

**Configurado para ejecutar incremental-repo-migration .py**

1.  En el equipo local, instale Python 2.6 o posterior. Para obtener más información y las versiones más recientes, consulte [el sitio web de Python](https://www.python.org/downloads/).

1. En la misma computadora, instale GitPython, que es una biblioteca de Python que se usa para interactuar con los repositorios de Git. Para obtener más información, consulte la [documentación de GitPython](http://gitpython.readthedocs.org/en/stable/).

1.  Utilice el comando **git clone --mirror** para clonar el repositorio que desea migrar a su equipo local. Desde el terminal (Linux, macOS o Unix) o el símbolo del sistema (Windows), utilice el comando **git clone --mirror** para crear un repositorio local para el repositorio, incluido el directorio en el que desea crear el repositorio local. Por ejemplo, para clonar un repositorio de Git llamado *MyMigrationRepo* con una URL *https://example.com/my-repo/* de en un directorio llamado*my-repo*:

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

   El resultado debería ser similar al siguiente, que indica que el repositorio se ha clonado en un repositorio local vacío denominado 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. Cambia los directorios al repositorio local del repositorio que acabas de clonar (por ejemplo,*my-repo*). En ese directorio, utilice el comando **git remote add *DefaultRemoteName* *RemoteRepositoryURL*** para añadir el repositorio de CodeCommit como repositorio remoto del repositorio local.
**nota**  
Si envía repositorios de gran tamaño, considere la posibilidad de utilizar SSH en lugar de HTTPS. Si envía un cambio grande, un gran número de cambios o un repositorio grande, a menudo las conexiones HTTPS de ejecución prolongada suelen interrumpirse de forma prematura debido a problemas de red o de configuración del cortafuegos. Para obtener más información sobre la configuración de CodeCommit SSH, consulte o. [Para conexiones SSH en Linux, macOS o Unix](setting-up-ssh-unixes.md) [Para conexiones SSH en Windows](setting-up-ssh-windows.md)

    Por ejemplo, utilice el siguiente comando para añadir el punto de conexión SSH a un CodeCommit repositorio MyDestinationRepo denominado repositorio remoto para el nombre remoto: `codecommit` 

   ```
   git remote add codecommit ssh://git-codecommit.us-east-2.amazonaws.com/v1/repos/MyDestinationRepo
   ```
**sugerencia**  
Dado que se trata de un clon, el nombre remoto predeterminado (`origin`) ya está en uso. Deberá utilizar otro nombre remoto. Aunque el ejemplo utiliza `codecommit`, puede usar el nombre que desee. Utilice el comando **git remote show** para revisar la lista de remotos configurados para su repositorio local.

1. Utilice el comando **git remote -v** para mostrar la búsqueda y el envío de la configuración de su repositorio local y confirmar que están configurados correctamente. Por ejemplo:

   ```
   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)
   ```
**sugerencia**  
Si siguen apareciendo entradas de búsqueda y envío para otro repositorio remoto (por ejemplo, entradas de origen), utilice el comando **git remote set-url --delete** para eliminarlas.

## Paso 2: Crear el script para migrar de forma incremental
<a name="how-to-push-large-repositories-createscript"></a>

Estos pasos se han escrito partiendo del supuesto de que está utilizando el script de ejemplo `incremental-repo-migration.py`. 

1. Abra un editor de texto y pegue el contenido [del script de muestra](#how-to-push-large-repositories-sample) en un documento vacío.

1. Guarde el documento en un directorio de documentos (no el directorio de trabajo de su repositorio local) y nómbrelo `incremental-repo-migration.py`. Asegúrese de que el directorio que elija esté configurado en sus variables del entorno local o de la ruta, para poder ejecutar el script de Python desde una línea de comandos o terminal.

## Paso 3: Ejecute el script y migre de forma incremental a CodeCommit
<a name="how-to-push-large-repositories-runscript"></a>

 Ahora que ha creado su `incremental-repo-migration.py` script, puede usarlo para migrar de forma incremental un repositorio local a un repositorio. CodeCommit De forma predeterminada, el script envía las confirmaciones en lotes de 1 000 e intenta utilizar la configuración de Git para el directorio desde el que se ejecuta como la configuración del repositorio local y del repositorio remoto. Puede utilizar las opciones incluidas en `incremental-repo-migration.py` para configurar otros ajustes, si es necesario.

1. Desde el terminal o la línea de comandos, cambie los directorios al repositorio local al que desea migrar.

1. Desde el directorio ejecute el siguiente comando:

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

1. El script se ejecuta y muestra el progreso en el terminal o en la línea de comandos. El progreso de algunos repositorios grandes es más lento. El script se detiene si intenta enviarlo sin éxito tres veces seguidas. A continuación, podrá volver a ejecutar el script que empieza de nuevo desde el lote que ha devuelto un error. Podrá volver a ejecutar el script hasta que todo se envíe correctamente y se haya completado la migración.

**sugerencia**  
Puede ejecutar `incremental-repo-migration.py` desde cualquier directorio siempre y cuando utilice las opciones `-l` y `-r` para especificar la configuración local y remota. Por ejemplo, si quieres usar el script de cualquier directorio para migrar un repositorio local ubicado en /tmp/ *my-repo* a un remoto con el siguiente apodo: *codecommit*  

```
python incremental-repo-migration.py -l "/tmp/my-repo" -r "codecommit" 
```
 También puede utilizar la opción `-b` para cambiar el tamaño del lote predeterminado utilizado para los envíos incrementales. Por ejemplo, si envía regularmente un repositorio con archivos binarios muy grandes que cambian con frecuencia y trabaja desde una ubicación con un ancho de banda de la red limitado, le recomendamos que utilice la opción `-b` para cambiar el tamaño de 1 000 a 500. Por ejemplo:  

```
python incremental-repo-migration.py -b 500
```
De este modo, el repositorio local envía de forma incremental en lotes de 500 confirmaciones. Si decide cambiar el tamaño del lote de nuevo al migrar el repositorio (por ejemplo, si decide reducir el tamaño del lote después de un intento erróneo), recuerde utilizar la opción `-c` para eliminar las etiquetas del lote antes de restablecer el tamaño del lote con `-b`:  

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

**importante**  
No utilice la opción `-c` si desea volver a ejecutar el script después de un intento erróneo. La opción `-c` elimina las etiquetas que se utilizan para crear los lotes de confirmaciones. Utilice la opción `-c` solo si desea cambiar el tamaño del lote y empezar de nuevo, o si decide que ya no quiere utilizar el script.

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

Para su comodidad, hemos desarrollado un script de Python de muestra, `incremental-repo-migration.py`, para enviar un repositorio de forma incremental. Este script es un ejemplo de código abierto y se proporciona tal cual.

```
# 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
)
```