Initial commit

parents
# Module to do Git Operations in eXist-db
Provides a mudule for manual or automated git operations.
## Documentation
Copy the `configuration.xml` example into the root of your application and edit it to your needs.
Do the same with `modules/trigger-versioning.xqm` and modules `config.xqm`.
`test-git-push.xql` is an example file generating entries for getting a collection automatically triggered to git pushes.
## Build
Building a XAR package with ant:
```shell
ant
```
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project default="xar" name="git">
<xmlproperty file="expath-pkg.xml"/>
<property name="project.version" value="${package(version)}"/>
<property name="project.app" value="git"/>
<property name="build.dir" value="build"/>
<target name="xar">
<mkdir dir="${build.dir}"/>
<zip basedir="." destfile="${build.dir}/${project.app}-${project.version}.xar" excludes="${build.dir}/*"/>
</target>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<collection xmlns="http://exist-db.org/collection-config/1.0">
<index xmlns:xs="http://www.w3.org/2001/XMLSchema">
<fulltext default="none" attributes="false"/>
</index>
<!-- Triggers to get changes automatically pushed to the git repository -->
<!-- triggers>
<trigger event="create" class="org.exist.collections.triggers.XQueryTrigger">
<parameter name="url" value="xmldb:exist://db/apps/lgpn-ling-data/modules/trigger-versioning.xqm"/>
</trigger>
<trigger event="update" class="org.exist.collections.triggers.XQueryTrigger">
<parameter name="url" value="xmldb:exist://db/apps/lgpn-ling-data/modules/trigger-versioning.xqm"/>
</trigger>
<trigger event="move" class="org.exist.collections.triggers.XQueryTrigger">
<parameter name="url" value="xmldb:exist://db/apps/lgpn-ling-data/modules/trigger-versioning.xqm"/>
</trigger>
<trigger event="copy" class="org.exist.collections.triggers.XQueryTrigger">
<parameter name="url" value="xmldb:exist://db/apps/lgpn-ling-data/modules/trigger-versioning.xqm"/>
</trigger>
<trigger event="delete" class="org.exist.collections.triggers.XQueryTrigger">
<parameter name="url" value="xmldb:exist://db/apps/lgpn-ling-data/modules/trigger-versioning.xqm"/>
</trigger>
</triggers -->
</collection>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<admin name="admin" password=""/>
<exist-collection data="data"/>
<git
user-name="John Doe"
email="john.doe@example.com"
file-path="/var/tmp/git"
source-url="git@gitlab.existsolutions.com:git.git"
branch="master"/>
</configuration>
\ No newline at end of file
xquery version "3.0";
declare variable $exist:path external;
declare variable $exist:resource external;
declare variable $exist:controller external;
declare variable $exist:prefix external;
declare variable $exist:root external;
if ($exist:path eq '') then
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<redirect url="{request:get-uri()}/"/>
</dispatch>
else if ($exist:path eq "/") then
(: forward root path to index.xql :)
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<redirect url="index.html"/>
</dispatch>
else if (ends-with($exist:resource, ".html")) then
(: the html page is run through view.xql to expand templates :)
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<view>
<forward url="{$exist:controller}/modules/view.xql"/>
</view>
<error-handler>
<forward url="{$exist:controller}/error-page.html" method="get"/>
<forward url="{$exist:controller}/modules/view.xql"/>
</error-handler>
</dispatch>
(: Resource paths starting with $shared are loaded from the shared-resources app :)
else if (contains($exist:path, "/$shared/")) then
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<forward url="/shared-resources/{substring-after($exist:path, '/$shared/')}">
<set-header name="Cache-Control" value="max-age=3600, must-revalidate"/>
</forward>
</dispatch>
else
(: everything else is passed through :)
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<cache-control cache="yes"/>
</dispatch>
<?xml version="1.0" encoding="UTF-8"?>
<xml/>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<xml/>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<div xmlns="http://www.w3.org/1999/xhtml" class="templates:surround?with=templates/page.html&amp;at=content">
<h1>An error has occurred</h1>
<p>An error has been generated by the application.</p>
<pre class="error templates:error-description"/>
<div class="source-links">
<p>View source: <a href="login.html" class="templates:load-source">this page</a>.</p>
</div>
</div>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://expath.org/ns/pkg" name="http://exist-db.org/apps/git" abbrev="git" version="0.01" spec="1.0">
<title>Git</title>
<dependency package="http://exist-db.org/apps/shared"/>
</package>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<div xmlns="http://www.w3.org/1999/xhtml" data-template="templates:surround" data-template-with="templates/page.html" data-template-at="content">
<div class="row">
<div class="col-md-8">
<div class="page-header">
<h1 data-template="config:app-title">Generated page</h1>
</div>
<div class="alert alert-success">
<p>
<a data-template="templates:load-source" href="index.html">This</a> is the entry page into your application
and was generated by eXide. It uses HTML templates for a clean separation of HTML views and application logic.</p>
<p>To add your own template functions, start by editing the XQuery module
<a data-template="templates:load-source" href="modules/app.xql">app.xql</a>.</p>
</div>
<div class="row">
<div class="col-md-6">
<p>The page template uses the <a href="http://twitter.github.com/bootstrap/">Bootstrap</a>
CSS library for the page layout.</p>
</div>
<div class="col-md-6">
<div data-template="app:test"/>
</div>
</div>
</div>
<div class="col-md-4">
<h2>Application Info</h2>
<div data-template="config:app-info"/>
</div>
</div>
</div>
\ No newline at end of file
xquery version "3.1";
module namespace app="http://exist-db.org/apps/git/templates";
import module namespace templates="http://exist-db.org/xquery/templates" ;
import module namespace config="http://exist-db.org/apps/git/config" at "config.xqm";
(:~
: This is a sample templating function. It will be called by the templating module if
: it encounters an HTML element with an attribute data-template="app:test"
: or class="app:test" (deprecated). The function has to take at least 2 default
: parameters. Additional parameters will be mapped to matching request or session parameters.
:
: @param $node the HTML node with the attribute which triggered this call
: @param $model a map containing arbitrary data - used to pass information between template calls
:)
declare function app:test($node as node(), $model as map(*)) {
<p>Dummy template output generated by function app:test at {current-dateTime()}. The templating
function was triggered by the data-template attribute <code>data-template="app:test"</code>.</p>
};
\ No newline at end of file
xquery version "3.1";
(:~
: A set of helper functions to access the application context from
: within a module.
:)
module namespace config="http://exist-db.org/apps/git/config";
declare namespace templates="http://exist-db.org/xquery/templates";
declare namespace repo="http://exist-db.org/xquery/repo";
declare namespace expath="http://expath.org/ns/pkg";
(:
Determine the application root collection from the current module load path.
:)
declare variable $config:app-root :=
let $rawPath := system:get-module-load-path()
let $modulePath :=
(: strip the xmldb: part :)
if (starts-with($rawPath, "xmldb:exist://")) then
if (starts-with($rawPath, "xmldb:exist://embedded-eXist-server")) then
substring($rawPath, 36)
else
substring($rawPath, 15)
else
$rawPath
return
substring-before($modulePath, "/modules")
;
declare variable $config:repo-descriptor := doc(concat($config:app-root, "/repo.xml"))/repo:meta;
declare variable $config:expath-descriptor := doc(concat($config:app-root, "/expath-pkg.xml"))/expath:package;
(:: Git variables coming from ../configuration.xml ::)
declare variable $config:file := doc("/db/apps/git/configuration.xml")/configuration;
declare variable $config:data-col := $config:file/exist-collection/@data/string();
declare variable $config:data-root := $config:app-root || "/" || $config:data-col;
declare variable $config:admin-user-name := $config:file/admin/@name/string();
declare variable $config:admin-user-pass := $config:file/admin/@password/string();
declare variable $config:name := $config:file/git/@user-name/string();
declare variable $config:email := $config:file/git/@email/string();
declare variable $config:source-url := $config:file/git/@source-url/string();
declare variable $config:branch := $config:file/git/@branch/string();
declare variable $config:file-repo-root := $config:file/git/@file-path/string();
declare variable $config:file-data-root := $config:file-repo-root || "/" || $config:data-col;
(:: Git variables end here ::)
(:~
: Resolve the given path using the current application context.
: If the app resides in the file system,
:)
declare function config:resolve($relPath as xs:string) {
if (starts-with($config:app-root, "/db")) then
doc(concat($config:app-root, "/", $relPath))
else
doc(concat("file://", $config:app-root, "/", $relPath))
};
(:~
: Returns the repo.xml descriptor for the current application.
:)
declare function config:repo-descriptor() as element(repo:meta) {
$config:repo-descriptor
};
(:~
: Returns the expath-pkg.xml descriptor for the current application.
:)
declare function config:expath-descriptor() as element(expath:package) {
$config:expath-descriptor
};
declare %templates:wrap function config:app-title($node as node(), $model as map(*)) as text() {
$config:expath-descriptor/expath:title/text()
};
declare function config:app-meta($node as node(), $model as map(*)) as element()* {
<meta xmlns="http://www.w3.org/1999/xhtml" name="description" content="{$config:repo-descriptor/repo:description/text()}"/>,
for $author in $config:repo-descriptor/repo:author
return
<meta xmlns="http://www.w3.org/1999/xhtml" name="creator" content="{$author/text()}"/>
};
(:~
: For debugging: generates a table showing all properties defined
: in the application descriptors.
:)
declare function config:app-info($node as node(), $model as map(*)) {
let $expath := config:expath-descriptor()
let $repo := config:repo-descriptor()
return
<table class="app-info">
<tr>
<td>app collection:</td>
<td>{$config:app-root}</td>
</tr>
{
for $attr in ($expath/@*, $expath/*, $repo/*)
return
<tr>
<td>{node-name($attr)}:</td>
<td>{$attr/string()}</td>
</tr>
}
<tr>
<td>Controller:</td>
<td>{ request:get-attribute("$exist:controller") }</td>
</tr>
</table>
};
\ No newline at end of file
xquery version "3.1";
module namespace git="http://exist-db.org/apps/git/git";
import module namespace config="http://exist-db.org/apps/git/config" at "config.xqm";
declare variable $git:name := "eXistdb User";
declare variable $git:email := "eXistdb-user@existsolutions.com";
declare variable $git:commit-message := "commited through eXistdb";
declare function git:options( $repo-path as xs:string ) {
let $options := <options><workingDir>{ $repo-path }</workingDir></options>
return
$options
};
declare function git:delete-file( $repo-path as xs:string, $app-root as xs:string, $collection as xs:string, $uri as xs:string ) {
let $file-uri := $repo-path || "/" || $collection || "/" || $uri
let $uri-elements := tokenize( $uri, "/" )
let $path-elements := ( $app-root, $collection, subsequence( $uri-elements, 1, count( $uri-elements )-1 ))
let $file := $uri-elements[last()]
let $path := string-join( $path-elements, "/" )
return(
util:log( "INFO", $uri ),
if( file:exists( $file-uri ))
then
file:delete( $file-uri )
else
util:log( "INFO", "file " || $file-uri || " not found" ),
if( xmldb:touch( $path, $file ))
then(
xmldb:remove( $path, $file )
)
else
util:log( "INFO", "resource " || $path || "/" || $file || " not found" )
)
};
declare function git:clone( $source-url as xs:string, $repo-path as xs:string, $branch as xs:string ) {
let $branch := if( $branch )
then( $branch )
else( "master" )
return
process:execute(( "git", "clone", $source-url, "-b", $branch, $repo-path ), ())
};
declare function git:pull( $repo-path as xs:string ) {
process:execute(( "git", "pull" ), git:options( $repo-path ))
};
declare function git:sync-files( $repo-path as xs:string, $app-root as xs:string, $collection as xs:string ) {
(: syncs files to disk :)
if( $collection ) then
file:sync( $app-root || "/" || $collection, $repo-path || "/" || $collection, ())
else
file:sync( $app-root, $repo-path, ())
};
declare function git:set-committer( $repo-path as xs:string, $name as xs:string, $email as xs:string ) {
let $name :=
if( $name ) then $name
else $git:name
let $email :=
if( $email ) then $email
else $git:email
let $set-name := process:execute(( "git", "config", "user.name", "'" || $name || "'" ), git:options( $repo-path ))
let $set-email := process:execute(( "git", "config", "user.email", "'" || $email || "'" ), git:options( $repo-path ))
return(
$set-name,
$set-email
)
};
declare function git:commit( $repo-path as xs:string, $name as xs:string, $email as xs:string, $message as xs:string, $collection as xs:string ) {
let $name :=
if( $name ) then $name
else $git:name
let $email :=
if( $email ) then $email
else $git:email
let $message :=
if( $message ) then $message
else $git:email
let $collection :=
if( $collection ) then $collection
else "."
let $add := process:execute(( "git", "add", $collection || "/**" ), git:options( $repo-path ))
let $commit := process:execute(( "git", "commit", "--quiet", "--no-gpg-sign", "--all",
"--author='" || $name || " <" || $email || ">'",
"--message='" || $message || "'"
), git:options( $repo-path ))
return(
$add,
$commit
)
};
declare function git:push( $name, $email, $message, $repo-path, $app-root, $collection ) {
util:log( "INFO", $collection ),
git:pull( $repo-path ),
git:sync-files( $repo-path, $app-root, $collection ),
git:set-committer( $repo-path, $name, $email ),
git:commit( $repo-path, $name, $email, $message, $collection ),
process:execute(( "git", "push", "--quiet" ), git:options( $repo-path ))
};
\ No newline at end of file
xquery version "3.1";
(:~
: A trigger to keep hsa-data in sync with GitLab
:)
module namespace trigger="http://exist-db.org/xquery/trigger";
import module namespace git="http://exist-db.org/apps/git/git" at "/db/apps/git/modules/git.xqm";
import module namespace config="http://lgpn.classics.ox.ac.uk/apps/lgpn-ling-data/config" at "/db/apps/lgpn-ling-data/modules/config.xqm";
(:~
: This function is called if a new document within hsa-data is created
: it case a xml document is created, it gets pushed to GitLab
:
: @param $url the db url of the created document
:)
declare function trigger:after-create-document($uri as xs:anyURI) {
let $log := util:log("info", "xml trigger:after-create-document: " || $uri)
return
if(contains($uri, $config:data-root))
then (
util:log("info", "trigger:after-create-document: XML"),
system:as-user(
$config:admin-user-name, $config:admin-user-pass,
git:push(
$config:name,
$config:email,
"after-create-document: " || $uri,
$config:file-repo-root,
$config:app-root,
$config:data-col
)
)
)
else()
};
(:~
: This function is called if a document within hsa-data was updated / changed
: it case a xml document was changed, it gets pushed to GitLab
:
: @param $url the db url of the changed document
:)
declare function trigger:after-update-document($uri as xs:anyURI) {
let $log := util:log("info", "xml trigger:after-update-document: " || $uri)
return
if(contains($uri, $config:data-root))
then (
util:log("info", "trigger:after-update-document: XML"),
system:as-user(
$config:admin-user-name, $config:admin-user-pass,
git:push(
$config:name,
$config:email,
"after-update-document: " || $uri,
$config:file-repo-root,
$config:app-root,
$config:data-col
)
)
)
else()
};
declare function trigger:after-move-document($new-uri as xs:anyURI, $uri as xs:anyURI) {
let $log := util:log("info", "xml trigger:after-move-document: " || $new-uri)
return
if(contains($new-uri, $config:data-root))
then (
util:log("info", "trigger:after-move-document: XML"),
system:as-user(
$config:admin-user-name, $config:admin-user-pass,
git:push(
$config:name,
$config:email,
"after-move-document: " || $uri,
$config:file-repo-root,
$config:app-root,
$config:data-col
)
)
)
else()
};
declare function trigger:after-copy-document($new-uri as xs:anyURI, $uri as xs:anyURI) {
let $log := util:log("info", "xml trigger:after-copy-document: " || $new-uri)
return
if(contains($new-uri, $config:data-root))
then (
util:log("info", "trigger:after-copy-document: XML"),
system:as-user(
$config:admin-user-name, $config:admin-user-pass,
git:push(
$config:name,
$config:email,
"after-copy-document: " || $uri,
$config:file-repo-root,
$config:app-root,
$config:data-col
)
)
)
else()
};
(:~
: This function is called if a document within the collection was deleted
: it case a document was deleted, it gets deleted at the hard disk as well
: :
: @param $url the db url of the deleted document
:)
declare function trigger:after-delete-document($uri as xs:anyURI) {
let $log := util:log("info", "trigger:after-delete-document: " || $uri)
let $uri-elements := tokenize( $uri, "/" )
let $path-elements := subsequence( $uri-elements, 6, count( $uri-elements ))
let $relative-uri := string-join( $path-elements, "/" )
return
if(contains($uri, $config:data-root))
then (
util:log("info", "trigger:after-delete-document: XML)"),
git:delete-file( $config:file-repo-root,
$config:app-root, $config:data-col, $relative-uri ),
system:as-user(
$config:admin-user-name, $config:admin-user-pass,
git:push(
$config:name,
$config:email,
"after-delete-document: " || $uri,
$config:file-repo-root,
$config:app-root,
$config:data-col
)
)
)
else()
};
\ No newline at end of file
(:~
: This is the main XQuery which will (by default) be called by controller.xql
: to process any URI ending with ".html". It receives the HTML from
: the controller and passes it to the templating system.
:)
xquery version "3.1";
import module namespace templates="http://exist-db.org/xquery/templates" ;
(:
: The following modules provide functions which will be called by the
: templating.
:)
import module namespace config="http://exist-db.org/apps/git/config" at "config.xqm";
import module namespace app="http://exist-db.org/apps/git/templates" at "app.xql";
declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare option output:method "html5";
declare option output:media-type "text/html";
let $config := map {
$templates:CONFIG_APP_ROOT : $config:app-root,
$templates:CONFIG_STOP_ON_ERROR : true()
}
(:
: We have to provide a lookup function to templates:apply to help it
: find functions in the imported application modules. The templates
: module cannot see the application modules, but the inline function
: below does see them.
:)
let $lookup := function($functionName as xs:string, $arity as xs:int) {
try {
function-lookup(xs:QName($functionName), $arity)
} catch * {
()
}
}
(:
: The HTML is passed in the request from the controller.
: Run it through the templating system and return the result.
:)
let $content := request:get-data()
return
templates:apply($content, $lookup, (), $config)
\ No newline at end of file
xquery version "1.0";
import module namespace xdb="http://exist-db.org/xquery/xmldb";
(: The following external variables are set by the repo:deploy function :)
(: file path pointing to the exist installation directory :)
declare variable $home external;
(: path to the directory containing the unpacked .xar package :)
declare variable $dir external;
(: the target collection into which the app is deployed :)
declare variable $target external;
declare function local:mkcol-recursive($collection, $components) {
if (exists($components)) then
let $newColl := concat($collection, "/", $components[1])
return (
xdb:create-collection($collection, $components[1]),
local:mkcol-recursive($newColl, subsequence($components, 2))
)
else
()
};
(: Helper function to recursively create a collection hierarchy. :)
declare function local:mkcol($collection, $path) {
local:mkcol-recursive($collection, tokenize($path, "/"))
};
(: store the collection configuration :)
local:mkcol("/db/system/config", $target),
xdb:store-files-from-pattern(concat("/db/system/config", $target), $dir, "*.xconf")
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<meta xmlns="http://exist-db.org/xquery/repo">
<description>Manage Git repositories</description>
<author>Thomas Friese</author>
<author>Lars Windauer</author>
<website>http://exist-db.org/</website>
<status>alpha</status>
<license>GNU-LGPL</license>
<copyright>true</copyright>
<type>application</type>
<target>git</target>
<prepare/>
<finish/>
<permissions user="admin" group="dba" mode="rw-rw-r--"/>
<deployed>2018-08-28T13:13:53.385+02:00</deployed>
</meta>
\ No newline at end of file
/* Application styles could go here */
footer .poweredby { float: right; margin: 33px 10px 0 0;}
footer .poweredby img { width: 120px; }
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title data-template="config:app-title">App Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta data-template="config:app-meta"/>
<link rel="shortcut icon" href="$shared/resources/images/exist_icon_16x16.ico"/>
<link rel="stylesheet" type="text/css" href="$shared/resources/css/bootstrap-3.0.3.min.css"/>
<link rel="stylesheet" type="text/css" href="resources/css/style.css"/>
<script type="text/javascript" src="$shared/resources/scripts/jquery/jquery-1.7.1.min.js"/>
<script type="text/javascript" src="$shared/resources/scripts/loadsource.js"/>
<script type="text/javascript" src="$shared/resources/scripts/bootstrap-3.0.3.min.js"/>
</head>
<body id="body">
<nav class="navbar navbar-default" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"/>
<span class="icon-bar"/>
<span class="icon-bar"/>
</button>
<a data-template="config:app-title" class="navbar-brand" href="./index.html">App Title</a>
</div>
<div class="navbar-collapse collapse" id="navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="dropdown" id="about">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Home</a>
<ul class="dropdown-menu">
<li>
<a href="index.html">Home</a>
</li>
</ul>
</li>
</ul>
</div>
</nav>
<div id="content" class="container"/>
<footer>
<a class="poweredby" href="http://exist-db.org">
<img src="$shared/resources/images/powered-by.svg" alt="Powered by eXist-db"/>
</a>
</footer>
</body>
</html>
\ No newline at end of file
xquery version "3.1";
import module namespace git="http://exist-db.org/apps/git/git" at "/db/apps/git/modules/git.xqm";
import module namespace config="http://exist-db.org/apps/git/config" at "/db/apps/git/modules/config.xqm";
declare variable $local:data-col := $config:app-root || "/" || $config:data-col;
declare variable $local:subcol := ();
declare variable $local:subcol-uri := if( $local:subcol ) then( "/" || $local:subcol ) else();
declare variable $local:file := "test01.xml";
declare variable $local:file-uri := if( $local:subcol ) then( $local:subcol || "/" || $local:file ) else( $local:file );
if( xmldb:touch( $local:data-col, $local:file-uri ))
then()
else(
if( xmldb:collection-available( $config:data-col || $local:subcol-uri ))
then()
else(
xmldb:create-collection( $config:app-root, $config:data-col || $local:subcol-uri )
),
xmldb:store( $local:data-col || $local:subcol-uri, $local:file, "<xml/>" )
),
util:wait( 5 ),
if( xmldb:touch( $local:data-col || $local:subcol-uri, $local:file ))
then(
git:delete-file( $config:file-repo-root, $config:app-root, $config:data-col, $local:file-uri )
)
else(
util:log( INFO, $local:data-col || $local:subcol-uri || "/" || $local:file || " could not be found" )
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment