Skip to main content

ベスト・プラクティス

以下の場合に Git プロジェクトを復元する CODESYS プロジェクトの編集中にクラッシュした

操作によって異なる CODESYS 以前にクラッシュしてしまった場合は、次の方法で復旧できます。

  • 通常、クラッシュ後にプロジェクトを再度開くことができます。 CODESYS Git 内部に保存された最後のプロジェクトの状態を復元し、プロジェクトの内容を Git プロジェクトストレージに書き換えます。この「フェイルセーブ」メカニズムは、プロジェクトを「マスター」データソースとして扱います。マージ操作中にプロジェクトがクラッシュした場合は、再度開いた後に作業を続ける直前に保存せず、まずすべての変更を破棄してください (git reset--hard)。そのためには、以下を使用できます すべての変更を破棄する のコマンド ステータスとステージング 表示。

  • クラッシュ後にプロジェクトを正常に開くことができなくなった場合 (たとえば、完全に互換性のないファイルがプロジェクト ストレージに残ってしまったため)、次のリカバリ オプションがあります。 リポジトリからプロジェクトを完全に再ロードします。詳細については、以下を参照してください。 リポジトリからプロジェクトを再構築

からのプロジェクトの転送 CODESYS SVNCODESYS Git

プロジェクトを GIT に正常に転送した後は、必要に応じて引き続き SVN リポジトリにアクセスできるように、SVN リポジトリを削除しないことを強くお勧めします。

ヒント

CODESYS Git バージョン 1.4.0.0 以降では、スクリプトインターフェイスを使用してプロジェクトを転送できるようになります CODESYS SVNCODESYS Git

重要

Git コマンド git-svn 転送には使用できません CODESYSからのプロジェクト CODESYS SVNCODESYS Git なぜなら CODESYS Git を使用する .json のバイナリファイル形式とは対照的なファイル形式 CODESYS SVN

. リポジトリ構造の違いによるコメントと推奨事項 CODESYS SVN そして CODESYS Git
  • CODESYS GitGit リポジトリが管理できるのは 1 つだけです。 CODESYS プロジェクト。SVN リポジトリには複数を含めることができるからです CODESYS プロジェクトを SVN リポジトリに転送するときは、SVN リポジトリ内のプロジェクトごとに個別の Git リポジトリを作成する必要があります。 CODESYS Git

  • SVN リポジトリの個々のプロジェクトを、次のリポジトリに転送することをお勧めします。 CODESYS Git プロジェクト単位で。

  • SVN リポジトリを複数の SVN リポジトリに分割すると、後で問題が発生する可能性があるため、お勧めしません。

  • ブランチとタグは SVN と GIT の両方に存在しますが、これらの 2 つのシステムでは設計が異なります。との移管です git-svn このコマンドは次の場所では使用できないため、コマンドは使用できません。 CODESYS Git (上記参照)。

    そのため、SVN トランクを Git ブランチ (デフォルトでは「Master」または「Main」) に転送することに限定しています。必要に応じて SVN タグを Git タグとして手動で追加できます

この手順では、最新バージョンの CODESYS プロジェクトは最初にチェックアウトされます。 CODESYS SVN。すると SVN への接続が切断され、Git リポジトリが CODESYS Git がプロジェクト用に初期化されます。この手順の短所は、最新の SVN リビジョンのみが Git に転送されるため、プロジェクトの履歴が失われることです

必要条件: CODESYS SVNCODESYS Git にインストールされています CODESYSツールカスタマイズ コマンド、 コミット完了 コマンドがに追加されました Git インテグレーション のコマンドカテゴリ Git メニュー。

  1. を開きます CODESYS SVN リポジトリに保存したプロジェクトです。

  2. クリック プロジェクトSVNチェックアウト

    プロジェクトの最新リビジョンがチェックアウトされました。

  3. クリック [プロジェクト]SVNプロジェクトを SVN から切断

  4. コンピューターのローカルファイルディレクトリに新しい空のフォルダーを作成します。

  5. CODESYS、クリック GitGit リポジトリを初期化 そして、前のステップで作成した空のフォルダをダイアログで選択します。

  6. クリック Gitコミット完了

    これで、プロジェクトはローカルの Git リポジトリに保存され、必要に応じてリモートリポジトリにプッシュできます。

この手順では、すべての SVN リビジョンが 1 回の Git コミットで手動で転送されます。

必要条件: CODESYS SVNCODESYS Git にインストールされています CODESYSツールカスタマイズ コマンド、 コミット完了 コマンドがに追加されました Git インテグレーション のコマンドカテゴリ Git メニュー。

  1. に新しい空のプロジェクトを作成 CODESYS そして、例えば以下のように名前を付けます。 Main Project

  2. クリック GitGit リポジトリを初期化

  3. クリック Gitコミット完了

  4. クリック Gitコミット 空のコミットを実行する。 段階的な変更をコミットする ダイアログで [] を選択します。 空のコミットを許可 オプション。

  5. このプロジェクトを閉じます。

  6. クリック [プロジェクト]SVNチェックアウト SVN に保存されているプロジェクトの希望のリビジョンをチェックアウトします。

  7. クリック [プロジェクト]SVNプロジェクトを SVN から切断。表示されるダイアログでは、デフォルトのオプションをそのまま使用してをクリックします OK

  8. プロジェクトを保存します。

  9. クリック GitGit リポジトリを初期化 そして、ファイルディレクトリ内の空のフォルダを選択します (例: Temp)。

  10. クリック Gitコミット完了

  11. プロジェクトを保存して閉じます。

    ザ・ *.project このプロジェクトのファイルは削除できます。Git リポジトリ Temp プロジェクトの削除はいかなる場合でも行わないでください。

  12. を開きます Main Project プロジェクト再び。

  13. クリック Gitリモコン を開くには リモコン 見る。

  14. の中に リモコン 表示、クリック [追加]新しいリモコンを追加 ダイアログで Git リポジトリの URL を指定 Temp そして、たとえば次のように指定します。 Temp_Remote として エイリアス名

  15. この追加されたリモコンを選択してクリック フェッチ

  16. クリック Gitブランチ を開くには Git ブランチ 表示。

  17. のマスター/メインブランチを選択してください Main、クリック トラックブランチリモートブランチを追跡 ダイアログで、のマスター/メインブランチを選択します Temp_Remote ステップ 14 で追加したリモート。

  18. Git リポジトリのマスター/メインブランチでプルを実行する メイン クリックして オプション付きプルGit ブランチ の表示と選択 コンフリクトには「相手のもの」を使う オプションとしては マージコンフリクトストラテジーGit プルマスター ダイアログ。のデフォルトオプション 早送り戦略 変更すべきではありません。

  19. 今すぐクリック Git -→ コミット完了. の中の 段階的変更と非段階的変更をコミットする ダイアログで [...] を選択します。 コミットを修正 オプション。

    このガイドの冒頭でチェックアウトしたSVNプロジェクトのリビジョンは、現在Gitリポジトリに保存されています。 メイン コミットとして。

  20. の中に リモコン 表示、選択 Temp リモートとクリック 削除

  21. を保存 Main Project 投影して閉じる。

  22. Git リポジトリを削除する Temp ファイルディレクトリから。

  23. SVN プロジェクトの別のリビジョンを Git リポジトリに転送する場合は、ステップ 6 の指示に従って、SVN プロジェクトの次のリビジョンを選択してください。

スクリプトを使用して SVN プロジェクトを Git に転送する

要件: CODESYS Git バージョン 1.4.0.0 以上

転送用の次のスクリプトテンプレートが以下に表示されます。

1。からプロジェクトを転送するためのスクリプト CODESYS SVNCODESYS Git

2。転送中に SVN リビジョンごとに対応する Git コミットが作成されたかどうかを確認するスクリプト

. [メモ]
  • SVN リポジトリは変更されません。

  • どちらのスクリプトも、それぞれの要件に合わせて調整する必要があるテンプレートとして作成されています。

SVN プロジェクトの転送用スクリプト

このスクリプトは、のトランクのすべての SVN リビジョンを転送します CODESYS SVN プロジェクトを a の「マスター」ブランチに送ります。 CODESYS Git プロジェクト。

import os
import shutil
import logging


""" --- inputs ---
"""
# URL of the SVN project you want to migrate
# only migrate a single project at a time if you have multiple in your SVN repo
SVN_REPO_URL = 'svn://your.svnserver.com/trunk/CodesysProjectNr1'
# Starts migration at this svn revision
STARTING_REVISION = 0
# Directory of the new project with the git repository
NEW_PROJECT_DIR = 'C:/CodesysProjects/CodesysProjectNr1'
# Name of the new project
NEW_PROJECT_NAME = 'CodesysProjectNr1'
# Is the project a library (otherwise will be checked out as a project)
IS_LIBRARY = False
# Path to the local git repository of the new project
NEW_PROJECT_REPO_DIR = 'C:/CodesysProjects/CodesysProjectNr1/repo'

# Path to an EMPTY temporary folder (doesn't have to exist) that can be deleted
TEMP_PATH = 'C:/CodesysProjects/CodesysProjectNr1/temp'

