リポジトリの物語

この話は、多くの多くの改訂前に始まりました-その後、SVNリポジトリは初期の状態であり、その存在によってそれを冒secしたバグは1つもありませんでした。 最初のコミット、最初のロールバック、ログビュー-これらはすべてとてもエキサイティングで、とても新しいものでした。 そして、リポジトリは、これらの最初の、そのような快適なステップがその後彼を手術台に導くと仮定できますか?

リポジトリは成長、クレープ、成熟しました。 時間が経つにつれて、私はコミットに慣れ、最初のタグが現れ、ブランチの夢でさえ実現不可能に思えました。 リポジトリは他のSVNリポジトリと知り合いになり、ファイルをいくつかのリポジトリと交換し始めました。 時々、彼は新しい友人から長い間変更を送り出し、その過程で差分の分析を楽しんでいた。

最初の雲が地平線上に現れ始めたのは、リポジトリが開発者の会話でますます馴染みのない単語「Git」と「DVCS」を聞き始めたときです。 彼はリポジトリの友人にこれについて尋ねようとしましたが、彼らは恥ずかしそうに目をそらしただけでした...

時間が経つにつれて、これらの心配はおさまりましたが、人生は以前と同じではなくなりました。 リポジトリは急落し、人生の意味についてもっと考え始めました。 古い結びつきはもはや喜びを与えず、むしろ負担の多い依存関係に変わりました。 マージ中に、悪夢が不溶性の紛争について夢を見るようになりました。

コミットが突然停止したとき、人生はゆっくりと流れました。 不安な予感はすぐに再開しました。 もちろん、リポジトリは、開発者が休暇をとらなければならないという考えで安心し、すぐにすべてが通常のコースに戻りますが、何か悪い感じは去りませんでした。

ある晩、ドアがノックされました。 「最後に、svn commit!」、リポジトリは幸福に考え、長年にわたって巧妙にドアに飛びついた。 「それは誰ですか?」と彼は尋ねたが、それに応えて、彼はドアの外で緊張したスナッフルだけを聞いた。 スプーンの下で卑劣に吸い込まれた。 しばらくして、ドアの後ろで、彼らは言いました:「開いて、svnadmin dump」。 震えている手で、リポジトリはドアを開けました...そして、感覚なしで落ちました。

コミットは私の目の前で次々と競い合い、現れたり消えたりしたので、何度も繰り返しました。 「Svnadminのロード、svnadminのダンプ、svnadminのロード、svnadminのダンプ」-リポジトリは何が起こっているのか理解できず、意識を取り戻したか、再び忘却に失敗しました。 そして、遠くにある小さなちらつきの光だけが希望に影響を与えました...

主人公をしばらく置いて、何が起こったのかを把握しましょう。

開発者の目を通して


賢明な読者は、Repositoryがそれ自身を見つける状況が何らかの形で分散バージョン管理システムに関連していると示唆するかもしれません。 これがまさに起こったことです-開発者はMercurialのすべての利点を吹き込まれ、そこですべてのコードを移行することに決めました。 しかし、SubversionからMercurialへの移行は非常に簡単に行うことができますが、なぜリポジトリを苦しめる必要があったのですか?

事実は、開発者が2つの間違いを犯す前に、今ではすべての栄光を見せているということです。
  1. 最初は、すべてのベストプラクティスに反して、従来のフォルダトランク、ブランチ、タグを作成することなく、リポジトリのルートにコミットし始めました。 その後、これらのフォルダーが作成され、ファイルが転送されましたが、ご存じのとおり、リポジトリからコミットを消去することはありません。 さて、すべてを変更しないままにしておくと、Mercurialに移行するときに、トランクの外側のコミットが消えます。
  2. 私たちが思い出すように、ヒーローのリポジトリは「面倒な依存関係」に陥っています。もちろん、ここでは、svn:externalsについて話しています。 前に、この技術の不整合について話しました。 現在、Mercurialに移行する場合、svn:externalsを移行する簡単な方法はありません。
SubversionからMercurialに履歴全体を正しく転送するために、開発者はSVNリポジトリを変更して上記の問題を取り除くことにしました。

メスの下のリポジトリ


リポジトリを変更するには、まずsvnadminユーティリティが必要です。このユーティリティを使用すると、SVNリポジトリの完全なダンプ、特定のリビジョンまたはリビジョンの範囲のダンプ、および既存のリポジトリへのロールダンプを作成できます。 Windowsユーザーの小さな余談-このユーティリティはTortoiseSVNの一部ではありませんが、svnadminが存在するSlik SVNなどの追加のSubversionクライアントをインストールできます。

リポジトリを変更する一般的な考え方は次のとおりです。「正しい」コミットを変更せずにコピーし、「間違った」コミットを手動で置き換えることにより、リポジトリを最初から再作成する必要があります。 私たちの場合、正確性は、コミットにsvn:externalsの言及が含まれておらず、すべてのファイルがトランクにある状況として理解されます。 再構成後、変更されたコミットの元のコミットの日付と時刻を調整する必要があります。

メソッドの動作を理解するために、実際のリポジトリの変更を検討してください。 上記の問題をすべて含む小さなテストリポジトリを準備しました。 ダンプ(demo_repo.dmp)は、 こちらの記事に記載されている他のダンプと一緒にダウンロードできます。

リポジトリには8つのリビジョンが含まれていますが、それぞれの説明を次に示します。
  1. リポジトリのルートにテキストファイルが追加されました。
  2. 小さなC#HelloWorldプロジェクトがリポジトリのルートに追加されました。
  3. トランク、タグ、ブランチフォルダが追加されました。 ルートからのすべてのファイルはトランクに移動されます。
  4. 「サードパーティ」フォルダーが追加され、外部リポジトリーからサブフォルダーにファイルをダウンロードするためのsvn:externalsプロパティーが設定されました。
  5. 「サードパーティ」フォルダーのsvn:externalsプロパティが削除され、トランクフォルダーに設定されました。
  6. トランクからテキストファイルを削除しました。
  7. トランクフォルダーにはsvn:ignoreプロパティが設定されています。 HelloWorldの一部のファイルを変更しました。
  8. トランクにテキストファイルを追加しました。 HelloWorldのサブフォルダーを削除しました。
明らかに、ここでの「正しい」ものはリビジョン6と8のみです。残りはすべて何らかの方法で修正する必要があります。

リビジョン1、2、3の問題は、ファイルがトランクの外側にあることです。 これを修正するために、それらの前に1つのコミットを挿入し、ブランチ、タグ、トランクフォルダーを作成します。 ファイルがトランクに追加されるように、コミット1および2を変更します。 コミット3は完全に削除されます。

リビジョン4と5はsvn:externalsに関連しています。外部の依存関係を外部リポジトリからロードされたファイルに直接置き換える必要があります。 リビジョン7は明らかに無害ですが、svn:externalsに間接的に関連しています。詳細は後で説明します。

それでは始めましょう。 Windowsのリポジトリを変更しますが、たとえばLinuxの場合とまったく同じ手順を実行することを妨げるものはありません。

SVNを取り除く:外部


すぐにすべてのコミットをトランクに移動して、svn:externalsを削除しないでください。 これらのタスクを順番に完了することをお勧めします。 2番目のタスクから始めます。

まず、手動で置き換える必要がある問題のあるすべてのリビジョンを識別する必要があります。 テキストエディタでリポジトリの完全なダンプを開き( メモ帳++を使用)、「svn:externals」というフレーズの出現を順番に探します。 それらは必要以上に多くなる可能性があります。私たちはそのような発生にのみ興味があります

  K 13
 svn:外観 

そのような場所ごとに、上の「Revision-number:」という行を見つける必要があります。 必要な問題のあるリビジョンの番号が含まれています。



テストリポジトリのダンプの最後に到達すると、4、5、および7の問題リビジョン番号を取得する必要があります。
次に、変更を必要としないリビジョン用の部分ダンプを準備する必要があります。 これを行うには:
  1. リポジトリを完全にローカルフォルダーにロードし、full_repoと呼びます。 これは、次のコマンドライン呼び出しを使用して行われます。

      C:\ Subversion> svnadmin create full_repo
     C:\ Subversion> svnadmin load full_repo <demo_repo.dmp 

  2. 次に、変更する必要のないリポジトリのリビジョン(1-3、6、8)のダンプを準備します。

      C:\ Subversion> svnadmin dump full_repo -r 0:3 --incremental> demo0_3.dmp
     C:\ Subversion> svnadmin dump full_repo -r 6 --incremental> demo6.dmp
     C:\ Subversion> svnadmin dump full_repo -r 8 --incremental> demo8.dmp 

  3. 結果のダンプに文字列「svn:externals」が含まれていないことを確認してください。 今すぐこれを慎重に確認することが重要です。この段階で誤って間違えると、将来の生活が非常に複雑になります。
