Internal Command: Sync DOpus Settings to git

TL;DR

  • A script that syncs DOpus settings to a git repo
  • Backup only, no restore functionality
  • Thus mainly used for keeping track of changes in DOpus config
  • USE AT YOUR OWN RISK; NOT APPROVED BY GPSoft

Background / Usecase
I spend a lot time in the past weeks with coding several scripts for DOpus and editing menus. Usually I then created a backup with a title containing information about the changes. But this didnt give me a real chance to track the actual changes i made (sometimes i forgot to do a backup, then having several changes in the backup but just a description for one in the name of the backup file...).
Since DOpus keeps its internal configuration in text/xml files, git is an appropriate way of keeping track of changes (except for chache /*.db files, which are binary but also not really important).
So what this command does is copying configuration files from different paths into the directory of your local git repo, asking you for a commit title and message with a choice to select which changes to push to git (right now only from which directory, probably finer granularity in the future).
Sync Settings to Git UI
image
This is how a change can look like in a diff tool (github desktop in this case)

:warning: WARNING/DISCLAIMER :warning:
USE THIS ON YOUR OWN RISK! No warranty neither from my side nor GPSoft side.
Even though i guess DOpus does nothing else then copying these settings into an archive file / copying them back when restoring a backup.

GPSoftware Notes:
We don't really recommend modifying the toolbar XML files directly, by hand or by script, outside of exceptional situations, as it could cause problems with future versions.
-- Leo
In terms of providing some kind of "safety" I have not yet included a restore function, so in case you want to restore a backup from those changes kept on git you have to do it on your own, so that you are actually aware of what you are doing.
Those are warnings, i never encountered any problem in DOpus while making such a backup.

Requirements
You need to have installed git so it is callable from commandline or specifiy the path to git.exe in the script settings.
You also need to have a cloned/initialized (empty) repo / initialized to your drive and set this path in the scripts settings.

Any ideas or improvements? Let me know

Additional
In case somebody else is looking for a solution: This is an example for how to use DOpus dialogs and pass default values to it without needing a message loop (detaching and writing to control.value).

var GitExePath = "";
var RepoRootPath = "";
var CommitTitle = "";
var CommitMessage = "";
var CommitLocalData = false;
var CommitRoamingData = true;
var CommitVFSPlugins = false;
var LeaveGitWindowOpen = true;
var AddDopusVersion = true;
var UseDateAsDefaultCommitTitle = true;

function OnInit(initData)
{
	//uid added via script wizard (do not change after publishing this script)
	var uid = "34C71A05-F3F4-439C-BBE5-1499F0417375";
	initData.name = "Sync DOpus settings to git";
	initData.desc = "Copy DOpus settings to git repo, commit and push. Pulling not implemented so far for security/stability reasons.";
	initData.version = "1.0";
	initData.copyright = "(c) Felix Froemel 2021";	
	initData.url = "https://resource.dopus.com/t/internal-command-sync-dopus-settings-to-git/38026";
	initData.default_enable = true;
	
	var cfg = new ConfigHelper(initData);
	cfg.add("RepoPath", "<dir>").
		des("Path of the local initialized git repo that should be used for syncing.");
	cfg.add("CommitLocalData", false).
		des("Commiting data from %LocalAppData% which includes e.g. MRU and Cache. Those can be binary and not pretty useful with git.");
	cfg.add("CommitRoamingData", true).
		des("Commiting data from %AppData% which includes the most important files e.g. Buttons, Collections, Favourites, Filetypes, Icons, Menus, Rename Presets, Scripts,  ...");
	cfg.add("CommitVFSPlugins",	false).
		des("Commiting binary data from %PROGRAMFILES% which are DLLs used as DOpus Plugins.");
	cfg.add("UseDateAsDefaultCommitTitle",	true).
		des("When commiting use the current Date Time as default commit title");
	cfg.add("Git.exe", "").
		des("Path to Git.exe. Leave empty to use installed Git.exe available in your %path%.");
	cfg.add("AddDopusVersion", true)
		.des("Automatically insert the current DOpus version into the commit message to have it associated with this commit.");
	cfg.add("LeaveGitWindowOpen", true).
		des("Leave the git window open after push to see git output");
			
	var cmd = initData.AddCommand();
	cmd.name = "SyncSettingsToGit"
	cmd.method = "CommandSyncSettingsToGit";
	cmd.desc = "Synchronizes DOpus settings to git";
	cmd.template = "SyncSettingsToGit";
}

//Command entry method
function CommandSyncSettingsToGit(clickData)
{
	InstallPolyfills();
	
	GitExePath = Script.config["Git.exe"] || "Git.exe";
	RepoRootPath = Script.config["RepoPath"];
	CommitLocalData = Script.config["CommitLocalData"];
	CommitRoamingData = Script.config["CommitRoamingData"];
	CommitVFSPlugins = Script.config["CommitVFSPlugins"];
	LeaveGitWindowOpen = Script.config["LeaveGitWindowOpen"];
	AddDopusVersion = Script.config["AddDopusVersion"];
	UseDateAsDefaultCommitTitle = Script.config["UseDateAsDefaultCommitTitle"];
		
	if(!DOpus.FSUtil.Exists(RepoRootPath))
	{
		Log("RepoPath \"" + RepoRootPath + "\" does not exist, please set in script settings.");
		var dlgResult = DOpus.Dlg.Request("RepoPath \"" + RepoRootPath + "\" does not exist, please set in script settings.\nDo you want to go to settings now?", "Yes|No", "No valid repo path set", dlgWindow);
		Log(dlgResult);
		if(dlgResult == 1)
			DOpus.NewCommand.RunCommand("Prefs PAGE=scripts:Command.Git.SyncSettingsToGit.js"); // Go to settings of script
		return;
	}
	if(RepoRootPath[RepoRootPath.length - 1] != '\\') //Does not end with => append '\'
		RepoRootPath = RepoRootPath + "\\";

	var dlgWindow = clickData.func.sourcetab;
	if(EnterCommitDetails(dlgWindow))
	{
		CopyFilesToRepo();
		CommitFiles();	
	}
}

function EnterCommitDetails(sourceTab)
{
	var dlg = DOpus.Dlg;
	dlg.window = sourceTab;
	dlg.template = "GitCommitDetails";
	dlg.detach = true;
	dlg.Show();
	if(UseDateAsDefaultCommitTitle)
	{
		dlg.Control("editTitle").value = DOpus.Create().Date().Format("D#yyyy.MM.dd T#HH:mm:ss");
		dlg.Control("editTitle").focus(true);
	}
	else 
		dlg.Control("editMessage").focus(true);
	dlg.Control("commitLocalFiles").value = CommitLocalData;
	dlg.Control("commitRoamingFiles").value = CommitRoamingData;
	dlg.Control("commitPlugins").value = CommitVFSPlugins;
	if(AddDopusVersion)
		dlg.Control("editMessage").value = "DOpus Product version: " + DOpus.version.product + "\nDOpus Module version: " + DOpus.version.module + "\n";

	var dialogResult = dlg.RunDlg(); //dialog was detached to set default values, no need for detached anymore, convert to non detached
	if(dialogResult == 1) //ok
	{
		CommitTitle = dlg.Control("editTitle").value;
		CommitMessage = String(dlg.Control("editMessage").value).replaceAll("\n", " ");
		CommitLocalData = dlg.Control("commitLocalFiles").value;
		CommitRoamingData = dlg.Control("commitRoamingFiles").value;
		CommitVFSPlugins = dlg.Control("commitPlugins").value;
		if(CommitTitle != "")
			return true;
	}
	return false;
}

//Run the commands to copy all DOpus file to the local git repo directory
function CopyFilesToRepo()
{
	CreateDataDirectories();
	CreateGitAttributes();
	var dopusPath = "\\GPSoftware\\Directory Opus";
	if(CommitLocalData)
	{
		var cmd = DOpus.Create.Command;
		cmd.SetSource(DOpus.FSUtil.Resolve("/LocalAppData") + dopusPath);
		//cmd.SetSource("%LocalAppData%\\GPSoftware\\Directory Opus");
		cmd.AddLine("Copy * TO " + RepoRootPath + "LocalAppData unattended=no WHENEXISTS=replace");
		cmd.Run();
	}
	if(CommitRoamingData)
	{
		var cmd = DOpus.Create.Command;
		cmd.SetSource(DOpus.FSUtil.Resolve("/APPDATA") + dopusPath);
		//cmd.SetSource("%APPDATA%\\GPSoftware\\Directory Opus");
		cmd.AddLine("Copy * TO " + RepoRootPath + "RoamingData unattended=no WHENEXISTS=replace");
		cmd.Run();
	}
	if(CommitVFSPlugins)
	{
		var cmd = DOpus.Create.Command;
		cmd.SetSource(DOpus.FSUtil.Resolve("/PROGRAMFILES") + dopusPath);
		//cmd.SetSource("%PROGRAMFILES%\\GPSoftware\\Directory Opus");
		cmd.AddLine("Copy * TO " + RepoRootPath + "Plugins unattended=no WHENEXISTS=replace");
		cmd.Run();
	}
}

//Assure that the directories in repo directory exist
function CreateDataDirectories()
{
	var cmd = DOpus.Create.Command;
	cmd.SetSource(RepoRootPath);
	cmd.AddLine("CreateFolder LocalAppData");
	cmd.AddLine("CreateFolder RoamingData");
	cmd.AddLine("CreateFolder Plugins");
	cmd.Run();
}

//Create a git attributes to treat dopus files as text for diff tools
//For me not yet fully working
function CreateGitAttributes()
{	
	var fso = new ActiveXObject("Scripting.FilesystemObject");
    var gitattributes = fso.CreateTextFile(RepoRootPath + "\\.gitattributes", true);
    gitattributes.WriteLine("* text=auto");
	gitattributes.WriteLine("*.js text"); 	//Javascript
	gitattributes.WriteLine("*.vbs text");	//VisualBasic
	gitattributes.WriteLine("*.txt text");	//Text
	gitattributes.WriteLine("*.xml text");	//XML
	gitattributes.WriteLine("*.col text");	//DOpus Collection file
	gitattributes.WriteLine("*.dop text");	//DOpus Button File
	gitattributes.WriteLine("*.off text");	//DOpus Folder Format File
	gitattributes.WriteLine("*.omd text");	//DOpus Metadata File
	gitattributes.WriteLine("*.osy text");	//DOpus ??? File
	gitattributes.WriteLine("*.otg text");	//DOpus Tabgroup File
	gitattributes.WriteLine("*.oxc text");	//DOpus Colorgroups/Scriptsettings File
	gitattributes.WriteLine("*.oxr text");	//DOpus Filetype File
    gitattributes.Close();
}

//Commit and push files 
function CommitFiles()
{
	var cmd = DOpus.Create.Command;
	if(LeaveGitWindowOpen)
		cmd.SetModifier("leavedoswindowopen");
	cmd.SetModifier("externalonly"); //dont use internal git, didnt work for me
	cmd.SetType("msdos"); //make msdos cmd
	cmd.AddLine("cd " + RepoRootPath);
	cmd.AddLine(GitExePath + " add *");
	cmd.AddLine(GitExePath + " commit -m \"" + CommitTitle + "\" -m \"" + CommitMessage + "\"");
	cmd.AddLine(GitExePath + " push");
	cmd.Run();
}

function Log(msg)
{
	DOpus.Output("Synt Settings to git: " + String(msg));
} 

// Install string.replaceAll
function InstallPolyfills()
{
	/**
	 * String.prototype.replaceAll() polyfill
	 * https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
	 * @author Chris Ferdinandi
	 * @license MIT
	 */
	if (!String.prototype.replaceAll) 
	{
		String.prototype.replaceAll = function(str, newStr)
		{
			// If a regex pattern
			if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') 
			{
				return this.replace(str, newStr);
			}

			// If a string
			return this.replace(new RegExp(str, 'g'), newStr);
		};
	}
}

