Skip to main content

Meilleures pratiques

Restaurer un projet Git si CODESYS s'est écrasé lors de l'édition du projet

Selon l'opération CODESYS planté auparavant, vous disposez des options de récupération suivantes :

  • Normalement, vous pouvez rouvrir le projet après un plantage. CODESYS Git restaure le dernier état de projet enregistré en interne et réécrit le contenu du projet dans le stockage de projet Git. Ce mécanisme de « échec de sauvegarde » traite le projet comme une source de données « maître ». Si le projet s'est écrasé lors d'une opération de fusion, vous ne devez pas enregistrer immédiatement avant de continuer à travailler après la réouverture, mais d'abord annuler toutes les modifications (git reset --hard). Pour ce faire, vous pouvez utiliser le Ignorer toutes les modifications commande dans le Statut et mise en scène voir.

  • S'il n'est plus possible d'ouvrir le projet après le crash (par exemple parce que des fichiers totalement incompatibles se sont retrouvés dans le stockage du projet), vous disposez de l'option de récupération suivante : Recharger complètement le projet à partir du référentiel. Pour plus d'informations, voir : Reconstruire le projet à partir du référentiel

Transférer un projet de CODESYS SVN à CODESYS Git

Nous vous recommandons fortement de ne pas supprimer les référentiels SVN après avoir transféré avec succès les projets vers GIT afin de pouvoir toujours y accéder si nécessaire.

Astuce

Dans CODESYS Git version 1.4.0.0 et supérieures, une interface de script sera disponible pour transférer un projet depuis CODESYS SVN à CODESYS Git.

Important

La commande Git git-svn ne peut pas être utilisé pour transférer CODESYSdes projets de CODESYS SVN à CODESYS Git parce que CODESYS Git utilise le .json format de fichier, contrairement au format de fichier binaire de CODESYS SVN.

. Commentaires et recommandations dus aux différentes structures de référentiel de CODESYS SVN et CODESYS Git
  • Dans CODESYS Git, un dépôt Git ne peut gérer qu'un seul CODESYS projet. Parce qu'un dépôt SVN peut contenir plusieurs CODESYS projets, un référentiel Git distinct doit être créé pour chaque projet dans un référentiel SVN lors du transfert vers CODESYS Git.

  • Il est recommandé de transférer les projets individuels d'un dépôt SVN vers les dépôts de CODESYS Git sur une base projet par projet.

  • Il n'est pas recommandé de diviser un référentiel SVN en plusieurs référentiels SVN car cela peut entraîner des problèmes ultérieurement.

  • Les branches et les balises existent à la fois dans SVN et GIT, mais elles sont conçues différemment dans ces deux systèmes. Un transfert avec le git-svn la commande n'est pas possible car cette commande ne peut pas être utilisée dans CODESYS Git (voir au dessus).

    On se limite donc à transférer le trunk SVN vers une branche Git (par défaut « Master » ou « Main »). Si nécessaire, les balises SVN peuvent être ajoutées manuellement en tant que balises Git.

Dans cette procédure, la dernière version du CODESYS le projet est d'abord extrait dans CODESYS SVN. Ensuite la connexion à SVN est déconnectée et un dépôt Git avec CODESYS Git est initialisé pour le projet. L'inconvénient de cette procédure est que l'historique du projet est perdu car seule la dernière révision SVN est transférée vers Git.

Exigences: CODESYS SVN et CODESYS Git sont installés dans CODESYS. Au moyen du OutilsPersonnaliser commande, le Validation terminée la commande a été ajoutée au Intégration Git catégorie de commande dans le Git menu.

  1. Ouvrez le CODESYS projet que vous avez enregistré dans le référentiel SVN.

  2. Cliquez sur ProjetSVNVérifier.

    La dernière révision du projet est extraite.

  3. Cliquez sur ProjetSVNDéconnecter le projet du SVN.

  4. Créez un nouveau dossier vide dans le répertoire de fichiers local de votre ordinateur.

  5. Dans CODESYS, Cliquez sur GitInitialiser le référentiel Git et sélectionnez le dossier vide créé à l'étape précédente dans la boîte de dialogue.

  6. Cliquez sur GitValidation terminée.

    Le projet est désormais stocké dans le référentiel Git local et peut être transféré vers un référentiel distant si nécessaire.

Dans cette procédure, chaque révision SVN est transférée manuellement dans un commit Git.

