Best Practices
Restoring a Git project when CODESYS has crashed while editing the project
Depending on which operation CODESYS crashed before, you have the following options for recovery:
Normally you can reopen the project after a crash. CODESYS Git restores the last internally saved project state and rewrites the project contents to the Git project storage. This “failsave” mechanism treats the project as a "master" data source. If the project has crashed during a merge operation, then you should not save immediately before continuing to work after reopening, but first discard all changes (git reset --hard). To do this, you can use the Discard all changes command in the Status & Staging view.
In case it is no longer possible to successfully open the project after the crash (for example, because completely incompatible files had ended up in the project storage), you have the following option for a recovery: Completely reload the project from the repository. For more information, see: Rebuild Project from Repository
Transferring a project from CODESYS SVN to CODESYS Git
We strongly recommend that you do not delete the SVN repositories after successfully transferring the projects to GIT so that you can still access them if necessary.
Tip
In CODESYS Git version 1.4.0.0 and higher, a scripting interface will be available for transferring a project from CODESYS SVN to CODESYS Git.
Important
The Git command git-svn
cannot be used to transfer CODESYSprojects from CODESYS SVN to CODESYS Git because CODESYS Git uses the .json
file format, in contrast to the binary file format of CODESYS SVN.
In CODESYS Git, a Git repository can manage only one CODESYS project. Because an SVN repository can contain multiple CODESYS projects, a separate Git repository has to be created for each project in an SVN repository when transferring to CODESYS Git.
It is recommended to transfer the individual projects of an SVN repository to the repositories of CODESYS Git on a project-by-project basis.
It is not recommended to split an SVN repository into multiple SVN repositories because this can cause problems later on.
Branches and tags exist in both SVN and GIT, but they are designed differently in these two systems. A transfer with the
git-svn
command is not possible because this command cannot be used in CODESYS Git (see above).Therefore, we limit ourselves to transferring the SVN trunk to a Git branch (by default "Master" or "Main"). If necessary, the SVN tags can be added manually as Git tags.
In this procedure, the latest version of the CODESYS project is first checked out in CODESYS SVN. Then the connection to SVN is disconnected and a Git repository with CODESYS Git is initialized for the project. The disadvantage of this procedure is that the history of the project is lost because only the latest SVN revision is transferred to Git.
Requirements: CODESYS SVN and CODESYS Git are installed in CODESYS. By means of the Tools → Customize command, the Commit complete command has been added to the Git Integration command category in the Git menu.
Open the CODESYS project which you have saved in the SVN repository.
Click Projekt → SVN → Checkout.
The latest revision of the project is checked out.
Click Project → SVN → Disconnect Project from SVN.
Create a new empty folder in the local file directory of your computer.
In CODESYS, click Git → Initialize Git Repository and select the empty folder created in the previous step in the dialog.
Click Git → Commit complete.
The project is now stored in the local Git repository and can be pushed to a remote repository if necessary.
In this procedure, every single SVN revision is manually transferred in one Git commit.
Requirements: CODESYS SVN and CODESYS Git are installed in CODESYS. By means of the Tools → Customize command, the Commit complete command has been added to the Git Integration command category in the Git menu.
Create a new empty project in CODESYS and name it, for example,
Main Project
.Click Git → Initialize Git Repository.
Click Git → Commit complete.
Click Git → Commit to execute an empty commit. In the Commit staged changes dialog, select the Allow empty commit option.
Close this project.
Click Project → SVN → Checkout to check out the desired revision of the project stored in SVN.
Click Project → SVN → Disconnect project from SVN. In the subsequent dialog, keep the default option and click OK.
Save the project.
Click Git → Initialize Git Repository and select an empty folder in the file directory (example: Temp).
Click Git → Commit complete.
Save the project and close it.
The
*.project
file of this project can be deleted. The Git repositoryTemp
of the project must not be deleted under any circumstances.Open the
Main Project
project again.Click Git → Remotes to open the Remotes view.
In the Remotes view, click Add. In the Add new remote dialog, specify the URL of the Git repository
Temp
and specify, for example,Temp_Remote
as the Alias name.Select this added remote and click Fetch.
Click Git → Branches to open the Git Branches view.
Select the master/main branch of
Main
, click Track branch. In the Track remote branch dialog, select the master/main branch of theTemp_Remote
remote which was added in Step 14.Execute a pull on the master/main branch of the Git repository Main by clicking Pull with options in the Git Branches view and selecting the Use "theirs" for conflicts option as the Merge Conflict Strategy in the Git Pull Master dialog. The default option for Fast Forward Strategy should not be changed.
Now click Git -→ Commit complete. in the Commit staged and unstaged changes dialog, select the Modify commit option.
The revision of the SVN project which was checked out at the beginning of this guide is now stored in the Git repository Main as a commit.
In the Remotes view, select the
Temp
remote and click Remove.Save the
Main Project
project and close it.Remove the Git repository
Temp
from your file directory.If you want to transfer another revision of the SVN project to the Git repository, then follow these instructions from Step 6 for the next desired revision of the SVN project.
Transferring an SVN project to Git with scripting
Requirement: CODESYS Git version >= 1.4.0.0
The following script templates for the transfer are displayed below:
1. Script for transferring a project from CODESYS SVN to CODESYS Git
2. Script for checking whether or not a corresponding Git commit has been created for each SVN revision during the transfer
No changes are made to the SVN repository.
Both scripts are intended as templates which you need to adapt to the respective requirements.
Script for the transfer of an SVN project
The script transfers all SVN revisions of the trunk of a CODESYS SVN project to the "master" branch of a CODESYS Git project.
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()
Script for checking the transfer result
The following script performs the following checks:
Check whether or not there is a corresponding Git commit in the CODESYS Git project after the transfer for each SVN revision of the trunk of a CODESYS SVN project
Check whether or not the CODESYS project of an SVN revision and the CODESYS project of the corresponding Git commit are identical
The comparison of a CODESYS project is based on the CODESYSdevelopment environment where the transfer and verification are performed.
Note
The usual scripting method compare_to()
in CODESYS does not behave as expected in this context. For this reason, a workaround is used in the script.
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()