GitLabGroovy

Last modified by René Vögeli on 2026/01/23 08:43

import org.gitlab.api.*;
import org.gitlab.api.http.*;
import org.gitlab.api.models.*;

import groovy.json.*;

import java.util.Collection;
import java.util.Iterator;
import java.util.Calendar;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

import java.text.SimpleDateFormat;

import org.apache.velocity.tools.generic.EscapeTool;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.codec.binary.Base64;

import org.dom4j.Node;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.xpn.xwiki.doc.XWikiDocument;

@SuppressWarnings("GrUnnecessarySemicolon")
public class GitLabGroovy {
    def xwiki;
    def context;
    def services;
    def logger;
    def gitlabapi;
    def gitlabproject;
    def prjname = "";
    def prjdesc = "";
    def prjversion = "";
    def contribVersion = "";
    def xmlVersion = "";
    def gitlaburl = "";
    def repuser = "";
    def repname = "";
    def reppath = "";
    def repsrcpath = "src/main/resources";
    def repbranch = "";
    def defaultspace = "";
    def configdoc;
    def savedlist = "";
    def repository = null;
    def username = "";
    def email = "";
    def token = "";
    def defaultDate = "";
    Boolean removeDates;
    String license;
    String parentDefinitionStrategy;
    String defaultLanguageDefinitionStrategy;
    def defaultUser = "";
    def status = new HashMap();
    def DEFAULTAUTHOR = "xwiki:XWiki.Admin";

    public setXWiki(xwiki, context, services) {
        this.xwiki = xwiki;
        this.context = context;
        this.services = services;
        this.logger = services.logging.getLogger(this.getClass().getCanonicalName());
    }

    public hasProgrammingRights() {
        return xwiki.hasProgrammingRights();
    }

    public setGitLabConfig(page) {
        this.configdoc = xwiki.getDocument(page)
        this.prjname = configdoc.getValue("name");
        this.prjdesc = configdoc.getValue("description");
        this.prjversion = configdoc.getValue("version");
        this.contribVersion = configdoc.getValue("contribVersion");
        this.xmlVersion = configdoc.getValue("xarXmlVersion");
        this.gitlaburl = configdoc.getValue("gitlab_url");
        this.repuser = configdoc.getValue("repository_user");
        this.repname = configdoc.getValue("repository_name");
        this.reppath = configdoc.getValue("repository_path");
        this.repbranch = (StringUtils.isBlank(configdoc.getValue("repository_branch"))) ? "master" : configdoc.getValue("repository_branch");
        this.defaultspace = configdoc.getValue("defaultspace");
        this.savedlist = configdoc.getValue("savedlist");
        this.defaultDate = configdoc.getValue("defaultdate");
        this.removeDates = configdoc.getValue("removeDates");
        this.license = configdoc.getValue("license");
        this.parentDefinitionStrategy = (configdoc.getValue("parentDefinitionStrategy") != null) ? configdoc.getValue("parentDefinitionStrategy") : "";
        this.defaultLanguageDefinitionStrategy = (configdoc.getValue("defaultLanguageDefinitionStrategy") != null) ? configdoc.getValue("defaultLanguageDefinitionStrategy") : "";
        this.defaultUser = configdoc.getValue("defaultuser");
        this.status = getStatus(configdoc.getValue("status"));

        if (contribVersion==null || contribVersion=="")
            contribVersion = "8.4"

        if (xmlVersion==null || xmlVersion=="")
            xmlVersion = "1.1"

        def authobj = configdoc.getObject("GitLab.Code.GitLabAuthClass", "contextuser", context.user)
        if (authobj && authobj.getProperty("token").property.value != '') {
            this.token = authobj.getProperty("token").property.value
        } else {
            Attempt to load a token defined on the user profile
            def userProfileDoc = xwiki.getDocument(context.user);
            userProfileDoc.use('GitLab.Code.GitLabAuthProfileClass');
            def userProfileToken = userProfileDoc.getValue('token');
            if (userProfileToken != '') {
                this.token = userProfileToken;
            }
        }

        Basic authentication
        
Create a GitLabApi instance to communicate with your GitLab server
        gitlabapi = GitlabAPI.connect(gitlaburl, token)

        gitlabproject = gitlabapi.getProject(repuser, repname)
        logger.debug("Project: ${gitlabproject}")
        return (gitlabproject==null) ? null : "";
    }

    public getStatus(status) {
        def smap = new HashMap();
        for (sline in StringUtils.split(status, "\r\n")) {
            def items = sline.split(";");
            smap.put(items[0], [ "xwikiversion" : (items.length>1) ? items[1] : "", "xwikihash" : (items.length>2) ? items[2] : "", "gitlabversion" : (items.length>3) ? items[3] : "" ])
        }
        return smap;
    }

    public getPageStatus(filePath) {
        return status.get(filePath);
    }

    public saveStatus() {
        def sstatus = "";
        for (key in status.keySet()) {
            def stat = status.get(key);
            sstatus += "${key};${stat.xwikiversion};${stat.xwikihash};${stat.gitlabversion}\n";
        }
        only save if changed
        if (status!=configdoc.getValue("status")) {
            configdoc.set("status", sstatus);
            configdoc.save();
        }
    }