# Author of the git commits
AUTHOR_NAME = 'Author Name'
# E-mail address of the git commits
AUTHOR_EMAIL = 'Author.Name@e-mail.com'

# Name of the branch that should contain the history
# Can't be named 'master'
TARGET_BRANCH_NAME = 'develop'
# Initial commit messages on the master branch and the target branch
IC_MSG_MASTER = 'master: Initial commit.'
IC_MSG_TARGET_BRANCH = 'develop: Initial commit.'

# Removes the newly created project and its repository if the migration fails
REMOVE_MAIN_PROJECT_ON_FAILURE = True


def get_git_commit_msg(svn_log):
    """ Returns the commit messages that will be seen in the resulting git history
        Can be customized to ones demands.
        *svn_log* see: https://content.helpme-codesys.com/en/ScriptingEngine/ScriptSubversion.html#ScriptSubversion.LogInfo
    """
    return 'SVN revision %d: %s' % (svn_log.revision, svn_log.message)


""" --- migration script ---
"""
# Type extension of the project (.project / .library)
PROJECT_FILE_TYPE_EXTENSION = '.library' if IS_LIBRARY else '.project'
# Path to your new Git project
NEW_MAIN_PROJECT_PATH = os.path.join(NEW_PROJECT_DIR, NEW_PROJECT_NAME + PROJECT_FILE_TYPE_EXTENSION)
# Name of the temporarily added remote
TEMP_REMOTE_NAME = 'remote1'
# Temporary commit message will be overwritten/amended (put what u like)
TEMP_COMMIT_MSG = 'TEMP COMMIT: This will be overwritten'


def create_git_project():
    """ 1. Creates a new project at *NEW_MAIN_PROJECT_PATH*
        2. Initializes it in git with the repository at *NEW_PROJECT_REPO_DIR*
        3. Makes an initial commit with the message *IC_MSG_MASTER*
        4. Creates and switches to the target branch *TARGET_BRANCH_NAME*
        5. Makes an initial commit on the target branch with the message *IC_MSG_TARGET_BRANCH*
        6. Saves and closes the project
    """
    git_project = projects.create(NEW_MAIN_PROJECT_PATH)
    git_project.git.init(NEW_PROJECT_REPO_DIR)
    git_project.git.commit_complete(IC_MSG_MASTER, AUTHOR_NAME, AUTHOR_EMAIL)

    git_project, git_branch = git_project.git.branch_copy(TARGET_BRANCH_NAME, checkout=True)
    git_project.git.commit_complete(IC_MSG_TARGET_BRANCH, AUTHOR_NAME, AUTHOR_EMAIL, bAllowEmptyCommits=True)

    git_project.save()
    git_project.close()


def remove_all_children(project):
    """ Removes all objects that can be removed from the project *project*
    """
    children = project.get_children(recursive=True)
    for child in children:
        try:
            child.remove()
        except Exception as ex:
            if not 'Object reference not set to an instance of an object.' == str(ex):
                logging.error(ex)


def create_git_repo_from_svn_revision(svn_log):
    """ 1. Checks out a project from the SVN server *SVN_REPO_URL*
        2. Disconnects it from SVN
        3. Initiates it in git with the repository at *repo_dir*
        4. Makes a commit with the message *commit_message*
            4.1 See "get_git_commit_msg" to customize the commit message
        5. Saves and closes the project
        6. Returns the path of the project and path of the repository
    """
    project_path = os.path.join(TEMP_PATH, 'revision_' + str(svn_log.revision) + PROJECT_FILE_TYPE_EXTENSION)
    repo_dir = os.path.join(TEMP_PATH, 'repo_' + str(svn_log.revision))
    commit_message = get_git_commit_msg(svn_log)

    temp_project_name = 'revision_%d' % svn_log.revision
    svn_project = svn.checkout(SVN_REPO_URL, TEMP_PATH, temp_project_name, svn_log.revision, as_library=IS_LIBRARY)

    svn_project.svn.disconnect()
    svn_project.git.init(repo_dir)
    svn_project.git.commit_complete(commit_message, AUTHOR_NAME, AUTHOR_EMAIL)
    svn_project.save()
    svn_project.close()

    return project_path, repo_dir


