Ein Repository inkrementell migrieren - AWS CodeCommit

AWS CodeCommit ist für Neukunden nicht mehr verfügbar. Bestandskunden von AWS CodeCommit können den Service weiterhin wie gewohnt nutzen. Erfahren Sie mehr“

Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.

Ein Repository inkrementell migrieren

Bei der Migration zu AWS CodeCommit sollten Sie eine inkrementelle oder stufenweise Push-Übertragung des Repositorys erwägen. Auf diese Weise sinkt das Risiko, dass aufgrund einer unterbrochenen oder schlechten Netzwerkverbindung die gesamte Push-Übertragung fehlschlägt. Indem Sie inkrementelle Push-Übertragungen mit einem Skript wie dem hier enthaltenen ausführen, können Sie die Migration erneut starten und nur die Commits, die im vorherigen Versuch nicht erfolgreich übermittelt werden konnten, erneut per Push übertragen.

Die Verfahren in diesem Thema veranschaulichen, wie Sie ein Skript erstellen und ausführen, mit dem eine inkrementelle Migration des Repositorys durchgeführt wird und nur die inkrementellen Schritte, die zuvor fehlgeschlagen sind, erneut ausgeführt werden, bis die Migration vollständig abgeschlossen ist.

Für diese Anweisungen wird davon ausgegangen, dass Sie bereits die Schritte unter Einrichtung und Erstellen eines -Repositorys ausgeführt haben.

Schritt 0: Stellen Sie fest, ob eine inkrementelle Migration erfolgen soll

Bei der Ermittlung der Repository-Gesamtgröße und der Bestimmung, ob eine inkrementelle Migration sinnvoll ist, sind zahlreiche Faktoren zu berücksichtigen. Am offensichtlichsten ist die Gesamtgröße der Artefakte im Repository. Aber auch andere Faktoren wie z. B. der gesamte Repository-Verlauf tragen zur Größe bei. Ein Repository mit jahrelangem Verlauf und Branches kann sehr umfangreich sein, auch wenn die einzelnen Komponenten nicht groß sind. Es gibt viele Strategien, die eine Migration dieser Repositorys einfacher und effizienter gestalten können. Sie können beispielsweise einen flachen Klon beim Klonen eines Repositorys mit einem umfangreichen Entwicklungsverlauf einsetzen oder die Deltakomprimierung bei großen Binärdateien deaktivieren. Sie können diese Optionen in der Git-Dokumentation prüfen oder Sie können inkrementelle Push-Übertragungen einrichten und konfigurieren, um Ihr Repository mit dem hier enthaltenen Skriptbeispiel incremental-repo-migration.py zu migrieren.

Sie sollten die Konfiguration inkrementeller Push-Übertragungen in Betracht ziehen, wenn eine der folgenden Bedingungen zutrifft:

  • Das zu migrierende Repository weist einen Verlauf von mehr als fünf Jahren auf.

  • Ihre Internetverbindung weist Probleme wie gelegentliche Ausfälle, nicht übertragene Pakete, langsame Geschwindigkeit oder andere Leistungsstörungen auf.

  • Die Gesamtgröße des zu migrierenden Repositorys übersteigt 2 GB.

  • Das Repository enthält große Artefakte oder Binärdateien, die sich nicht gut komprimieren lassen (z. B. große Bilddateien mit mehr als fünf verfolgten Versionen).

  • Sie haben bereits versucht, zu migrieren, CodeCommit und die Meldung „Internal Service Error“ erhalten.

Auch wenn keine der oben genannten Bedingungen zutrifft, können Sie sich für eine inkrementelle Push-Übertragung entscheiden.

Schritt 1: Installieren Sie die erforderlichen Komponenten und fügen Sie das CodeCommit Repository als Remote-Repository hinzu

Sie können ein eigenes Skript mit eigenen Voraussetzungen erstellen. Wenn Sie das Beispiel in diesem Thema verwenden können, müssen Sie Folgendes beachten:

  • Installieren Sie die Voraussetzungen.

  • Klonen Sie das Repository auf Ihren lokalen Computer.

  • Fügen Sie das CodeCommit Repository als Remote-Repository für das Repository hinzu, das Sie migrieren möchten.