    public getDefaultSpace() {
        return this.defaultspace;
    }

    public getSavedList() {
        return (this.savedlist==null) ? "" : this.savedlist;
    }

    public getFilePath(pagedoc, nested) {
        get page
        def filePath = getSourcePath() + ((nested) ? pagedoc.getSpace().replaceAll("[.]","/") : pagedoc.getSpace()) + "/" + pagedoc.documentReference.name;
        def language = pagedoc.getLanguage();
        if (language!=null&&language!="")
            filePath += "." + language;
        filePath += ".xml"
        return filePath;
    }

    public getStatusPath(pagedoc) {
        get page
        def filePath = pagedoc.getSpace() + "/" + pagedoc.documentReference.name;
        def language = pagedoc.getLanguage();
        if (language!=null&&language!="")
            filePath += "." + language;
        return filePath;
    }

    public cleanXML(gitlabcontent, withFormat) {
        def newdoc = new XWikiDocument();
        newdoc.fromXML(gitlabcontent.replaceAll("\r",""));
        return getXML(newdoc.newDocument(context.getContext()), withFormat);
    }

    public String getFileContentAsString(sha, withFormat) {
        /*       def dservice = new DataService(gitlabclient);
               def blob = dservice.getBlob(repository, sha);
               def gitlabcontent = new String(Base64.decodeBase64(blob.content.getBytes()), "UTF-8");
               if (withFormat)
                return cleanXML(gitlabcontent, true);
               return gitlabcontent;
       */
    }

/*
    public String getFileContentAsString(String space, String page, String language) {
        get page
        def filePath = space + "/" + page;
        if (language!=null&&language!="")
          filePath += "." + language;
        filePath += ".xml"
        return "";
   }
*/

    public getPOMFile() {
        def escapetool = new EscapeTool();
        def samplePOM = xwiki.getDocument("GitLab.Code.SamplePOM").getContent();
        samplePOM = samplePOM.replaceAll("PRJNAME", prjname.toLowerCase().replaceAll(" ", "-"))
        samplePOM = samplePOM.replaceAll("PRJPNAME", prjname)
        samplePOM = samplePOM.replaceAll("PRJDESC", escapetool.xml(prjdesc))
        samplePOM = samplePOM.replaceAll("PRJVERSION", prjversion)
        samplePOM = samplePOM.replaceAll("CONTRIBVERSION", contribVersion)
        samplePOM = samplePOM.replaceAll("XMLVERSION", xmlVersion)
        samplePOM = samplePOM.replaceAll("REPOSITORY_NAME", repname)
        samplePOM = samplePOM.replaceAll("REPOSITORY_USER", repuser)
        samplePOM = samplePOM.replaceAll("USERNAME", username)
        return samplePOM;
    }

    public getXML(pagedoc, boolean withFormat) {
        def clonedDoc = pagedoc.document.clone();
        boolean isTranslationDocument = false;

        if ((this.parentDefinitionStrategy.isBlank() || this.parentDefinitionStrategy.equals("setDefault")) && clonedDoc.getParent().isBlank()) {
            clonedDoc.setParent(clonedDoc.getSpace() + ".WebHome");
        }

         If the cloned document is a translation, we won't be able to know directly if it has a TranslationDocumentClass ; the quick and dirty way to check it is to get the default document.
        if (xwiki.getDocument(clonedDoc.getFullName()).getObject("XWiki.TranslationDocumentClass")) {
            if (clonedDoc.getSyntaxId()!="plain/1.0") {
                clonedDoc.setSyntaxId("plain/1.0");
            }
            if (!clonedDoc.isHidden()) {
                clonedDoc.setHidden(true);
            }
            isTranslationDocument = true;
        }

         remove Tag object
        if (clonedDoc.getObject("XWiki.TagClass")) {
            clonedDoc.removeObject(clonedDoc.getObject("XWiki.TagClass"));
        }

         remove all gitlab authentication classes so that we don't commit passwords
        for (object in clonedDoc.getObjects("GitLab.Code.GitLabAuthClass")) {
            clonedDoc.removeObject(object);
        }

         remove all gitlab authentication classes so that we don't commit passwords
        for (object in clonedDoc.getObjects("GitLabCode.GitLabConfigClass")) {
            object.set("status", "", context.getContext())
        }

        if (withFormat) {
            clonedDoc.setCreator(DEFAULTAUTHOR);
            clonedDoc.setContentAuthor(DEFAULTAUTHOR);
            clonedDoc.setAuthor(DEFAULTAUTHOR);

        } else if (defaultUser && defaultUser!="") {
            clonedDoc.setCreator(defaultUser);
            clonedDoc.setContentAuthor(defaultUser);
            clonedDoc.setAuthor(defaultUser);
        }  else {
            clonedDoc.setCreator(clonedDoc.getAuthor());
            clonedDoc.setContentAuthor(clonedDoc.getAuthor());
        }

        if (defaultDate && defaultDate!="") {
            clonedDoc.setCreationDate(defaultDate);
            clonedDoc.setContentUpdateDate(defaultDate);
            clonedDoc.setDate(defaultDate);
            clonedDoc.setVersion("1.1");
        } else {
            clonedDoc.setContentUpdateDate(clonedDoc.getDate())
            clonedDoc.setCreationDate(clonedDoc.getDate())
            clonedDoc.setVersion("1.1");
        }

         Update attachment dates
        for (xa in clonedDoc.getAttachmentList()) {
            xa.setVersion("1.1");
            if (defaultDate && defaultDate!="") {
                xa.setDate(defaultDate);
            }
            if (withFormat) {
                xa.setAuthor(DEFAULTAUTHOR);
            } else if (defaultUser && defaultUser!="") {
                xa.setAuthor(defaultUser);
            }
        }

        clonedDoc.setComment("");
        clonedDoc.setMinorEdit(false);
        def c = clonedDoc.toXML(true, false, true, false, context.getContext())
        def c2 = c.trim().replaceAll("[\r]","");
        return (withFormat) ? format(c2, "", isTranslationDocument) : c2;
    }