def magic_git_pull(project, temp_repo_path, svn_log):
    """ 1. Removes everything from the *project* (to insure svn revision and git commit match exactly)
        2. Pulls from the repo at *repo_path*
        4. Commits the changes with the commit message *commit_message*
        5. Saves and closes the project
    """
    remove_all_children(project)

    # Temp commit for fake git merge --squash
    project.git.commit_complete(TEMP_COMMIT_MSG, AUTHOR_NAME, AUTHOR_EMAIL, bAllowEmptyCommits=True)

    project.git.remote_add(TEMP_REMOTE_NAME, temp_repo_path)
    project.git.fetch(TEMP_REMOTE_NAME)
    project.git.branch_track('refs/remotes/%s/master' % TEMP_REMOTE_NAME, TARGET_BRANCH_NAME)

    pull_options = git.get_pull_options()
    pull_options.fast_forward_strategy = GitFastForwardStrategy.NoFastForward
    pull_options.resolve_file_conflict_strategy = GitResolveFileConflictStrategy.Theirs
    project, merge_result = project.git.pull(AUTHOR_NAME, AUTHOR_EMAIL, pull_options)

    commit_message = get_git_commit_msg(svn_log)
    project.git.commit_complete(commit_message, AUTHOR_NAME, AUTHOR_EMAIL, bAllowEmptyCommits=True, bAmendCommit=True)

    project.git.branch_unset_upstream()
    project.git.remote_remove(TEMP_REMOTE_NAME)

    project.save()
    project.close()


def remove_repo(project_path):
    try:
        project = projects.open(project_path)
        project.git.de_init(True)
        project.close()
    except:
        pass


def remove_temp_project(project_path):
    """ Opens the project at *project_path* and deinitializes its git repository
        This needs to be done because the script can't delete the repository with "shutil.rmtree"
        Removes the rest of the contents int the folder at *TEMP_PATH*
        Removes the temporary .project/.library files and their associated files
    """
    remove_repo(project_path)
    try:
        shutil.rmtree(TEMP_PATH)
        os.makedirs(TEMP_PATH)
    except Exception as e:
        logging.error('Failed to delete %s. Reason: %s' % (TEMP_PATH, e))


def main():
    if projects.primary is not None:
        projects.primary.close()

    create_git_project()

    # get the list of all revision of SVN_REPO_URL
    # oldest to newest
    # starting at revision STARTING_REVISION + 1
    svn_logs = list(reversed(svn.get_log(SVN_REPO_URL)))
    svn_logs_to_migrate = [log for log in svn_logs if STARTING_REVISION <= log.revision]
    assert svn_logs_to_migrate, ('Nothing to migrate. STARTING_REVISION is greater than the newest '
                                 'revision in %s') % SVN_REPO_URL

    system.prompt_answers['LossOfDataWarning2'] = PromptResult.Yes
    no_project_root_dir_amount = 0
    try:
        for svn_log in svn_logs_to_migrate:
            try:
                temp_project_path, temp_git_repo_path = create_git_repo_from_svn_revision(svn_log)

                # pull to your main Git project
                git_project = projects.open(NEW_MAIN_PROJECT_PATH)

                magic_git_pull(git_project, temp_git_repo_path, svn_log)

                # can be omitted if enough storage space is available (big performance increase)
                # if omitted the folder at "TEMP_PATH" needs to be deleted manually
                # needed storage space = (project file size + project git repository size) * revisions
                # example:   255.16 MB = (1.6 KB + 2.55 MB) * 100
                remove_temp_project(temp_project_path)
            except ValueError as ve:
                # Early svn revisions often do not contain a codesys project
                if ('The URL %s is not a project root directory.' % SVN_REPO_URL) == str(ve):
                    no_project_root_dir_amount += 1
                    if no_project_root_dir_amount < len(svn_logs_to_migrate):
                        logging.info(ve)
                        continue
                logging.critical(ve)
                raise
    except:
        if REMOVE_MAIN_PROJECT_ON_FAILURE:
            remove_repo(NEW_MAIN_PROJECT_PATH)
            for file in os.listdir(NEW_PROJECT_DIR):
                if file.startswith(NEW_PROJECT_NAME):
                    os.remove(os.path.join(NEW_PROJECT_DIR, file))
        raise
    finally:
        system.prompt_answers.clear()


if __name__ == '__main__':
    main()

転送結果を確認するためのスクリプト

次のスクリプトは、次のチェックを実行します。

  1. 対応する Git コミットがあるかどうかを確認してください CODESYS Git のトランクのSVNリビジョン毎の転送後のプロジェクト CODESYS SVN プロジェクト

  2. かどうかを確認してください CODESYS SVN リビジョンのプロジェクトと CODESYS 対応する Git コミットのプロジェクトは同一です

の比較 CODESYS プロジェクトは CODESYS転送と検証が行われる開発環境。

注記

通常のスクリプト方法 compare_to()CODESYS このコンテキストでは期待どおりに動作しません。このため、スクリプトでは回避策が使用されています。

import os
import logging


""" --- inputs ---
"""
# URL of the SVN project you want to check the migration for
SVN_REPO_URL = 'svn://your.svnserver.com/trunk/CodesysProjectNr1'
# Directory of the project with git repository to check
PROJECT_DIR = 'C:/CodesysProjects/CodesysProjectNr1'
# Name of the project to check
PROJECT_NAME = 'CodesysProjectNr1'
# Is the project a library (otherwise will be checked out as a project)
IS_LIBRARY = False

# Path to an EMPTY temporary folder (doesn't have to exist) that can be deleted
TEMP_PATH = 'C:/CodesysProjects/CodesysProjectNr1/temp'

# Name of the branch that should be checked
TARGET_BRANCH_NAME = 'develop'

""" --- check script ---
"""
# Type extension of the project (.project / .library)
PROJECT_FILE_TYPE_EXTENSION = '.library' if IS_LIBRARY else '.project'
# Path to your new Git project
MAIN_PROJECT_PATH = os.path.join(PROJECT_DIR, PROJECT_NAME + PROJECT_FILE_TYPE_EXTENSION)


def get_revision_from_commit_msg(msg):
    """ filters out the revision number from the commit message *msg*
        migrated SVN commits in git: git commit = 'SVN revision *revision number*: *svn commit message*'
        if your migrated svn commits in git are different this possibly won't work
    """
    s_num = ''
    for c in msg:
        if c.isdigit():
            s_num = s_num + c
        if ':' == c:
            break
    return int(s_num)


def compare_changed_objects_workaround(changed_objects):
    """ Compares the changed objects in the *changed_objects* list
        by comparing their content
        Returns True if the objects are identical (not changed)
    """
    for changed_object in changed_objects:
        if changed_object.left_object is None:
            return False
        if changed_object.right_object is None:
            return False
        if not changed_object.left_object.textual_declaration.text == changed_object.right_object.textual_declaration.text:
            logging.error('Compared objects are not the same:')
            logging.error('left: %s/%s' %
                          (changed_object.left_object.parent.get_name(), changed_object.left_object.get_name()))
            logging.error(changed_object.left_object.textual_declaration.text)
            logging.error('right: %s/%s' %
                          (changed_object.right_object.parent.get_name(), changed_object.right_object.get_name()))
            logging.error(changed_object.right_object.textual_declaration.text)
            logging.error('diff: ' + str(changed_object.differences))
            logging.error('-----------------------------------------------------')
            return False
    return True


def compare_projects(project_one, project_two):
    """ Compares two projects
        Returns True if they are identical
    """
    comparison_result = project_one.compare_to(project_two, flags=ComparisonFlags.IGNORE_PROPERTIES)
    changed_objects = list(comparison_result.get_changed_objects())

    if 0 == len(changed_objects):
        return True
    else:
        return compare_changed_objects_workaround(changed_objects)


def main():
    system.prompt_handling = PromptHandling.LogSimplePrompts

    if projects.primary is not None:
        projects.primary.close()

    git_project = projects.open(MAIN_PROJECT_PATH)

    git_project, git_branch = git_project.git.checkout(TARGET_BRANCH_NAME, force=True)

    git_logs = list(reversed(list(git_project.git.log())))

    system.prompt_answers['LossOfDataWarning2'] = PromptResult.Yes

    checks_done = 0
    try:
        for git_log in git_logs:
            if 'SVN revision ' in git_log.message_string:
                git_project, git_null_branch = git_project.git.checkout(git_log.sha_string)

                revision = get_revision_from_commit_msg(git_log.message_string)

                temp_project = svn.checkout(SVN_REPO_URL, TEMP_PATH, 'revision_' + str(revision), revision,
                                            as_library=IS_LIBRARY ,as_primary_project=False)

                if not compare_projects(git_project, temp_project):
                    prompt_message = ("Revision %d does not equal it's corresponding git commit. "
                                      "Do you want to continue?") % revision
                    logging.error(prompt_message)
                    prompt_result = system.ui.prompt(prompt_message, PromptChoice.YesNo, PromptResult.No)
                    if PromptResult.No == prompt_result:
                        temp_project.close()
                        break

                checks_done += 1
                temp_project.close()
    finally:
        system.prompt_answers.clear()
        git_project, git_branch = git_project.git.checkout(TARGET_BRANCH_NAME)
        git_project.close()

    if checks_done:
        system.ui.prompt('Done', PromptChoice.OK, PromptResult.OK)
    else:
        error_msg = 'No migrated commits found. If you have custom commit messages change this script accordingly.'
        logging.error(error_msg)
        raise Exception(error_msg)


if __name__ == '__main__':
    main()