Für die Ausführung von incremental-repo-migration .py einrichten
  1. Installieren Sie Python 2.6 oder eine neuere Version auf Ihrem lokalen Computer. Weitere Informationen und die neuesten Versionen finden Sie auf der Python-Website.

  2. Installieren Sie auf demselben Computer. Dabei handelt es sich um eine Python-Bibliothek GitPython, die für die Interaktion mit Git-Repositorys verwendet wird. Weitere Informationen finden Sie in der GitPython Dokumentation.

  3. Verwenden Sie den Befehl git clone --mirror, um das Repository, das auf Ihren lokalen Computer migriert werden soll, zu klonen. Verwenden Sie im Terminal (Linux, macOS oder Unix) oder in der Befehlszeile (Windows) den git clone --mirror Befehl, um ein lokales Repository für das Repository zu erstellen, einschließlich des Verzeichnisses, in dem Sie das lokale Repository erstellen möchten. Um beispielsweise ein Git-Repository MyMigrationRepomit der URL https://example.com/my-repo/ in ein Verzeichnis namens my-repo zu klonen:

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

    Die Ausgabe sieht folgendermaßen aus und gibt an, dass das Repository in ein leeres lokales Repository mit der Bezeichnung "my-repo" geklont wurde:

    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. Ändern Sie die Verzeichnisverweise auf das lokale Repository entsprechend in das gerade geklonte Repository (z. B. my-repo). Fügen Sie für dieses Verzeichnis mit dem Befehl git remote add DefaultRemoteName RemoteRepositoryURL das CodeCommit -Repository als Remote-Repository für das lokale Repository hinzu.

    Anmerkung

    Bei der Push-Übertragung von umfangreichen Repositorys sollten Sie anstelle von HTTPS die Verwendung von SSH in Betracht ziehen. Wird eine umfassende Änderung, eine große Anzahl an Änderungen oder ein großes Repository per Push übertragen, werden HTTPS-Langzeitverbindungen häufig aufgrund von Problemen mit der Netzwerkverbindung oder den Firewalleinstellungen vorzeitig beendet. Weitere Informationen zur Einrichtung von SSH CodeCommit finden Sie unter oder. Für SSH-Verbindungen unter Linux, macOS oder Unix Für SSH-Verbindungen unter Windows

    Verwenden Sie beispielsweise den folgenden Befehl, um den SSH-Endpunkt für ein CodeCommit Repository hinzuzufügen, das MyDestinationRepo als Remote-Repository für das Remote-Repository benannt ist: codecommit

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

    Da es sich um einen Klon handelt, ist der Standardname für das Remote-Repository (origin) bereits in Verwendung. Sie müssen daher einen anderen Namen festlegen. Obwohl im Beispiel codecommit verwendet wird, können Sie den Namen beliebig auswählen. Überprüfen Sie mit dem Befehl git remote show die Liste der Remote-Repositorys, die für Ihr lokales Repository eingerichtet sind.

  5. Zeigen Sie mit dem Befehl git remote -v die Fetch- und Push-Einstellungen für das lokale Repository an und überprüfen Sie, ob die Einstellungen korrekt sind. Beispiele:

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

    Falls noch Fetch- und Push-Einträge für ein anderes Remote-Repository angezeigt werden (z. B. Einträge für "origin"), entfernen Sie diese mit dem Befehl git remote set-url --delete.

Schritt 2: Erstellen Sie das Skript, das für die inkrementelle Migration verwendet werden soll