function ConfigHelper(data){ //v1.2
		var t=this; t.d=data; t.c=data.config; t.cd=DOpus.Create.Map();
		t.add=function(name, val, des){ t.l={n:name,ln:name.
			toLowerCase()}; return t.val(val).des(des);}
		t.des=function(des){ if (!des) return t; if (t.cd.empty)
			t.d.config_desc=t.cd; t.cd(t.l.n)=des; return t;}
		t.val=function(val){ var l=t.l; if (l.v!==l.x&&typeof l.v=="object")
			l.v.push_back(val);else l.v=t.c[l.n]=val;return t;}
		t.trn=function(){return t.des(t("script.config."+t.l.ln));}
}

///////////////////////////////////////////////////////////////////////////////
function OnAboutScript(data){ //v0.1
	var cmd = DOpus.Create.Command();
	if (!cmd.Commandlist('s').exists("ScriptWizard")){
		if (DOpus.Dlg.Request("The 'ScriptWizard' add-in has not been found.\n\n"+
"Install 'ScriptWizard' from [resource.dopus.com].\nThe add-in enables this dialog and also offers "+
"easy updating of scripts and many more.","Yes, take me there!|Cancel", "No About.. ", data.window))
		cmd.RunCommand('http://resource.dopus.com/viewtopic.php?f=35&t=23179');}
	else
		cmd.RunCommand('ScriptWizard ABOUT WIN='+data.window+' FILE="'+Script.File+'"');
}


