Skip to main content

最佳实践

何时恢复 Git 项目 CODESYS 编辑项目时崩溃了

取决于哪种操作 CODESYS 以前崩溃过,你可以选择以下恢复:

  • 通常,你可以在崩溃后重新打开项目。 CODESYS Git 恢复最近一次内部保存的项目状态,并将项目内容重写到 Git 项目存储中。这种 “故障保存” 机制将项目视为 “主” 数据源。如果项目在合并操作期间崩溃,则不应在重新打开后继续工作之前立即保存,而应先丢弃所有更改(git reset--hard)。为此,你可以使用 丢弃所有更改 命令在 状态和阶段 观点。

  • 如果崩溃后无法再成功打开项目(例如,因为完全不兼容的文件最终出现在项目存储中),您可以选择以下恢复选项:从存储库完全重新加载项目。有关更多信息,请参阅: 从存储库重建项目

将项目转移自 CODESYS SVNCODESYS Git

我们强烈建议您在成功将项目转移到 GIT 后不要删除 SVN 存储库,这样在必要时您仍然可以访问它们。

提示

CODESYS Git 版本 1.4.0.0 及更高版本,脚本接口可用于从中传输项目 CODESYS SVNCODESYS Git

重要

Git 命令 git-svn 不能用来转移 CODESYS项目来自 CODESYS SVNCODESYS Git 因为 CODESYS Git 使用 .json 文件格式,与二进制文件格式的对比 CODESYS SVN

. 由于存储库结构的不同而提出的评论和建议 CODESYS SVNCODESYS Git
  • 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 SVNCODESYS Git 安装在 CODESYS。借助 工具自定义 命令, 提交完成 命令已添加到 Git 集成 中的命令类别 Git 菜单。

  1. 打开 CODESYS 你保存在SVN存储库中的项目。

  2. 点击 项目SVN结账

    该项目的最新版本已签出。

  3. 点击 项目SVN断开项目与 SVN 的连接

  4. 在计算机的本地文件目录中创建一个新的空文件夹。

  5. CODESYS,点击 Git初始化 Git 存储库 然后在对话框中选择上一步中创建的空文件夹。

  6. 点击 Git提交完成

    该项目现在存储在本地 Git 存储库中,必要时可以推送到远程存储库。

在此过程中,每个 SVN 修订都是在一个 Git 提交中手动传输的。

要求: CODESYS SVNCODESYS Git 安装在 CODESYS。借助 工具自定义 命令, 提交完成 命令已添加到 Git 集成 中的命令类别 Git 菜单。

  1. 在中创建一个新的空项目 CODESYS 然后给它起个名字,例如, Main Project

  2. 点击 Git初始化 Git 存储库

  3. 点击 Git提交完成

  4. 点击 Git提交 执行空提交。 提交分阶段更改 对话框,选择 允许空提交 选项。

  5. 关闭此项目。

  6. 点击 项目SVN结账 查看存储在 SVN 中的项目的所需版本。

  7. 点击 项目SVN断开项目与 SVN 的连接。在随后的对话框中,保留默认选项,然后单击 好吧

  8. 保存该项目。

  9. 点击 Git初始化 Git 存储库 然后在文件目录中选择一个空文件夹(例如: 温度)。

  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 Pull 大师 对话框。的默认选项 快进策略 不应更改。

  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 提交的项目是相同的

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