Using the Git Scripting Interface
CODESYS Git provides a scripting interface for Git. Examples of how to use the interface are shown below. Below you will also find information on the text-based output of the messages that are generated by many Git operations.
For more information on working with the scripting interface, see: Script Engine API Documentation
Requirements
To run the examples below, the following is required:
CODESYS 3.5.19.30 or higher
The following components are also required:
CODESYS Library Documentation Support (to create the compiled library)
CODESYS Git 1.6.0.0 or higher
A local Git installation
Important
Use SecureString passwords whenever possible
For increased security, passwords should be passed as a .NET SecureString.
The affected GIT operations are: clone
, fetch
, pull
, push
SecureStrings can be created in IronPython as follows. The “password” itself should come from a secure string and not, as here for demonstration purposes, be in plain text in the script. Internally, every password provided is handled securely :
from System.Security import SecureString sec_str_password = SecureString() for c in "Passwort": sec_str_password.AppendChar(c)
For further security measures when using CODESYS Git see: Security for CODESYS Git
Preparation
CODESYS library
Because no CODESYS libraries are currently managed in Git, the sources of a CODESYS library are required. The String Functions.library
library from the CODESYS String Libraries product is used in the examples.
Remote Git repository
For this example, a bare Git repository in the file system is used as a remote repository.
To prepare, first delete the corresponding directory and then create a new one.
import shutil import os def prepare_empty_dir(empty_dir_path): print("Prepare empty directory at", empty_dir_path) shutil.rmtree(empty_dir_path, ignore_errors=True) if not(os.path.exists(empty_dir_path) and os.path.isdir(empty_dir_path)): os.makedirs(empty_dir_path)
An empty bare Git repository is then created.
import subprocess def create_bare_git_repository(bare_repository_path): print("Create bare git repository at", bare_repository_path) create_bare_repository_cmd = 'cmd /c "git -C \"' + bare_repository_path + '\" init --bare"' try: retcode = subprocess.call(create_bare_repository_cmd, shell=True) if retcode < 0: raise Exception("Creating bare git repository at " + bare_repository_path + " failed: ", -retcode) else: print("Creating bare git repository at " + bare_repository_path + " succeeded.") except Exception as e: print("[ERROR] Creating bare git repository failed: ", e) raise
The empty bare Git repository is filled with the contents of the CODESYS library.
def initialize_bare_git_repository(library_path, local_repository_path, bare_repository_path): print("Open library:", library_path) project = projects.open(library_path) print("Initiate local git repository") project.git.init(local_repository_path) project.git.commit_complete("Create git repo for lib", "user", "mail@mail") print("Push to remote git repository") origin_remote = project.git.remote_add("origin", bare_repository_path) project.git.branch_set_upstream_to(origin_remote) project.git.push() project.git.de_init(cleanUpFileSystem=True) project.close()
The following script executes the described functions.
import os def main(): if projects.primary: projects.primary.close() basepath = "C:\\CODESYS_Projects\\Git_Scripting_Tutorial\\" project_basepath = os.path.join(basepath, "projects\\") library_file_name = "ExampleLib1.library" library_path = os.path.join(project_basepath, library_file_name) remote_repo_basepath = os.path.join(basepath, "remotes\\") remote_repo_directory_name = "ExampleLib1RemoteRepo" remote_repo_path = os.path.join(remote_repo_basepath, remote_repo_directory_name) local_repo_basepath = os.path.join(basepath, "repos\\") local_repo_directory_name = "ExampleLib1LocalRepo" local_repo_path = os.path.join(local_repo_basepath, local_repo_directory_name) print("Create and push library to remote git repository") prepare_empty_dir(remote_repo_path) create_bare_git_repository(remote_repo_path) initialize_bare_git_repository(library_path, local_repo_path, remote_repo_path) print("[Success] All done") if __name__ == '__main__': main()
The bare Git repository created in this way and provided with contents is used for the other examples.
Cloning a remote Git repository
The following function executes git clone
for a remote Git repository.
def clone_git_repository(project_basepath, project_file_name, remote_repo_url_or_path, local_repo_path): update_flags = VersionUpdateFlags.UpdateAll | VersionUpdateFlags.SilentMode project = git.clone(project_basepath, project_file_name, remote_repo_url_or_path, local_repo_path, update_flags=update_flags) project.save() return project
Creating and merging a new branch
The following auxiliary function creates some new objects in a CODESYS project as an example.
def add_dut(project): ST_STRUCT_STR = """\ a : BOOL; b : BIT; c : BIT; """ ST_UNION_STR = """\ TYPE ExampleUnion : UNION Zahl : INT; Prozent : ExampleAlias; Bits : ExampleStruct; END_UNION END_TYPE """ # Create a struct DUT and insert the list of variables just into the right # place in line two, row 0 (line numbering starts with line 0) example_dut_struct = project.create_dut('ExampleStruct') # DutType.Structure is the default example_dut_struct.textual_declaration.insert(2, 0, ST_STRUCT_STR) # Alias types get their "content" via the base type, which will just end up # as one line in the declaration part: # TYPE MyAlias : INT (0..100); END_TYPE example_dut_alias = project.create_dut('ExampleAlias', DutType.Alias, "INT (0..100)") # Instead of injecting the variables into the existing declaration, # one can also just replace the complete declaration part, including the # boilerplate code. example_dut_union = project.create_dut('ExampleUnion', DutType.Union) example_dut_union.textual_declaration.replace(ST_UNION_STR)
The following auxiliary function increments the build version in the project information of a CODESYS project.
def increment_build_version(project): """ Increment build version in project info. """ info = project.get_project_info() old_version = info.version info.version = (old_version.Major, old_version.Minor, old_version.Build + 1, 0) project.save()
The following function first creates a new branch and makes changes in this branch, and then merges these changes back into the main branch.
def copy_branch_and_merge(project): current_branch = project.git.branch_show_current() print("Current branch: ", current_branch.friendly_name) project, current_branch = project.git.branch_copy(current_branch, "new_branch", checkout=True) print("Current branch: ", current_branch.friendly_name) add_dut(project) project.git.commit_complete("Added DUT", "user", "mail@mail") increment_build_version(project) project.git.commit_complete("Incremented build version", "user", "mail@mail") project, current_branch = project.git.checkout("master") print("Current branch: ", current_branch.friendly_name) project, merge_result = project.git.merge("new_branch") print("Merged: ", merge_result.ToString()) project.save() return project
The following script executes git clone
for a remote Git repository, makes changes in the project, and then pushes the changes to the remote Git repository (CopyBranchAndMerge.py).
def main(): if projects.primary: projects.primary.close() basepath = "C:\\CODESYS_Projects\\Git_Scripting_Tutorial\\" project_basepath = os.path.join(basepath, "projects\\") library_file_name = "ExampleLib1Cloned.library" remote_repo_basepath = os.path.join(basepath, "remotes\\") remote_repo_directory_name = "ExampleLib1RemoteRepo" remote_repo_path = os.path.join(remote_repo_basepath, remote_repo_directory_name) local_repo_basepath = os.path.join(basepath, "repos\\") local_repo_directory_name = "ExampleLib1LocalRepo" local_repo_path = os.path.join(local_repo_basepath, local_repo_directory_name) print("Clone project") project = clone_git_repository(project_basepath, library_file_name, remote_repo_path, local_repo_path) project = copy_branch_and_merge(project) project.git.push() project.save() project.git.de_init(cleanUpFileSystem=True) project.save() project.close() print("[Success] All done") if __name__ == '__main__': main())
Creating a compiled library
The following script executes git clone
for a CODESYS source library from a remote Git repository and then creates a compiled library from it (CreateCompiledLibrary.py).
import os class CompileError(Exception): pass def clone_git_repository(project_basepath, project_file_name, remote_repo_url_or_path, local_repo_path): update_flags = VersionUpdateFlags.UpdateAll | VersionUpdateFlags.SilentMode project = git.clone(project_basepath, project_file_name, remote_repo_url_or_path, local_repo_path, update_flags=update_flags) project.save() return project def create_compiled_library(project): # requires the CODESYS Library Documentation Support Package! project.check_all_pool_objects() compile_result_message = system.get_messages(category='{97F48D64-A2A3-4856-B640-75C046E37EA9}')[-1] if "0 errors" in compile_result_message: project.save_as_compiled_library(destination_name=None) else: raise CompileError("Compile failed: " + compile_result_message) return project basepath = "D:\\JiraTickets\\GIT-145\\" project_basepath = os.path.join(basepath, "projects\\") remote_repo_basepath = os.path.join(basepath, "remotes\\") remote_repo_directory_name = "StringFunctions.git" remote_repo_path = os.path.join(remote_repo_basepath, remote_repo_directory_name) local_repo_basepath = os.path.join(basepath, "repos\\") local_repo_path = os.path.join(local_repo_basepath, "StringFunctions.git") print("Clone project") project = clone_git_repository(project_basepath, "String Functions Cloned.library", remote_repo_path, local_repo_path) project = create_compiled_library(project) project.git.de_init(cleanUpFileSystem=True) project.close() print("[Success] All done")
Installing a library from a remote Git repository
The following script executes git clone
for a CODESYS source library from a remote Git repository and installs this library in the current CODESYS instance (InstallLibrary.py).
import os def clone_git_repository(project_directory_path, project_file_name, remote_repo_url_or_path, local_repo_path): update_flags = VersionUpdateFlags.UpdateAll | VersionUpdateFlags.SilentMode project = git.clone(project_directory_path, project_file_name, remote_repo_url_or_path, local_repo_path, update_flags=update_flags) project.save() return project def install_library(project): library_repo = librarymanager.repositories[0] librarymanager.install_library(project.path, library_repo, True) def main(): if projects.primary: projects.primary.close() basepath = "C:\\CODESYS_Projects\\Git_Scripting_Tutorial\\" project_basepath = os.path.join(basepath, "projects\\") library_file_name = "ExampleLib1Cloned2.library" remote_repo_basepath = os.path.join(basepath, "remotes\\") remote_repo_directory_name = "ExampleLib1RemoteRepo" remote_repo_path = os.path.join(remote_repo_basepath, remote_repo_directory_name) local_repo_basepath = os.path.join(basepath, "repos\\") local_repo_directory_name = "ExampleLib1LocalRepo" local_repo_path = os.path.join(local_repo_basepath, local_repo_directory_name) print("Clone project") project = clone_git_repository(project_basepath, library_file_name, remote_repo_path, local_repo_path) print("Install library") install_library(project) project.git.de_init(cleanUpFileSystem=True) project.close() print("[Success] All done") if __name__ == '__main__': main()
Message output for Git operations
When using CODESYS Git, most commands provide text-based output. When running CODESYS in the command line, this is output automatically output during the execution of CODESYS Git commands via the script driver. When using CODESYS Git in CODESYS Development System, the outputs also appear in the message window.
Structure of the messages: Git:<severity>: [<time>] <text>
In the user interface of CODESYS Git, the output is reduced to: [<time>] <text>
severity
: message category. The categories range from purely informational to critical errortime
: Exact time of message. Format:HH:MM:SS
text
: Contents of the message. For standard Git commands, the contents correspond to the command line command that would deliver the same result as the script driver call made. For CODESYS Git commands that do not correspond to a standard Git command (for example, Recover Project from Repository), the message text explains the action performed.
Commands with multiple messages:
For some commands (for example, git log
), the output is split into multiple messages. In the case of git log
, each displayed commit is shown in a separate message. In this case, to make it clear that these messages are part of the git log
command, a reference is made to the original command in the messages.