    /

  • Creates a new file in the repository
         *
  • @param branchName The name of a repository branch
  • @param commitMsg  The commit message
  • @param actions    The actions to send
  • @throws IOException on gitlab api call error
         */
        public gitLabCommits(String branchName, String commitMsg, Object actions) throws IOException {
            String tailUrl = gitlabproject.URL + "/" + gitlabproject.getId() + "/repository/commits";
            GitlabHTTPRequestor requestor = gitlabapi.dispatch();

        def req = requestor
                .with("branch", branchName)
                .with("commit_message", commitMsg)
                .with("actions", actions);
        logger.trace("JSON: " + JsonOutput.toJson(req.data));
        return req.to(tailUrl, GitlabCommit.class);
    }

    public commitFiles(pagelist, message, pom) {
        def srcPath = getSourcePath();
        def newSpaces = new ArrayList();
        def newPages = new ArrayList();

        def currentPageData = new HashMap();
        def pagesBySpace = new HashMap();

        def pagename2 = "";

         we need to list entries to see if we create or update
        def entriesKeySet = getEntries("").keySet();

        for (pagename in pagelist) {
            separate the language particle at the end of the page name from the page fullname. The pagename is of form <docfullname>.<doclanguage>
            def languageSeparator = pagename.lastIndexOf('.');
            if (languageSeparator < 0) {
                def pagefullname = pagename;
            }
            def pagefullname = pagename.substring(0, languageSeparator);
            def lang = pagename.substring(languageSeparator + 1);
            def pagedoc = xwiki.getDocument(pagefullname);
            
make sure we get the right translations
            if (lang!=null && lang!="") {
                pagedoc = pagedoc.getTranslatedDocument(lang);
            }
            def space = pagedoc.getSpace();
            def page = pagedoc.documentReference.name;
            def language = pagedoc.getLanguage();

            def pageList = pagesBySpace.get(space);
            if (pageList==null) {
                pageList = new ArrayList();
                pagesBySpace.put(space, pageList);
            }
            pageList.add(pagedoc);
        }

        def committedPages = new ArrayList();
        def commitInfo = null;

        def actions = []
        def gitlabEntries = getEntries("");

        try {
            for (space in pagesBySpace.keySet()) {
                loop on each page
                for (pagedoc in pagesBySpace.get(space)) {
                    pagename2 = pagedoc.getFullName();
                    def page = pagedoc.documentReference.name;
                    def language = pagedoc.getLanguage();

                     find page Path
                    def result = findPage(gitlabEntries, pagedoc);
                    def entry = result.entry;
                    def filePath = result.filePath;

                    def newXML = getXML(pagedoc, true);
                    def newData = newXML.getBytes();

                    def action = [:];
                    action.action = (entry!=null) ? "update" : "create";
                    action.file_path = filePath;
                    action.content = newXML;

                     adding the page
                    actions.add(action);

                    if (!newPages.contains(filePath)) {
                        def oldData = currentPageData.get(filePath);
                        updating status to check for changes
                        committedPages.add(getStatusPath(pagedoc));
                        def pageStatus = status.get(getStatusPath(pagedoc));
                        if (pageStatus==null)
                            pageStatus = [ "xwikiversion" : "", "xwikihash" : "", "gitlabversion" : ""];
                        status.put(getStatusPath(pagedoc), pageStatus);
                        pageStatus.xwikiversion = "${pagedoc.getVersion()}";
                        pageStatus.xwikihash = "${newXML.hashCode()}";
                    }
                }
            }
        } catch (Throwable e) {
            e.printStackTrace();
            logger.debug("error preparing commit on page ${pagename2}: " + e.getMessage());
            return null;
        } finally {
        }

         Adding pom in commit
        if (pom=="1") {
            def action = [:];
            try {
                gitlabapi.getRepositoryFile(gitlabproject, "pom.xml", repbranch)
                action.action = "update"
            } catch(e) {
                action.action = "create"
            }
            action.file_path = "pom.xml";
            action.content = getPOMFile();
            actions.add(action);
            logger.debug("Added pom.xml to commit");
        }

         now real commit
        def newCommit = gitLabCommits(repbranch, message, actions);
        logger.debug("Created commit: " + newCommit);

         we need to save the status
        saveStatus();
        return "${gitlaburl}${repuser}/${repname}/tree/${newCommit.id}";
    }