Exigences: CODESYS SVN et CODESYS Git sont installés dans CODESYS. Au moyen du OutilsPersonnaliser commande, le Validation terminée la commande a été ajoutée au Intégration Git catégorie de commande dans le Git menu.

  1. Créez un nouveau projet vide dans CODESYS et nommez-le, par exemple, Main Project.

  2. Cliquez sur GitInitialiser le référentiel Git.

  3. Cliquez sur GitValidation terminée.

  4. Cliquez sur GitCommettre pour exécuter un commit vide. Dans le Valider les modifications par étapes boîte de dialogue, sélectionnez le Autoriser la validation vide option.

  5. Fermez ce projet.

  6. Cliquez sur ProjetSVNVérifier pour consulter la révision souhaitée du projet stockée dans SVN.

  7. Cliquez sur ProjetSVNDéconnecter le projet du SVN. Dans la boîte de dialogue suivante, conservez l'option par défaut et cliquez sur D'ACCORD.

  8. Enregistrez le projet.

  9. Cliquez sur GitInitialiser le référentiel Git et sélectionnez un dossier vide dans le répertoire de fichiers (exemple : Température).

  10. Cliquez sur GitValidation terminée.

  11. Enregistrez le projet et fermez-le.

    Le *.project Le fichier de ce projet peut être supprimé. Le dépôt Git Temp du projet ne doit en aucun cas être supprimé.

  12. Ouvrez le Main Project projet à nouveau.

  13. Cliquez sur GitTélécommandes pour ouvrir le Télécommandes voir.

  14. Dans le Télécommandes voir, cliquez Ajouter. Dans le Ajouter une nouvelle télécommande boîte de dialogue, spécifiez l'URL du référentiel Git Temp et précisez, par exemple, Temp_Remote comme le Alias.

  15. Sélectionnez cette télécommande ajoutée et cliquez sur Aller chercher.

  16. Cliquez sur GitBranches pour ouvrir le Branches Git voir.

  17. Sélectionnez la branche principale/maître de Main, Cliquez sur Suivre la branche. Dans le Suivre la succursale distante boîte de dialogue, sélectionnez la branche maître/principale du Temp_Remote télécommande qui a été ajoutée à l’étape 14.

  18. Exécuter un pull sur la branche master/main du dépôt Git Principal en cliquant Tirez avec des options dans le Branches Git afficher et sélectionner le Utilisez « le leur » pour les conflits option comme Fusionner la stratégie de conflit dans le Maître Git Pull dialogue. L'option par défaut pour Stratégie d'avance rapide ne devrait pas être modifié.

  19. Cliquez maintenant Git -→ Validation terminée. dans le Valider les modifications échelonnées et non échelonnées boîte de dialogue, sélectionnez le Modifier le commit option.

    La révision du projet SVN qui a été extraite au début de ce guide est désormais stockée dans le dépôt Git Principal comme un engagement.

  20. Dans le Télécommandes vue, sélectionnez le Temp télécommande et cliquez Retirer.

  21. Sauver la Main Project projet et fermez-le.

  22. Supprimer le dépôt Git Temp à partir de votre répertoire de fichiers.

  23. Si vous souhaitez transférer une autre révision du projet SVN vers le référentiel Git, suivez ces instructions de l'étape 6 pour la prochaine révision souhaitée du projet SVN.

Transférer un projet SVN vers Git avec des scripts

Exigence: CODESYS Git version >= 1.4.0.0

Les modèles de script suivants pour le transfert sont affichés ci-dessous :

1. Script pour transférer un projet depuis CODESYS SVN à CODESYS Git

2. Script pour vérifier si un commit Git correspondant a été créé pour chaque révision SVN lors du transfert

. Remarques
  • Aucune modification n'est apportée au référentiel SVN.

  • Les deux scripts sont conçus comme des modèles que vous devez adapter aux exigences respectives.

Script pour le transfert d'un projet SVN

Le script transfère toutes les révisions SVN du tronc d'un CODESYS SVN projet à la branche "maître" d'un CODESYS Git projet.

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 pour vérifier le résultat du transfert

Le script suivant effectue les vérifications suivantes :

  1. Vérifiez s'il existe ou non un commit Git correspondant dans le CODESYS Git projet après le transfert pour chaque révision SVN du tronc d'un CODESYS SVN projet

  2. Vérifiez si oui ou non le CODESYS projet d'une révision SVN et le CODESYS Les projets du commit Git correspondant sont identiques

La comparaison d'un CODESYS le projet est basé sur le CODESYSenvironnement de développement où le transfert et la vérification sont effectués.

Note

La méthode de script habituelle compare_to() dans CODESYS ne se comporte pas comme prévu dans ce contexte. Pour cette raison, une solution de contournement est utilisée dans le 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()