Skip to main content

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 error

  • time: 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.