    public getChangedPages(String spaces) {
        return getChangedPages(spaces, "");
    }

    public findPage(entries, pagedoc) {
        def filePath = getFilePath(pagedoc, false);
        logger.debug("Check ${filePath} in entries");
        def entry = entries.get(filePath);
        if (entry==null) {
            filePath = getFilePath(pagedoc, true);
            logger.debug("Check ${filePath} in entries");
            entry = entries.get(filePath);
        }
        return [ entry : entry, filePath : filePath ];
    }

    protected checkPage(page, pagedoc, changedMap, samePages, gitlabEntries) {
        def wikicontent = getXML(pagedoc, true);
        def wikihash = (wikicontent==null) ? "" : "${wikicontent.hashCode()}";

        def wikicontent_unformatted = getXML(pagedoc, false);

        def result = findPage(gitlabEntries, pagedoc);
        def entry = result.entry;
        def filePath = result.filePath;
        logger.debug("Check ${filePath} ${wikihash}");

        if (entry==null || !wikihash.equals(entry.gitlabhash) || !wikihash.equals(entry.gitlabhash_unformatted)) {
            def pageStatus = getPageStatus(getStatusPath(pagedoc));
            if (pageStatus==null) {
                pageStatus = [ "xwikiversion" : pagedoc.getVersion(), "xwikihash" : "", "gitlabversion" : "", "gitlabsha" : (entry==null) ? "" : entry.gitlabsha ];
                status.put(getStatusPath(pagedoc), pageStatus);
            }
            pageStatus.filePath = filePath;
            pageStatus.page = page;
            pageStatus.fullname = pagedoc.fullName;
            pageStatus.language = pagedoc.language
            pageStatus.status = "";
            pageStatus.gitlabsha = (entry==null) ? "" : entry.gitlabsha;

            logger.debug("Checking page ${page} version ${pagedoc.getVersion()} pageStatus ${pageStatus} ${wikihash}")
            if the documents match after formatting but the pure gitlab content is different then display it as 'F' (content is equivalent except formatting).
            if (entry!=null && wikihash.equals(entry.gitlabhash)) {
                pageStatus.status = "F";
            } else if (entry==null && !pagedoc.isNew()) {
                pageStatus.status = "A";
            } else if (pageStatus.xwikihash=="") {
                def gitlabsha = (entry==null) ? "" : entry.gitlabsha;
                if (gitlabsha=="")
                    pageStatus.status = "A";
                else
                    pageStatus.status = "?";
            } else if (pagedoc.getVersion().equals(pageStatus.xwikiversion)) {
                
version has not changed in the wikia
                def whash = "${wikicontent.hashCode()}";
                if the recorded hash is the same then we have a modified version in GitLab
                
otherwise it's a bad state so it's a conflict
                if (whash == pageStatus.xwikihash)
                    pageStatus.status = "U";
                else
                    pageStatus.status = "C";
            } else {
                def gitlabsha = (entry==null) ? "" : entry.gitlabsha;
                if (gitlabsha=="")
                    pageStatus.status = "A";
                else if(pageStatus.gitlabsha==gitlabsha)
                    pageStatus.status = "M";
                else
                    pageStatus.status = "C";
            }

             we do not check the parent anymore because we force it's value as we cannot edit it
            
verify Invalid parents
            if (pagedoc.getParent()=="")
            
 pageStatus.status += " - Empty Parent"

             verify default language
            if (pagedoc.getLanguage()=="") {
                if (pagedoc.getDefaultLanguage()!="" && pagedoc.getTranslationList().size()==0)
                    pageStatus.status += " - Default language should be empty"
            }

            changedMap.put(page, pageStatus)
        } else {
            samePages.add(filePath);
            def pageStatus = getPageStatus(getStatusPath(pagedoc));
            if (pageStatus==null) {
                pageStatus = [ "xwikiversion" : "", "xwikihash" : "", "gitlabversion" : "", "gitlabsha" : "" ];
                status.put(getStatusPath(pagedoc), pageStatus);
            }
            pageStatus.xwikiversion = pagedoc.getVersion();
            pageStatus.xwikihash = wikicontent.hashCode();
            if (entry!=null) {
                pageStatus.gitlabversion = entry.gitlabversion;
                pageStatus.gitlabsha = entry.gitlabsha;
            }
        }
    }

    public containsSpace(spaceName, spaceList) {
        for (item in spaceList) {
            if (spaceName.startsWith(item))
                return true;
        }
        return false;
    }

