Memigrasi repositori secara bertahap - AWS CodeCommit

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

Memigrasi repositori secara bertahap

Ketika bermigrasi ke AWS CodeCommit, pertimbangkan untuk mengirim repositori Anda secara bertahap atau dalam beberapa potongan untuk mengurangi kemungkinan masalah jaringan intermiten atau kinerja jaringan yang memburuk yang mengakibatkan seluruh pengiriman gagal. Dengan menggunakan pengiriman tambahan dengan skrip seperti yang disertakan di sini, Anda dapat me-restart migrasi dan mengirim hanya commit yang tidak berhasil pada upaya sebelumnya.

Prosedur dalam topik ini menunjukkan kepada Anda cara membuat dan menjalankan skrip yang memigrasi repositori Anda secara bertahap dan hanya mengirim kembali tahapan yang tidak berhasil sampai migrasi selesai.

Instruksi ini ditulis dengan asumsi bahwa Anda telah menyelesaikan langkah-langkah di Pengaturan dan Buatlah sebuah repositori.

Langkah 0: Menentukan apakah akan bermigrasi secara bertahap

Ada beberapa faktor yang perlu dipertimbangkan untuk menentukan ukuran keseluruhan repositori Anda dan apakah akan dimigrasi secara bertahap. Yang paling jelas adalah ukuran keseluruhan artefak dalam repositori. Faktor-faktor seperti akumulasi riwayat repositori juga dapat berkontribusi terhadap ukuran. Sebuah repositori dengan riwayat tahunan dan banyak cabang bisa memiliki ukuran yang sangat besar, meskipun aset individunya tidak . Ada sejumlah strategi Anda dapat jalankan untuk membuat migrasi repositori ini lebih sederhana dan lebih efisien. Misalnya, Anda dapat menggunakan strategi klon dangkal saat mengkloning repositori dengan riwayat perkembangan yang panjang, atau Anda dapat mematikan kompresi delta untuk file biner besar. Anda dapat meneliti pilihan dengan mengkonsultasikan dokumentasi Git Anda, atau Anda dapat memilih untuk mengatur dan mengkonfigurasi pengiriman tambahan untuk migrasi repositori Anda menggunakan contoh skrip yang disertakan dalam topik ini, incremental-repo-migration.py.

Anda mungkin ingin mengkonfigurasi pengiriman tambahan jika satu atau lebih kondisi berikut ini benar:

  • Repositori yang ingin Anda migrasikan memiliki riwayat lebih dari lima tahun.

  • Koneksi internet Anda tergantung pada gangguan intermiten, paket yang terputus, respons lambat, atau gangguan lain dalam layanan.

  • Ukuran keseluruhan repositori lebih besar dari 2 GB dan Anda berniat untuk memigrasikan seluruh repositori.

  • Repositori berisi artefak besar atau binari yang tidak memampatkan dengan baik, seperti file gambar besar dengan lebih dari lima versi yang dilacak.

  • Anda sebelumnya telah mencoba migrasi ke CodeCommit dan menerima pesan “Kesalahan Layanan Internal”.

Bahkan jika tidak ada kondisi di atas yang benar, Anda masih dapat memilih untuk mengirim secara bertahap.

Langkah 1: Instal prasyarat dan tambahkan repositori sebagai CodeCommit remote

Anda dapat membuat skrip khusus Anda sendiri, yang memiliki prasyarat tersendiri. Jika Anda menggunakan sampel yang disertakan dalam topik ini, Anda harus:

  • Memasang prasyaratnya.

  • Mengkloning repositori ke komputer lokal Anda.

  • Tambahkan CodeCommit repositori sebagai remote untuk repositori yang ingin Anda migrasikan.

Siapkan untuk menjalankan incremental-repo-migration .py
  1. Pada komputer lokal Anda, instal Python 2.6 atau versi yang lebih baru. Untuk informasi selengkapnya dan versi terbaru, lihat situs web Python.

  2. Pada komputer yang sama, instal GitPython, yang merupakan pustaka Python yang digunakan untuk berinteraksi dengan repositori Git. Untuk informasi selengkapnya, lihat dokumentasi GitPython.

  3. Gunakan perintah git clone --mirror untuk mengkloning repositori yang ingin Anda migrasikan ke komputer lokal Anda. Dari terminal (Linux, macOS, atau Unix) atau command prompt (Windows), gunakan perintah git clone --mirror untuk membuat repo lokal untuk repositori, termasuk direktori di mana Anda ingin membuat repo lokal. Misalnya, untuk mengkloning repositori Git bernama MyMigrationRepodengan URL https://example.com/my-repo/ ke direktori bernama my-repo:

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

    Anda akan melihat output yang mirip dengan berikut ini, yang menunjukkan repositori telah dikloning ke repo lokal telanjang bernama 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. Ubah direktori ke repo lokal untuk repositori yang baru saja Anda kloning (misalnya, my-repo). Dari direktori itu, gunakan git remote add DefaultRemoteName RemoteRepositoryURL perintah untuk menambahkan repositori sebagai CodeCommit repositori jarak jauh untuk repo lokal.

    catatan

    Saat mengirim repositori besar, pertimbangkan untuk menggunakan SSH dan bukan HTTPS. Ketika Anda mengirim perubahan besar, sejumlah besar perubahan, atau repositori besar, koneksi HTTPS yang berjalan lama sering dihentikan sebelum waktunya karena masalah jaringan atau pengaturan firewall. Untuk informasi selengkapnya tentang pengaturan CodeCommit SSH, lihat Untuk koneksi SSH di Linux, macOS, atau Unix atauUntuk koneksi SSH pada Windows.

    Misalnya, gunakan perintah berikut untuk menambahkan titik akhir SSH untuk repositori bernama MyDestinationRepo sebagai CodeCommit repositori jarak jauh untuk remote bernama: codecommit

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

    Karena ini adalah klon, nama remote default (origin) sudah digunakan. Anda harus menggunakan nama remote lain. Meskipun contoh menggunakan codecommit, Anda dapat menggunakan nama yang Anda inginkan. Gunakan perintah git remote show untuk meninjau daftar remote yang ditetapkan untuk repo lokal Anda.

  5. Gunakan perintah git remote -v untuk menampilkan pengaturan pengambilan dan pengiriman untuk repo lokal Anda dan pastikan pengaturannya sudah benar. Misalnya:

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

    Jika Anda masih melihat entri pengambilan dan pengiriman untuk repositori remote yang berbeda (misalnya, entri untuk asal), gunakan perintah git remote set-url --delete untuk menghapusnya.