次に、変更されたリポジトリの作成を開始します。 最初に、最初の3つのリビジョンをロードします(たとえば、result_repoと呼びましょう):

  C:\ Subversion> svnadmin create result_repo
 C:\ Subversion> svnadmin load result_repo <demo0_3.dmp

次に、4番目のコミットを修正する必要があります。 これを行うには、新しいリポジトリをチェックアウトする必要があります。 これには、TortoiseSVNを使用するか、次のようにコマンドラインから作業を続行できます。

  C:\ Subversion> svn co file:/// C:/ Subversion / result_repo result_checkout 

すべてが計画どおりであることを確認します-result_checkoutフォルダーに移動して、3つのリビジョンがロードされていることを確認します。

  C:\ Subversion> cd result_checkout
 C:\ Subversion \ result_checkout> svn log -l 1
 -------------------------------------------------- ----------------------
 r3 | シバエフ|  2010-12-09 23:53:09 +0600(2010年12月9日木曜日)|  1行

トランクに移動しました。
 -------------------------------------------------- ----------------------

ここで、「正しい」4回目のコミットを実行する必要があります。 これを行うには、4番目のリビジョンの元のリポジトリの状態が必要です。 したがって、私たちは彼のためにチェックアウトを行いますが、すぐに改訂番号4になります。

  C:\ Subversion \ result_checkout> cd ..
 C:\ Subversion> svn co file:/// C:/ Subversion / full_repo full_checkout -r 4 

外部リポジトリからファイルがダウンロードされるため、最後のコマンドには少し時間がかかる場合があります。

時計を確認してください。 作業フォルダーは次のようになります。



また、TortoiseSVNのfull_checkoutのログは次のようになります。



ログでは、第4リビジョンで何が変更されたかを確認します。 そして、以下が変更されました-「サードパーティ」フォルダーがトランクに追加されましたが、単純ではありませんが、svn:externalsプロパティが設定されています:

 iTextSharp https://itextsharp.svn.sourceforge.net/svnroot/itextsharp/tags/iTextSharp_5_0_5/iTextSharp/text/xml/simpleparser/ 

したがって、4回目のコミットを修正するには、修正するリポジトリに/ trunk / 3rd party / iTextSharpフォルダーを追加してコミットする必要があります。
  1. result_checkout / trunkに移動します
  2. サードパーティのフォルダーを作成する
  3. full_checkoutからiTextSharpフォルダーをコピーします
  4. .svnサブフォルダーを削除します
  5. result_checkoutリポジトリ内のすべてのコンテンツを含むサードパーティのフォルダーを追加(svn add)
  6. 次のように、元の署名を保持してコミットします。
      C:\ Subversion \ result_checkout> svn commit -m "外部参照を追加しました" 
OK、最初の変更されたコミットがあります! ただし、先に進む前に、そのようなプロセスの過程でエラーが何に満ちているかを考えてみましょう(たとえば、間違ったものをコミットしたり、間違ったダンプをロールした場合)。 リポジトリには8回のコミットではなく2000回のコミットがあり、初めてsvn:externalsがリビジョン番号1200にあると想像してください。 この場合、最初のダンプ(0〜1199のリビジョン)が既に読み込まれているため、チェックアウトには時間がかかります。

エラーの結果は非常に不快なものになる可能性があります。再作成されたリポジトリを簡単に破壊する可能性があるため、最初からやり直す必要があります。 したがって、中間結果のバックアップの問題はすぐに処理する必要があります。 ゼロから復元するのに時間がかかる場合は、少なくともリポジトリ(result_repo)とチェックアウト(result_checkout)を保存する必要があります。

この方法でバックアップを整理します-必要なフォルダーをzipアーカイブに圧縮し、特定のフォルダーにコピーします。 これを行うには、 7-zipアーカイバを使用します。 インストールして、7z.exeへのパスをPATH環境変数に追加します。 これで、次のようにリポジトリの中間状態をバックアップできます。

  C:\ Subversion> 7zバックアップ\ result_repoX.zip result_repo、 
Xはリビジョン番号です(たとえば、今回のケースではX == 4)