    public getChangedPages(String spaces, String savedlist) {
        def changedMap = new TreeMap();
        def gitlabEntries = getEntries(spaces);
        def spaceList = null;
        def samePages = new ArrayList();
        def list;

        if (!savedlist || savedlist=="") {
            spaceList = Arrays.asList(StringUtils.split(spaces," ,"));
            def joinList = [];
            for (space in spaceList) {
                joinList.add("doc.fullName like '${space}%'")
            }
            def whereSpace = StringUtils.join(joinList, " or ");
            def sql = "select distinct doc.fullName from XWikiDocument as doc where ${whereSpace}";
            logger.debug("Searching for ${sql}");
            list = xwiki.search(sql)
        } else {
            list = StringUtils.split(xwiki.getDocument(savedlist).getValue("list"), "|");
        }

        for (page in list) {
            def pagedoc = xwiki.getDocument(page);
            checkPage(page, pagedoc, changedMap, samePages, gitlabEntries);

            if (pagedoc.isNew()) {
                logger.debug("GitLabApp: error reading page ${pagedoc.fullName} which should exist");
            }

            def transList = null
            try {
                transList = pagedoc.getTranslationList();
            } catch (Exception e) {
                System.out.println("Exception getting trans list of ${pagedoc.fullName} store ${pagedoc.store}: " + e.getMessage());
                e.printStackTrace();
                transList = [];
            }
            for (trans in transList) {
                def tpagedoc = pagedoc.getTranslatedDocument(trans);
                checkPage(page + "." + tpagedoc.language, tpagedoc, changedMap, samePages, gitlabEntries);
            }
        }

        if (spaceList!=null) {
            for (filePath in gitlabEntries.keySet()) {
                def entry = gitlabEntries.get(filePath);
                def i0 = getSourcePath().size()
                def i1 = filePath.lastIndexOf("/");
                def i2 = filePath.indexOf(".xml");
                def spaceName = (i1==-1) ? filePath : filePath.substring(i0, i1);
                spaceName = spaceName.replaceAll("/", ".")
                def page = (i2==-1) ? filePath : filePath.substring(i1+1, i2);
                def language = "";
                def pageName = page;
                if (page.contains(".")) {
                    def i3 = page.indexOf(".");
                    language = page.substring(i3+1);
                    pageName = page.substring(0, i3);
                }
                logger.debug("SpaceName: ${spaceName} SpaceList: ${spaceList} SamePages: ${samePages} FilePath: ${filePath} Page: ${spaceName}.${page}")
                if (containsSpace(spaceName, spaceList)&&!samePages.contains(filePath)&&!changedMap.keySet().contains(spaceName + "." + page)) {
                    changedMap.put(spaceName + "." + pageName, [ "fullname" : "${spaceName}.${pageName}", "language" : language, "status" : "N", "xwikiversion" : "", "gitlabsha" : entry.gitlabsha ])
                    logger.debug("Adding page ${spaceName}.${page}")
                } else {
                    if (!containsSpace(spaceName, spaceList))
                        logger.debug("${spaceName} not in ${spaceList}")
                    if (samePages.contains(filePath))
                        logger.debug("${filePath} already in list")
                    if (changedMap.keySet().contains(spaceName + "." + page))
                        logger.debug("${spaceName}.${page} already in list")
                }
            }
        }
        saveStatus if changed
        saveStatus();
        return changedMap;
    }