==SCRIPT RESOURCES
<resources>
	<resource name="GitCommitDetails" type="dialog">
		<dialog fontsize="8" height="157" lang="english" title="Sync Settings to Git" width="220">
			<control halign="left" height="8" name="static1" title="Commit Title" type="static" width="61" x="8" y="7" />
			<control halign="left" height="8" name="static2" title="Commit Description" type="static" width="68" x="8" y="20" />
			<control halign="left" height="12" name="editTitle" tip="Commit Title" type="edit" width="133" x="80" y="4" />
			<control halign="left" height="61" multiline="yes" name="editMessage" type="edit" width="133" x="80" y="19" />
			<control close="1" default="yes" height="14" name="buttonOK" title="OK" type="button" width="50" x="8" y="138" />
			<control close="0" height="14" name="buttonCancel" title="Cancel" type="button" width="50" x="164" y="138" />
			<control height="10" name="commitLocalFiles" title="Commit Local File (MRU, Cache)" type="check" width="132" x="14" y="93" />
			<control checked="yes" height="10" name="commitRoamingFiles" title="Commit Roaming Data (Scripts, Menus, Collections...)" type="check" width="192" x="14" y="106" />
			<control checked="yes" height="10" name="commitPlugins" title="Commit Plugins (Viewers...)" type="check" width="132" x="14" y="119" />
			<control height="49" name="group1" title="Select what to commit" type="group" width="204" x="9" y="83" />
		</dialog>
	</resource>
