最佳实践
何时恢复 Git 项目 CODESYS 编辑项目时崩溃了
取决于哪种操作 CODESYS 以前崩溃过,你可以选择以下恢复:
- 通常,你可以在崩溃后重新打开项目。 CODESYS Git 恢复最近一次内部保存的项目状态,并将项目内容重写到 Git 项目存储中。这种 “故障保存” 机制将项目视为 “主” 数据源。如果项目在合并操作期间崩溃,则不应在重新打开后继续工作之前立即保存,而应先丢弃所有更改(git reset--hard)。为此,你可以使用 丢弃所有更改 命令在 状态和阶段 观点。 
- 如果崩溃后无法再成功打开项目(例如,因为完全不兼容的文件最终出现在项目存储中),您可以选择以下恢复选项:从存储库完全重新加载项目。有关更多信息,请参阅: 从存储库重建项目 
将项目转移自 CODESYS SVN 到 CODESYS Git
我们强烈建议您在成功将项目转移到 GIT 后不要删除 SVN 存储库,这样在必要时您仍然可以访问它们。
提示
在 CODESYS Git 版本 1.4.0.0 及更高版本,脚本接口可用于从中传输项目 CODESYS SVN 到 CODESYS Git。
重要
Git 命令 git-svn 不能用来转移 CODESYS项目来自 CODESYS SVN 到 CODESYS Git 因为 CODESYS Git 使用 .json 文件格式,与二进制文件格式的对比 CODESYS SVN。
- 在 CODESYS Git,一个 Git 存储库只能管理一个 CODESYS 项目。因为一个 SVN 存储库可以包含 CODESYS 项目,传输到时,必须为 SVN 存储库中的每个项目创建单独的 Git 存储库 CODESYS Git。 
- 建议将 SVN 存储库的各个项目转移到的存储库中 CODESYS Git 在逐个项目的基础上。 
- 不建议将 SVN 存储库拆分成多个 SVN 存储库,因为这可能会导致以后出现问题。 
- 分支和标签存在于 SVN 和 GIT 中,但它们在这两个系统中的设计不同。向... 转账 - git-svn命令不可用,因为此命令不能用于 CODESYS Git (见上文)。- 因此,我们只能将 SVN 主干转移到 Git 分支(默认为 “主分支” 或 “主分支”)。如有必要,可以手动将 SVN 标签添加为 Git 标签 
在此过程中,最新版本的 CODESYS 项目首次签出 CODESYS SVN。然后断开与 SVN 的连接,并使用 Git 存储库 CODESYS Git 已为项目初始化。此过程的缺点是项目的历史记录会丢失,因为只有最新的 SVN 版本被传输到 Git
要求: CODESYS SVN 和 CODESYS Git 安装在 CODESYS。借助 工具 → 自定义 命令, 提交完成 命令已添加到 Git 集成 中的命令类别 Git 菜单。
- 打开 CODESYS 你保存在SVN存储库中的项目。 
- 点击 项目 → SVN → 结账。 - 该项目的最新版本已签出。 
- 点击 项目 → SVN → 断开项目与 SVN 的连接。 
- 在计算机的本地文件目录中创建一个新的空文件夹。 
- 在 CODESYS,点击 Git → 初始化 Git 存储库 然后在对话框中选择上一步中创建的空文件夹。 
- 点击 Git → 提交完成。 - 该项目现在存储在本地 Git 存储库中,必要时可以推送到远程存储库。 
在此过程中,每个 SVN 修订都是在一个 Git 提交中手动传输的。
要求: CODESYS SVN 和 CODESYS Git 安装在 CODESYS。借助 工具 → 自定义 命令, 提交完成 命令已添加到 Git 集成 中的命令类别 Git 菜单。
- 在中创建一个新的空项目 CODESYS 然后给它起个名字,例如, - Main Project。
- 点击 Git → 初始化 Git 存储库。 
- 点击 Git → 提交完成。 
- 点击 Git → 提交 执行空提交。 提交分阶段更改 对话框,选择 允许空提交 选项。 
- 关闭此项目。 
- 点击 项目 → SVN → 结账 查看存储在 SVN 中的项目的所需版本。 
- 点击 项目 → SVN → 断开项目与 SVN 的连接。在随后的对话框中,保留默认选项,然后单击 好吧。 
- 保存该项目。 
- 点击 Git → 初始化 Git 存储库 然后在文件目录中选择一个空文件夹(例如: 温度)。 
- 点击 Git → 提交完成。 
- 保存项目并将其关闭。 - 这个 - *.project可以删除该项目的文件。Git 存储库- Temp在任何情况下都不得删除该项目。
- 打开 - Main Project再次进行项目。
- 点击 Git → 遥控器 打开 遥控器 观点。 
- 在 遥控器 查看,单击 添加。 添加新遥控器 对话框中,指定 Git 存储库的 URL - Temp并指定,例如- Temp_Remote作为 别名。
- 选择此添加的遥控器,然后单击 取。 
- 点击 Git → 分支机构 打开 Git 分支 观点。 
- 选择的主分支/主分支 - Main,点击 赛道分支。 追踪远程分支机构 对话框中,选择主分支/主分支- Temp_Remote在步骤 14 中添加的远程。
- 对 Git 存储库的主/主分支执行提取 主要 通过点击 使用期权进行拉动 在 Git 分支 查看并选择 用 “他们的” 来处理冲突 选项作为 合并冲突策略 在 Git Pull 大师 对话框。的默认选项 快进策略 不应更改。 
- 现在点击 Git -→ 提交完成. 在 提交暂存和未暂存的更改 对话框,选择 修改提交 选项。 - 本指南开头查看的 SVN 项目的修订版现在存储在 Git 存储库中 主要 作为承诺。 
- 在 遥控器 查看,选择 - Temp遥控并点击 移除。
- 保存 - Main Project项目并关闭它。
- 移除 Git 存储库 - Temp从您的文件目录中。
- 如果您想将 SVN 项目的另一个版本转移到 Git 存储库,请按照步骤 6 中的说明进行操作,获取 SVN 项目的下一个所需修订版本。 
使用脚本将 SVN 项目转移到 Git
要求: CODESYS Git 版本 >= 1.4.0.0
以下传输脚本模板如下所示:
1。用于从中转移项目的脚本 CODESYS SVN 到 CODESYS 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()
检查传输结果的脚本
以下脚本执行以下检查:
- 检查中是否有相应的 Git 提交 CODESYS Git 每次 SVN 版本的中继版本传输后的项目 CODESYS SVN 项目 
- 检查是否 CODESYS SVN 修订版的项目和 CODESYS 相应 Git 提交的项目是相同的 
a 的比较 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()