    public updatePages(pageList, spaces) {
        def changedMap = new TreeMap();
        def gitlabEntries = getEntries(spaces);

        for (pageobj in pageList) {
            def page = pageobj.page;
            def sha = pageobj.sha;

            logger.debug("Ready to update: ${page} ${sha}");

             separate the language particle at the end of the page name from the page fullname. The pagename is of form <docfullname>.<doclanguage>
            def languageSeparator = page.lastIndexOf('.');
            if (languageSeparator < 0) {
                def pagefullname = page;
            }
            def pagefullname = page.substring(0, languageSeparator);
            def lang = page.substring(languageSeparator + 1);

            def pagedoc = xwiki.getDocument(pagefullname);
            if (lang!=null && lang!="") {
                pagedoc = pagedoc.getTranslatedDocument(lang);
            }
            def wikicontent = getXML(pagedoc, true);

            def result = findPage(gitlabEntries, pagedoc);
            def entry = result.entry;
            def filePath = result.filePath;
            def gitlabversion = (entry==null) ? "" : entry.gitlabversion;
            def gitlabcontent = (entry==null) ? "" : entry.gitlabcontent;

            logger.debug("Ready to update: ${filePath}");

            if (!wikicontent.equals(gitlabcontent) && gitlabcontent!=null && gitlabcontent!="") {
                def pageStatus = getPageStatus(getStatusPath(pagedoc));
                if (pageStatus==null) {
                    pageStatus = [ "xwikiversion" : pagedoc.getVersion(), "xwikihash" : "", "gitlabversion" : "" ];
                }
                pageStatus.filePath = filePath;
                pageStatus.page = pagefullname;
                pageStatus.status = "";

                 updating XWiki document from GitLab
                changedMap.put(pagefullname, pageStatus);
                def archive = pagedoc.document.getDocumentArchive(context.getContext());
                def version = pagedoc.document.getRCSVersion();

                def newdoc = new XWikiDocument();
                newdoc.fromXML(gitlabcontent);

                 check attachments that do not exist in updated pages and delete them to recycle bin
                for (xa in pagedoc.getAttachmentList()) {
                    if (!newdoc.getAttachment(xa.getFilename())) {
                        pagedoc.document.deleteAttachment(xa.attachment, true, context.getContext());
                    }
                }

                 Make sure they are not marked dirty
                for (xa in newdoc.getAttachmentList()) {
                    xa.setMetaDataDirty(false);
                    xa.getAttachment_content().setContentDirty(false);
                }

                 we need to make sure previous history is kept
                newdoc.setDocumentArchive(archive);

                 we need to keep the creator if there was already a document
                if (pagedoc.getCreator()!=null)
                    newdoc.setCreator(pagedoc.getCreator());

                 set user and author to current user
                newdoc.setContentAuthor(context.getUser());
                newdoc.setAuthor(context.getUser());

                 we need to make sure no version is added
                if (pagedoc.isNew()) {
                    newdoc.setMetaDataDirty(true);
                    newdoc.setContentDirty(true);
                    newdoc.setRCSVersion(null);
                } else {
                    newdoc.setMetaDataDirty(true);
                    newdoc.setContentDirty(true);
                }

                 Go through each object change between the previous and the new document in order to identify
                
objects that have been removed. We will need to explicitely mark them to be removed when
                saving newdoc
                newdoc.getObjectDiff(pagedoc.getDocument(), newdoc, context.getContext()).each{ diff ->
                  diff.each{ diffElement ->
                    if (diffElement.getAction().equals("object-removed")) {
                      def removedObject = pagedoc.getObject(diffElement.getClassName(), diffElement.getNumber());
                      if (removedObject != null) {
                        newdoc.addXObjectToRemove(removedObject.getXWikiObject());
                      }
                    }
                  }
                }

                 saving document
                xwiki.getXWiki().saveDocument(newdoc, "Updated from GitLab", context.getContext());

                 saving attachments
                newdoc.saveAllAttachments(false, true, context.getContext());

                 we need to force the saving the document archive.
                if (newdoc.getDocumentArchive() != null) {
                    xwiki.getXWiki().getVersioningStore().saveXWikiDocArchive(newdoc.getDocumentArchive(context.getContext()), true, context.getContext());
                }

                 reading the information to set the status
                def newpagedoc = xwiki.getDocument(pagedoc.getFullName());
                def newwikicontent = getXML(pagedoc, true);
                pageStatus.xwikiversion = pagedoc.getVersion();
                pageStatus.xwikihash = "${newwikicontent.hashCode()}";
                pageStatus.gitlabversion = gitlabversion;
            }
        }
        
saveStatus if changed
        saveStatus();
        return changedMap;
    }

    public exportPages(docname, pageList) {
        def export = xwiki.package
        export.setWithVersions(true)
        export.setWithVersions(false)
        export.setName(docname)
        for (page in pageList) {
            export.add(page, 0);
        }
        export.export();
    }

    public getModifiedFiles(rev) {
        return (getModifiedFiles("", rev, "10"));
    }

    public getModifiedFiles2(date, hour) {
        return (getModifiedFiles("", date, hour));
    }

    public getModifiedFiles2(dir, date, hour) {
    }

    public getModifiedFiles(dir, rev, max) {
    }

    public getRevisions(dir) {
    }

    public listFiles(dir, recursive) {
    }

    public getCommitStatus(prefix, sep, updatedonly) {
        def str = "";
        str += "${prefix}page${sep}language${sep}version${sep}isnew${sep}hash${sep}gitlabpath${sep}gitlabversion${sep}gitlabhash${sep}isdiff\n"

        while (spaceIterator.hasNext()) {
            def space = entry.getName().toString();
            Collection pageEntries = repository.getDir(space, -1, null, (Collection) null);
            Iterator pageIterator = null;
            while (pageIterator.hasNext()) {
                def pageEntry = pageIterator.next();
                def fileName = pageEntry.getName().toString();
                def i1 = fileName.indexOf(".xml");
                def pageName = (i1==-1) ? fileName : fileName.substring(0, i1);
                pageName = space + "." + pageName;
                def pagedoc = xwiki.getDocument(pageName);
                def pagexml = getXML(pagedoc, true);

                def baos = new ByteArrayOutputStream();
                logger.debug("reading file: ${space}/${fileName}")
                
repository.getFile(space + "/" + fileName, -1, fileProperties, baos);
                def gitlabcontent = new String(baos.toByteArray())

                def version = (pagedoc==null) ? "" : pagedoc.getVersion();
                def hash = (pagexml==null) ? "" : pagexml.hashCode();
                def gitlabversion = pageEntry.getRevision().toString();
                def gitlabhash = (gitlabcontent==null) ? "" : gitlabcontent.hashCode();

                def isdiff = !pagexml.equals(gitlabcontent)
                if (!updatedonly || !isdiff)
                    str += "${prefix}${pageName}${sep}${pagedoc.getLanguage()}${sep}${version}${sep}${pagedoc.isNew()}${sep}${hash}${sep}${space}/${fileName}${sep}${gitlabversion}${sep}${gitlabhash}${sep}${isdiff}\n"
            }
        }
        return str;
    }