</resources>
//MD5 = "3d5bd4f614ec63b56476f3e307d0c5eb"; DATE = "2021.03.09 - 10:16:44"

Installation:
To install the command, download the *.js.txt file below and drag it to Preferences / Toolbars / Scripts (probably rename to make sure auto opening of settings will work) or copy code to "%USERPROFILE%\AppData\Roaming\GPSoftware\Directory Opus\Script AddIns" as "Command.Git.SyncSettingsToGit.js".

Sample Button
Puts this command as a submenu button of "Backup & Restore"

<?xml version="1.0"?>
<button always_enable="yes" backcol="none" display="both" label_pos="right" separate="yes" textcol="none" type="menu_button">
	<label>Backup and Restore...</label>
	<tip>Backup and Restore your Directory Opus configuration</tip>
	<icon1>#saveprefs</icon1>
	<function type="normal">
		<instruction>Prefs BACKUPRESTORE</instruction>
	</function>
	<button backcol="none" display="both" label_pos="right" textcol="none">
		<label>Sync DOpus settings</label>
		<icon1>#DOpus9:checkforupdates</icon1>
		<icon2>/programfiles/Git/git-bash.exe,0</icon2>
		<function type="normal">
			<instruction>SyncSettingsToGit</instruction>
		</function>
	</button>
</button>

Current Version
v 1.0 Initial Version (2021-03-08)Command.Git.SyncSettingsToGit.js.txt (22.1 KB)

1 Like