中間結果を保存したら、次に進みます。5番を手動でコミットする必要があります。 これを行うには:
  1. full_checkoutをリビジョン番号5に更新します。

      C:\ Subversion \ full_checkout> svn up -r 5 

  2. 5番目のコミットに含まれる変更を確認する
この場合、変更は最小限です-svn:externalsプロパティは「サードパーティ」フォルダーに対して削除され、トランクフォルダーに対して作成されました。 外部リポジトリのアドレス、およびファイルをダウンロードするフォルダは変更されていません。 したがって、新しいリポジトリの場合、変更は必要ありません。何らかの方法でコミットするだけです。 これを行うには、トランクに空のFictiveファイルを作成し、SVNに追加してコミットします。 署名をコミットに変更しないことをお勧めします(理由-後で明らかになります)

5回目のコミットが追加された後、demo6.dmpをロールします。

  C:\ Subversion> svnadmin load result_repo <demo6.dmp 

バックアップを作成し、プログラムの最後の重要なポイントであるコミット7の変更に進みます。 full_checkoutリポジトリを7番目のリビジョンに更新し、変更のリストを確認します。



このコミットを変更する必要がある理由は、svn:externalsに関して変更が加えられていないため、すぐには明らかになりません。 トランクフォルダーのsvn:ignoreプロパティが変更されました。これが理由でしょうか。 そうです。 実際には、トランクフォルダープロパティの完全な説明は、svn:externalsとともにリビジョン7のSVNダンプに格納されています。



だから、ポイントに。 7回目のコミットを行うには2つの方法があります。