    public getBasePath() {
        def basepath = reppath;
        if (basepath.startsWith("/"))
            basepath = srcpath.substring(1);
        if (basepath=="")
            return "";
        else if (basepath.endsWith("/"))
            return basepath;
        else
            return basepath + "/";
    }

    public getSourcePath() {
        def srcpath = getBasePath();

        if (repsrcpath==""||repsrcpath=="/"||repsrcpath==".")
            return srcpath;
        else if (repsrcpath.startsWith("/"))
            srcpath = srcpath + repsrcpath.substring(0);
        else
            srcpath = srcpath + repsrcpath;

        if (srcpath=="")
            return "";
        else if (srcpath.endsWith("/"))
            return srcpath
        else
            return srcpath + "/";
    }

    public matchSpace(path, spaces) {
        if (spaces==null)
            return true;

        for (space in spaces.split(",")) {
            if (path.startsWith(space.replaceAll("[.]", "/"))) {
                return true;
            }
        }
        return false;
    }

    public getEntries(String spaces) {
        def entries = new HashMap();

        def tree = gitlabapi.getRepositoryTree(gitlabproject, "", repbranch, true);
        def srcPath = getSourcePath();
        for (file in tree) {
            if (file.type=="blob") {
                if (file.path.startsWith(srcPath)) {
                    def file2 = gitlabapi.getRepositoryFile(gitlabproject, file.path, repbranch)
                    def gitlabcontent = new String(Base64.decodeBase64(file2.content.getBytes()), "UTF-8");
                    calculate the hash before formatting
                    def gitlabhash_unformatted = (gitlabcontent==null) ? "" : gitlabcontent.hashCode();
                    gitlabcontent = cleanXML(gitlabcontent, true);
                    def gitlabhash = (gitlabcontent==null) ? "" : gitlabcontent.hashCode();
                    def gitlabversion = 0;
                    entries.put(file.path, [ "gitlabversion" : gitlabversion, "gitlabsha" : file.id, "gitlabhash" : "${gitlabhash}", "gitlabhash_unformatted" : "${gitlabhash_unformatted}", "gitlabcontent" : gitlabcontent ]);
                    logger.debug("Adding: ${file.path}");
                }
            }
        }
        
logger.debug("Entries: ${entries}")
        return entries;
    }

    public getXMLForDiff(pagedoc, filePath, withFormat) {
        def file2 = gitlabapi.getRepositoryFile(gitlabproject, filePath, repbranch)
        def gitlabxml = new String(Base64.decodeBase64(file2.content.getBytes()), "UTF-8");
        if (withFormat)
            gitlabxml = cleanXML(gitlabxml, true);
        logger.debug("Gitlab hash " + gitlabxml.hashCode())
        def xml = getXML(pagedoc, true);
        logger.debug("XWiki hash " + xml.hashCode())

        if (pagedoc.isNew() && gitlabxml == null)
            return "Document does not exist";

        if (pagedoc.isNew())
            return "Document does not exist in the wiki"

        if (gitlabxml==null)
            return "Document does not exist in GitLab"

         remove attachment content from xml
        gitlabxml = gitlabxml.replaceAll("(?s)<attachment>(.*?)<content>(.*?)</content>(.*?)</attachment>", "<attachment>\$1<content></content>\$3</attachment>")
        xml = xml.replaceAll("(?s)<attachment>(.*?)<content>(.*?)</content>(.*?)</attachment>",
                "<attachment>\$1<content></content>\$3</attachment>")
        return [gitlabxml, xml];
    }

    public showXMLDiff(pagedoc, filePath, withFormat) {
        def file2 = gitlabapi.getRepositoryFile(gitlabproject, filePath, repbranch)
        def gitlabxml = new String(Base64.decodeBase64(file2.content.getBytes()), "UTF-8");
        if (withFormat)
            gitlabxml = cleanXML(gitlabxml, true);
        logger.debug("Gitlab hash " + gitlabxml.hashCode())
        def xml = getXML(pagedoc, true);
        logger.debug("XWiki hash " + xml.hashCode())

        if (pagedoc.isNew() && gitlabxml == null)
            return "Document does not exist";

        if (pagedoc.isNew())
            return "Document does not exist in the wiki"

        if (gitlabxml==null)
            return "Document does not exist in GitLab"

         remove attachment content from xml
        gitlabxml = gitlabxml.replaceAll("(?s)<attachment>(.*?)<content>(.*?)</content>(.*?)</attachment>", "<attachment>\$1<content></content>\$3</attachment>")
        xml = xml.replaceAll("(?s)<attachment>(.*?)<content>(.*?)</content>(.*?)</attachment>",
                "<attachment>\$1<content></content>\$3</attachment>")
        return xwiki.diff.getDifferencesAsHTML(gitlabxml, xml, false);
    }

