Migrare un repository in modo incrementale - AWS CodeCommit

AWS CodeCommit non è più disponibile per i nuovi clienti. I clienti esistenti di AWS CodeCommit possono continuare a utilizzare il servizio normalmente. Scopri di più»

Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.

Migrare un repository in modo incrementale

Durante la migrazione in AWS CodeCommit, valuta la possibilità di eseguire il push del repository in incrementi o blocchi per ridurre il rischio che un problema di rete intermittente o prestazioni di rete ridotte possano comportare l'esito negativo dell'intero push. L'utilizzo dei push incrementali con uno script simile a quello incluso in questa pagina, consente di riavviare la migrazione ed eseguire solo il push dei commit che non sono riusciti nel tentativo precedente.

Le procedure in questo argomento illustrano come creare ed eseguire uno script che migra il repository in incrementi ed esegue un nuovo push dei soli incrementi non riusciti, finché la migrazione non è stata completata.

Queste istruzioni presuppongono che sia già stata completata la procedura in Configurazione e Creazione di un repository .

Fase 0: determinare se effettuare la migrazione in modo incrementale

Prima di stabilire la dimensione globale del repository e decidere se effettuare una migrazione incrementale devi prendere in considerazione diversi fattori. Il più ovvio è la dimensione complessiva degli elementi inclusi nel repository. Anche un fattore come la cronologia cumulativa del repository può contribuire alla dimensione. Un repository con anni di storia e numerosi rami può essere molto grande, anche se i singoli asset non lo sono. Puoi adottare diverse strategie per rendere la migrazione di questi repository più semplice ed efficiente. Ad esempio, l'implementazione di una strategia di clone superficiale durante la clonazione di un repository caratterizzato da una lunga storia di sviluppo oppure la disattivazione della compressione delta per i file binari di grandi dimensioni. Puoi cercare le opzioni disponibili consultando la documentazione Git oppure puoi scegliere di impostare e configurare push incrementali per migrare il repository utilizzando lo script di esempio incluso nell'argomento incremental-repo-migration.py.

Puoi scegliere di configurare i push incrementali se si verifica una o più delle seguenti condizioni:

  • Il repository che desideri migrare ha più di cinque anni di storia.

  • La connessione a Internet è soggetta a interruzioni intermittenti, eliminazione di pacchetti, risposte lente o altre interruzioni durante il servizio.

  • La dimensione complessiva del repository è superiore a 2 GB e hai intenzione di migrare l'intero repository.

  • Il repository contiene elementi o file binari di grandi dimensioni che non si comprimono correttamente, ad esempio file di immagine di grandi dimensioni con più di cinque versioni tracciate.

  • In precedenza hai tentato una migrazione verso CodeCommit e hai ricevuto un messaggio di «Errore interno del servizio».

Anche se nessuna delle condizioni appena indicate si verifica, puoi comunque scegliere di eseguire il push in modo incrementale.

Fase 1: Installare i prerequisiti e aggiungere il CodeCommit repository come telecomando

Puoi creare uno script personalizzato con prerequisiti specifici. Se utilizzi l'esempio incluso in questo argomento, devi:

  • Installarne i prerequisiti.

  • Clonare il repository sul tuo computer locale.

  • Aggiungi il CodeCommit repository come remoto per il repository che desideri migrare.

Configura per eseguire .py incremental-repo-migration
  1. Sul computer locale, installare Python 2.6 o versione successiva. Per ulteriori informazioni e per le versioni più recenti, vedere il sito Web di Python.

  2. Sullo stesso computer, install GitPython, che è una libreria Python usata per interagire con i repository Git. Per ulteriori informazioni, consulta la GitPython documentazione.

  3. Utilizzare il comando git clone --mirror per clonare il repository da migrare sul computer locale. Dal terminale (Linux, macOS o Unix) o dal prompt dei comandi (Windows), usa il git clone --mirror comando per creare un repository locale per il repository, inclusa la directory in cui desideri creare il repository locale. Ad esempio, per clonare un repository Git denominato MyMigrationRepocon un URL di https://example.com/my-repo/ in una directory denominata my-repo:

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

    Dovrebbe essere visualizzato un output simile al seguente, che indica che il repository è stato clonato in un repository locale essenziale denominato 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.
  4. Modificare le directory del repository locale con il repository appena clonato, ad esempio my-repo. Da questa directory, utilizzare il comando git remote add DefaultRemoteName RemoteRepositoryURL per aggiungere il repository CodeCommit come repository remoto per il repository locale.

    Nota

    Quando si esegue il push di repository di grandi dimensioni, è consigliabile utilizzare SSH anziché HTTPS. Quando si esegue il push di una modifica estesa, di un numero elevato di modifiche oppure di un repository di grandi dimensioni, le connessioni HTTPS con esecuzione prolungata vengono spesso terminate prematuramente a causa di problemi di rete o delle impostazioni del firewall. Per ulteriori informazioni sulla configurazione di SSH, consulta CodeCommit o. Per connessioni SSH su Linux, macOS o Unix Per le connessioni SSH in Windows

    Ad esempio, usa il comando seguente per aggiungere l'endpoint SSH per un CodeCommit repository MyDestinationRepo denominato repository remoto per il nome remoto: codecommit

    git remote add codecommit ssh://git-codecommit.us-east-2.amazonaws.com/v1/repos/MyDestinationRepo
    Suggerimento

    Poiché è un clone, il nome di default del remoto (origin) è già in uso. È quindi necessario utilizzare un altro nome di remoto. Anche se l'esempio utilizza codecommit, è possibile immettere qualsiasi nome. Specificare il comando git remote show per esaminare l'elenco dei remoti impostati per il repository locale.

  5. Specificare il comando git remote -v per visualizzare le impostazioni di recupero e di push per il repository locale e confermarne la corretta impostazione. Ad esempio:

    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)
    Suggerimento

    Se sono ancora visualizzate voci di recupero e push per un altro repository remoto, ad esempio le voci per origin, rimuoverle utilizzando il comando git remote set-url --delete.