Bei diesen Schritten wird davon ausgegangen, dass Sie das Beispiel-Skript incremental-repo-migration.py verwenden.

  1. Öffnen Sie einen Texteditor und fügen Sie den Inhalt aus dem Skriptbeispiel in ein leeres Dokument ein.

  2. Speichern Sie das Dokument in einem Dokumentverzeichnis (nicht im Arbeitsverzeichnis des lokalen Repositorys) unter dem Namen incremental-repo-migration.py. Achten Sie darauf, dass das gewählte Verzeichnis für Ihre lokale Umgebung oder Pfadvariablen konfiguriert ist, damit Sie das Python-Skript über ein Befehlszeilen- oder Teminalfenster ausführen können.

Schritt 3: Führen Sie das Skript aus und migrieren Sie schrittweise zu CodeCommit

Nachdem Sie Ihr incremental-repo-migration.py Skript erstellt haben, können Sie es verwenden, um ein lokales Repo inkrementell in ein Repository zu migrieren. CodeCommit Standardmäßig überträgt das Skript Stapel von je 1 000 Commits. Zudem nutzt das Skript die Git-Einstellungen des Verzeichnisses, in dem es ausgeführt wird, als Einstellungen für das lokale und das Remote-Repository. Mit den in incremental-repo-migration.py enthaltenen Optionen können Sie bei Bedarf andere Einstellungen konfigurieren.

  1. Ändern Sie über das Terminal- oder Befehlszeilenfenster die Verzeichnisverweise auf das zu migrierende lokale Repository.

  2. Führen Sie aus dem Verzeichnis den folgenden Befehl aus:

    python incremental-repo-migration.py
  3. Das Skript wird ausgeführt, der Fortschritt wird im Terminal- oder Befehlszeilenfenster angezeigt. Bei sehr umfangreichen Repositorys ist nur ein langsamer Fortschritt sichtbar. Falls ein einzelner Push-Versuch dreimal fehlschlägt, wird das Skript angehalten. Sie können das Skript dann ab dem fehlgeschlagenen Stapel erneut ausführen. Das Skript kann so lange ausgeführt werden, bis alle Push-Übertragungen erfolgreich verlaufen sind und die Migration abgeschlossen ist.

Tipp

Sie können incremental-repo-migration.py aus jedem Verzeichnis ausführen, sofern Sie die zu verwendenden Einstellungen für das lokale und das Remote-Repository mithilfe der Optionen -l und -r festgelegt haben. Um beispielsweise das Skript von einem beliebigen Verzeichnis aus zu verwenden, um das lokale Repository unter /tmp/my-repo in ein externes Repository namens codecommit zu migrieren, schreiben Sie:

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

Sie können auch die Option -b verwenden, um die Standard-Batch-Größe für den inkrementellen Push zu ändern. Wenn Sie beispielsweise regelmäßig ein Repository mit sehr umfangreichen Binärdateien, an denen häufig Änderungen vorgenommen werden, per Push über eine langsame Netzwerkverbindung übertragen müssen, können Sie die Stapelgröße mithilfe der Option -b von 1 000 auf 500 verringern. Beispiele:

python incremental-repo-migration.py -b 500

Auf diese Weise wird das lokale Repository inkrementell in Stapeln zu je 500 Commits per Push übertragen. Falls Sie die Stapelgröße bei der Repository-Migration erneut ändern möchten (z. B. um die Stapelgröße nach einem fehlgeschlagenen Versuch zu verringern), müssen Sie die Stapel-Tags erst mit der Option -c entfernen, bevor Sie die Stapelgröße mit -b ändern:

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

Verwenden Sie nicht die Option -c, wenn Sie das Skript nach einem Fehlschlag erneut ausführen möchten. Die Option -c löscht die Tags, mit denen die Commits in Stapeln zusammengefasst werden. Nutzen Sie daher die Option -c nur, wenn Sie die Stapelgröße ändern und neu starten möchten oder wenn Sie entschieden haben, das Skript nicht weiter zu verwenden.

Anhang: Beispielskript incremental-repo-migration.py

Wir haben für Sie das Python-Skriptbeispiel incremental-repo-migration.py für eine einfache inkrementelle Push-Übertragung von Repositorys entwickelt. Dieses Skript ist ein Open Source-Codebeispiel und wird wie gesehen bereitgestellt.

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