     Adding class and method to perform indentation and cleanup
    public class XWikiXMLWriter extends XMLWriter
    {
        /

  • True if we use an output format.
             */
            private boolean useFormat;

        /

  • @param output the stream where to write the XML
  • @throws UnsupportedEncodingException in case encoding issue
             */
            public XWikiXMLWriter(OutputStream output) throws UnsupportedEncodingException
            {
                super(output);
            }

        /

  • @param output the stream where to write the XML
  • @param format the style to use when outputting the XML
  • @throws UnsupportedEncodingException in case encoding issue
             */
            public XWikiXMLWriter(OutputStream output, OutputFormat format) throws UnsupportedEncodingException
            {
                super(output, format);
                this.useFormat = true;
            }

        @Override
        protected void writeComment(String text) throws IOException
        {
            super.writeComment(text);

             Add a new line after the license declaration
            if (text.contains("See the NOTICE file distributed with this work for additional")) {
                println();
            }
        }

        @Override
        protected void writeNodeText(Node node) throws IOException
        {
            if (this.useFormat && node.getText().trim().length() == 0) {
                Check if parent node contains non text nodes
                boolean containsNonTextNode = false;
                for (Object object : node.getParent().content()) {
                    Node objectNode = (Node) object;
                    if (objectNode.getNodeType() != Node.TEXT_NODE) {
                        containsNonTextNode = true;
                        break;
                    }
                }
                if (containsNonTextNode) {
                    
Don't do anything, i.e. don't print the current text node
                } else {
                    super.writeNodeText(node);
                }
            } else {
                super.writeNodeText(node);
            }
        }

        @Override
        protected void writePrintln() throws IOException
        {
            We need to reimplement this method because of a bug (bad logic) in the original writePrintln() which checks
            
the last output char to decide whether to print a NL or not:
             ...3</a></b> > ...3</a>\n</b>
            
 but
             ...3\n</a></b> 
> ...3\n</a></b>
            
and
             ...3\n</a>\n</b> > ...3\n</a></b>
            if (this.useFormat) {
                println();
                
 writer.write(getOutputFormat().getLineSeparator());
            }
        }
    }
    formats the XWiki XML including indentation
    def String format(String data, String defaultLanguage, boolean isTranslationDocument) throws Exception
    {
        def sr = new StringReader(data);
        SAXReader reader = new SAXReader();
        Document domdoc = reader.read(sr);

        Node rnode = domdoc.getRootElement();
        if (rnode !=null) {
            Node node = rnode.element("author");
            if (node != null) {
                node.setText(DEFAULTAUTHOR);
            }
            node = rnode.element("contentAuthor");
            if (node != null) {
                node.setText(DEFAULTAUTHOR);
            }
            node = rnode.element("creator");
            if (node != null) {
                node.setText(DEFAULTAUTHOR);
            }
            node = rnode.element("originalMetadataAuthor");
            if (node != null) {
                node.setText(DEFAULTAUTHOR);
            }
        }

        if (this.removeDates) {
            this.logger.debug("Removing dates from generated XML ...");
            removeNodes("xwikidoc/creationDate", domdoc);
            removeNodes("xwikidoc/date", domdoc);
            removeNodes("xwikidoc/contentUpdateDate", domdoc);
            removeNodes("xwikidocattachment/date", domdoc);
        }

        if (this.parentDefinitionStrategy.equals("remove")) {
            removeNodes("xwikidoc/parent", domdoc);
        }

        if (this.defaultLanguageDefinitionStrategy.equals("alwaysBlank")
            || (!isTranslationDocument && this.defaultLanguageDefinitionStrategy.equals("blankIfNotTranslation"))) {
          Node node = rnode.selectSingleNode("defaultLanguage/text()");
          if (node != null) {
            node.detach();
          }
        }

        def baos = new ByteArrayOutputStream()
        XMLWriter w;
        OutputFormat format = new OutputFormat("  ", true, "UTF-8");
        format.setExpandEmptyElements(false);
        w = new XWikiXMLWriter(baos, format);

        w.write(domdoc);
        w.close();
        def result = baos.toString();

         Adding license header
        def xmlHeader = """<?xml version="${this.xmlVersion}" encoding="UTF-8"?>"""
        if (this.license != null && !this.license.isBlank() && xwiki.exists(this.license)) {
            String licenseContent = xwiki.getDocument(this.license).getContent();
            result = result.replaceAll("(<.xml .*>)", xmlHeader + "\n\n" + licenseContent);
        } else {
            result = result.replaceAll("(<.xml .*>)", xmlHeader);
        }

        return result.replaceAll("\r","");
    }

    /

  • Remove the nodes found with the xpath expression.
  • Copied from FormatMojo in xwiki-commons-tool-xar-plugin
         *
  • @param xpathExpression the xpath expression of the nodes
  • @param domdoc The DOM document
         */
        private void removeNodes(String xpathExpression, Document domdoc)
        {
            List<Node> nodes = domdoc.selectNodes(xpathExpression);
            for (Node node : nodes) {
                node.detach();
            }
        }
    }