Passaggio 2: creare lo script da utilizzare per la migrazione incrementale

Queste istruzioni presuppongono che si sta utilizzando lo script di esempio incremental-repo-migration.py.

  1. Aprire un editor di testo e incollare il contenuto dello script di esempio in un documento vuoto.

  2. Salvare il documento in una directory di documenti (non la directory di lavoro del repository locale) e denominarlo incremental-repo-migration.py. Verificare che la directory scelta sia configurata nell'ambiente locale o nelle variabili di percorso, in modo da poter eseguire lo script Python dalla riga di comando o sul terminale.

Passaggio 3: Esegui lo script ed esegui la migrazione incrementale a CodeCommit

Ora che hai creato incremental-repo-migration.py lo script, puoi utilizzarlo per migrare in modo incrementale un repository locale in un repository. CodeCommit Per impostazione predefinita, lo script esegue il push dei commit in batch di 1.000 commit e tenta di utilizzare le impostazioni Git per la directory da cui viene eseguito come impostazioni per il repository locale e quello remoto. È possibile utilizzare le opzioni incluse in incremental-repo-migration.py per configurare altre impostazioni, se necessario.

  1. Dal terminale o dal prompt dei comandi, modificare le directory del repository locale che si desidera migrare.

  2. Da tale directory, eseguire il comando seguente:

    python incremental-repo-migration.py
  3. Lo script viene eseguito e mostra l'avanzamento sul terminale o nel prompt dei comandi. La visualizzazione dell'avanzamento è lenta per alcuni repository di grandi dimensioni. Lo script viene interrotto se un singolo push ha esito negativo per tre volte. È quindi possibile eseguirlo di nuovo, partendo dal batch che ha restituito esito negativo. È possibile rieseguire lo script finché tutti i push non avranno esito positivo e la migrazione non sarà stata completata.

Suggerimento

È possibile eseguire incremental-repo-migration.py da qualsiasi directory a condizione che vengano utilizzate le opzioni -l e -r per specificare le impostazioni locali e remote da utilizzare. Per utilizzare, ad esempio, lo script da qualsiasi directory per migrare una directory locale che si trova nel percorso /tmp/my-repo in un remoto con nome alternativo codecommit:

python incremental-repo-migration.py -l "/tmp/my-repo" -r "codecommit"

È anche possibile utilizzare l'opzione -b per modificare la dimensione predefinita dei batch utilizzata per il push incrementale. Se, ad esempio, si sta regolarmente eseguendo il push di un repository con file binari di grandi dimensioni che cambiano spesso e vengono utilizzati da un percorso con larghezza di banda di rete limitata, è possibile utilizzare l'opzione -b per modificare la dimensione del batch in 500 anziché 1.000. Ad esempio:

python incremental-repo-migration.py -b 500

Ciò consente di eseguire il push incrementale del repository locale in batch di 500 commit. Se si decide di modificare di nuovo la dimensione dei batch durante la migrazione del repository, ad esempio se si decide di ridurre la dimensione dei batch dopo un tentativo non riuscito, ricordarsi di utilizzare l'opzione -c per rimuovere i tag dei batch prima di ripristinare la dimensione dei batch con l'opzione -b:

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

Non utilizzare l'opzione -c se si desidera eseguire di nuovo lo script dopo un esito negativo. L'opzione -c rimuove i tag utilizzati per creare batch di commit. Utilizza l'opzione -c solo se si desidera modificare la dimensione dei batch e riavviare la procedura oppure se si sceglie di non utilizzare più lo script.

Appendice: script di esempio incremental-repo-migration.py

Per praticità, abbiamo sviluppato lo script Python di esempio incremental-repo-migration.py per eseguire il push incrementale di un repository. Questo script è un codice di esempio open source e viene fornito senza alcuna modifica.

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