1つ目は、元のリポジトリから7番目のリビジョンのダンプを取得し、svn:externalsをtrunkフォルダープロパティから削除します(テキストエディターで開いて、削除し、プロパティ「Prop-content-length」および「または、「svnadmin setrevprop」コマンドを使用して、この「正しい」ダンプを新しいリポジトリにロールします。 この場合、このアプローチが最適なソリューションです。

ただし、ほとんどの場合、実際のリポジトリに使用する必要があるため、2番目の方法を検討する方が興味深いです。 この方法は、TortoiseSVNのスキンを試して、7のすべての変更を自分でコミットすることで構成されます。

SVNクライアントのアクションをどれだけ慎重に繰り返すかは、忍耐と注意力に完全に依存します。 各変更を個別に処理しないでください。主なことは、元のリポジトリと新しいリポジトリのファイルセットが一致することを確認することです。 唯一のことは、ファイル/フォルダーをSVNに移動することは、移動する前にオブジェクトの履歴を失わないように正確に繰り返すことが望ましいということです。 ところで、この段階で、トランクで以前に作成した仮想ファイルを削除することもできます。

すべての変更が行われた後、影響を受けるプロジェクトがまだコンパイルされ、テストに合格していることを確認する価値があります。 ただし、Subversionクライアントとしての動作に完全に自信がある場合は、確認できません。

次に、変更をコミットしてフィニッシュラインに進みます。 最後のダンプdemo8.dmpをロールします。 result_checkoutとfull_checkoutの両方の作業コピーを最新のリビジョンに更新し、それらを比較します。 すべてが正しく行われた場合、ファイルのセットで完全に同一である必要があります。

変更されたリポジトリのダンプを取得します。

  C:\ Subversion> svnadmin dump result_repo> without_externals.dmp 

このダンプには外部の依存関係はまったくありませんが、一部の変更は引き続き必要です。 事実、手動で行われたコミットの場合、新しいコミット時間がダンプに書き込まれ、誤ったユーザー名(通常はWindowsのオペレーティングシステムの名前と同じ)が書き込まれます。 修正する必要があります。

テキストエディターで元のダンプと新しいダンプを開き、変更されたリビジョン(この例では4、5、7)を順番に確認します。 次のようになります。



次に、新しいダンプの対応する日付を元の日付に慎重に置き換える必要があります。 これを行うには、再びsvnadminユーティリティとその「setrevprop」オプションを使用できますが、私の意見では、テキストエディターで手でこれを行う方が高速です。

日付の置換は無害な操作ですが、コミットの作成者を置換するには追加の作業が必要です。 ご覧のとおり、「Prop-content-length」プロパティと「Content-length」プロパティの値、および初期ダンプと更新されたダンプの作成者の名前の上にある数字は一致しません。 これは、著者の名前の長さが元の名前と異なるためです。 したがって、まずコミットの作成者を変更し、次に対応する値を更新します。 Notepad ++は、すべてが正しく行われたことを確認する便利な方法を提供します。



変更されたすべてのコミットのプロパティが修正された後、svn:externals completeを取り除く段階を検討できます。 ただし、その後、結果のダンプがローカルリポジトリに完全に正しくロードされていることを確認することをお勧めします。

  C:\ Subversion> svnadmin create test_repo
 C:\ Subversion> svnadmin load test_repo <without_externals.dmp 

このチェック中に、リポジトリの変更中に発生したエラーが自動的に検出される場合があります。 この場合、ダンプのロードはエラーの場所で中断され、その理由がわかります。 これは通常、ファイルの内容を台無しにしたり、ファイルを更新しない場合に発生します。 エラーメッセージは、チェックサムが一致しないことを通知します。

すべてのコミットをトランクに転送する


前の手順で習得したスキルを使用すると、すべてのコミットをトランクに転送するのは簡単です。 したがって、最初の2つのコミットがリポジトリのルートに対して行われ、3番目のコミットではトランク、タグ、ブランチフォルダが作成され、すべてのファイルがそこに転送されるリポジトリwithout_externals.dmpがあります。

行動計画:
  1. クリーンなリポジトリを作成し、空のブランチ、タグ、トランクフォルダーをコミットします
  2. ダンプwithout_externals.dmpを2つに分割します。0から2までのリビジョンと4から8までのリビジョンです。 コミット#3は破棄されます!
  3. 最初のダンプ(0から2リビジョン)では、ファイル内のすべてのパスにプレフィックス「trunk /」を追加します
  4. 両方のダンプを新しいリポジトリに連続的にロールします
  5. エラーがないことを確認し、結果のリポジトリの完全なダンプを作成します
  6. 最初のコミットの時間と作成者を変更します
行こう クリーンなリポジトリ(final_repoフォルダー)を作成し、チェックアウトします(final_checkout)。 3つのフォルダーを作成し、リポジトリーに追加してコミットします。

  C:\ Subversion> svnadmin create final_repo
 C:\ Subversion> svn co file:/// C:/ Subversion / final_repo final_checkout
 C:\ Subversion> mkdirブランチタグtrunk
 C:\ Subversion> svn add branch tags trunk
 C:\ Subversion> svn commit -m "準備されたリポジトリ構造" 

不要なコミットを排除するために、外部依存関係のないリポジトリのダンプを2つに分割しました。

  C:\ Subversion> svnadmin create without_externals_repo
 C:\ Subversion> svnadmin load without_externals_repo <without_externals.dmp
 C:\ Subversion> svnadmin dump without_externals_repo -r 0:2 --incremental> final0_2.dmp
 C:\ Subversion> svnadmin dump without_externals_repo -r 4:8 --incremental> final4_8.dmp 

ダンプfinal0_2.dmpを開き、すべての行「-path:」を「-path:trunk /」に置き換えます。 重要-パスは「Node-path」および「Node-copyfrom-path」プロパティで使用できるため、「Node-path:」だけでなく、まさにこれを置き換えます。



final0_2.dmpとfinal4_8.dmpを新しいリポジトリにロールします。

  C:\ Subversion> svnadmin load final_repo <final0_2.dmp
 C:\ Subversion> svnadmin load final_repo <final4_8.dmp 

final_checkoutのSVNログを確認してください。すべてのコミットは、トランク内のファイルに対してのみ動作します。 更新されたダンプを取得します。

  C:\ Subversion> svnadmin dump final_repo> final.dmp 

最初のリビジョンの時刻と作成者、および0番目のリビジョンの時刻を変更します。 without_externals.dmpの0番目のリビジョンの時間を基準とし、それを新しいダンプの0番目のリビジョンに使用してから、数秒を追加して1番目のリビジョンに設定します。
結果のダンプが正しいことを確認して、リポジトリにロードします。

  C:\ Subversion> svnadmin create test_final_repo
 C:\ Subversion> svnadmin load test_final_repo <final.dmp 

ダウンロード中にチェックサムの不一致に関するエラーが発生した場合、「-path:」行がファイル内のどこかにある可能性が高く、誤って置換しました(実際のリポジトリでは、置換する必要のあるすべての場所を目で見ることはできませんたくさん)。 さらに、文字列はリビジョンプロパティとまったく同じ形式(たとえば、「Node-path:...」)で発生するため、置換テンプレートを強化しても役に立ちません。

この例では、エラーは発生しません。つまり、Mercurialへの移行に適した完全なリポジトリが得られました。

まとめると


SVNリポジトリを変更するために使用される多くの手法を検討しました。 ハイライト:
  1. svnadminユーティリティがメインツールです。 ダンプをリポジトリにアップロードし、目的のリビジョンのダンプを取得できます。
  2. 変更されたリポジトリの「手動」コミットと既存のダンプのロードを交互に行うことができます。 典型的なトリックは、不要なコミットをリポジトリからスローし、新しいリポジトリでそれらを置き換える(または単にスキップする)ことです。
  3. できるだけ頻繁に中間結果のバックアップを作成します。
  4. すべての典型的な操作(ダンプのロード、ダンプの作成、チェックアウト、バックアップ)には、batまたはbashスクリプトを使用すると便利です。
  5. テキストエディターでSVNダンプを変更する場合-数値の更新を忘れないでください-対応するコンテンツのサイズ(バイト単位)。
  6. SVNダンプが大きい(> 700 MB)場合、Windowsのほとんどのテキストエディターは正常に開くことができないため、編集に問題があります。 メモ帳++も例外ではありません。 私は多くの異なるオプションを試しましたが、 EmEditorのみが役に立ちました。少量のRAMを搭載したマシンでもうまく動作します。
  7. 変更後、結果のダンプからリポジトリをロードできない場合、オフセットを使用して何かを台無しにしたり、ファイルの更新を忘れたり、ファイルの内容を台無しにしたりします。
  8. 特定のコミットで変更を自分で繰り返す必要がある場合、フォルダー内のすべての.svnサブフォルダーを再帰的に削除すると便利です。 これを行うには、現在のディレクトリ内のすべてのサブフォルダーを再帰的に削除するスクリプト(Windows用)を使用できます。

      FOR / F "tokens = *" %% G IN( 'DIR / B / AD / S * .svn *')DO RMDIR / S / Q "%% G" 

  9. ダンプのリビジョンの説明のバイト数に影響するため、署名を変更してコミットする必要はありません。 このため、コミットの署名の長さの違いも考慮する必要があるため、コミットの作成者の名前を更新するのが少し難しくなります。

おわりに


リポジトリが実現しました。 コミット、ログ、svnadmin-すべてが彼のかつて明るい頭で混同しました。 麻酔のベールはまだ完全に消散していませんが、リポジトリはすでにその内部の何かが変化したと感じています。

目を開けると、リポジトリは最初に周りを見回しました-部屋は広々としていましたが、インテリアはまったく馴染みがありませんでした。 コミット付きのオークシフォンの代わりに、小さなものがあり、壁にタグの付いた箱の代わりに、「6fc1d7a7ae346b0a09be5647e94c1561764b8619」のような不明瞭な線の付いた小さなステッカーが貼られていました。 しかし、驚くべきことに、Feng Shuiによると、リポジトリは部屋の全体的な装飾が好きで、すべてが気持ちよく装飾されていました。

リポジトリには、ドアベルの鳴り方に本当に慣れる時間がありませんでした。 「こんにちは! コミットメントの配信”-碑文のついた帽子の背の低い男” Mercurialがばらばらになった。 リポジトリには口を開く時間がありませんでした。見知らぬ人が、表紙に「hg commit」という碑文が書かれた2冊の小冊子を手渡すと、そのようになりました。

リポジトリは、彼がすでにこれを経験したことを思い出しました。 むかしむかし、最初に見知らぬ人が最初の「svn commit」を自分のアパートにドラッグしました。 歴史は今繰り返されていますか? リポジトリは喜びで息をのむようなものでした-蓄積された経験と知識をすべて持って子供時代に戻る幸運な人はほとんどいませんでした、そしてそれはそれで起こったようです! そして、彼は無駄にサンタクロースを信じなくなりました-これは奇跡である可能性があります-それは彼の仕事でした...

リポジトリは何が先にあるかを知りませんでした。 彼は一つのことを明確に理解しました-彼は新しい人生を始めました-冒険で、面白くて、予測不可能でした。 惑星マーキュリアルでの生活。

Source: https://habr.com/ru/post/J109970/


All Articles