Langkah 2: Buat skrip untuk digunakan untuk migrasi secara bertahap

Langkah-langkah ini ditulis dengan asumsi bahwa Anda menggunakan sontoh skrip incremental-repo-migration.py.

  1. Buka editor teks dan tempel konten contoh skrip ke dalam dokumen kosong.

  2. Simpan dokumen dalam direktori dokumen (bukan direktori kerja dari repo lokal Anda) dan namai incremental-repo-migration.py. Pastikan direktori yang Anda pilih adalah salah satu yang dikonfigurasi di lingkungan lokal atau variabel jalur, sehingga Anda dapat menjalankan skrip Python dari baris perintah atau terminal.

Langkah 3: Jalankan skrip dan migrasi secara bertahap ke CodeCommit

Sekarang setelah Anda membuat incremental-repo-migration.py skrip, Anda dapat menggunakannya untuk memigrasikan repo lokal secara bertahap ke repositori. CodeCommit Secara default, skrip mengirim commit dalam batch 1.000 commit dan mencoba untuk menggunakan pengaturan Git untuk direktori dari yang dijalankan sebagai pengaturan untuk repo lokal dan repositori remote. Anda dapat menggunakan opsi yang disertakan dalam incremental-repo-migration.py untuk mengkonfigurasi pengaturan lainnya, jika perlu.

  1. Dari terminal atau command prompt, ubah direktori ke repo lokal Anda yang ingin Anda migrasikan.

  2. Dari direktori tersebut, jalankan perintah berikut:

    python incremental-repo-migration.py
  3. Skrip berjalan dan menunjukkan kemajuan pada terminal atau command prompt. Beberapa repositori besar lambat menunjukkan kemajuan. Skrip berhenti sebuah pengiriman tunggal gagal tiga kali. Selanjutnya Anda dapat menjalankan kembali skrip, dan mulai dari batch yang gagal. Anda dapat menjalankan kembali skrip sampai semua pengiriman berhasil dan migrasi selesai.

Tip

Anda dapat menjalankan incremental-repo-migration.py dari direktori apapun selama Anda menggunakan pilihan -l dan -r untuk menentukan pengaturan lokal dan remote yang akan digunakan. Misalnya, untuk menggunakan skrip dari direktori apapun untuk memigrasi repo lokal yang terletak di /tmp/my-repo ke remote yang dijuluki codecommit:

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

Anda juga mungkin ingin menggunakan pilihan -b untuk mengubah ukuran batch default yang digunakan ketika melakukan pengiriman secara bertahap. Sebagai contoh, jika Anda secara teratur mengirim repositori dengan file biner yang sangat besar yang sering berubah dan bekerja dari lokasi yang memiliki bandwidth jaringan terbatas, Anda mungkin ingin menggunakan pilihan -b untuk mengubah ukuran batch ke 500 bukan 1.000. Misalnya:

python incremental-repo-migration.py -b 500

Hal ini mengirim repo lokal secara bertahap dalam batch 500 commit. Jika Anda memutuskan untuk mengubah ukuran batch lagi ketika Anda memigrasi repositori (misalnya, jika Anda memutuskan untuk mengurangi ukuran batch setelah gagal melakukan upaya pengiriman), ingat untuk menggunakan pilihan -c untuk menghapus tanda batch sebelum mengatur ulang ukuran batch dengan -b:

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

Jangan gunakan pilihan -c jika Anda ingin menjalankan ulang skrip setelah gagal. Pilihan -c menghapus tanda yang digunakan untuk batch commit. Gunakan pilihan -c hanya jika Anda ingin mengubah ukuran batch dan mulai lagi, atau jika Anda memutuskan tidak lagi ingin menggunakan skrip.

Lampiran: Skrip contoh incremental-repo-migration.py

Untuk kenyamanan Anda, kami telah mengembangkan contoh skrip Python, incremental-repo-migration.py, untuk mengirim repositori secara bertahap. Skrip ini adalah contoh kode sumber terbuka dan disediakan apa adanya.

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