Skip to content

Instantly share code, notes, and snippets.

@l-Luna
Created March 21, 2025 11:58
Show Gist options
  • Save l-Luna/3d1963bd363002e361085a2cfb05738e to your computer and use it in GitHub Desktop.
Save l-Luna/3d1963bd363002e361085a2cfb05738e to your computer and use it in GitHub Desktop.
simple git script for FMOD Studio
// util functions
function newLineFixup(text){
return text.replace(/\r\n/g, "\n");
}
function projectRoot(){
var filePath = studio.project.filePath;
return filePath.substr(0, filePath.lastIndexOf("/")) + "/";
}
function gitRoot(){
return runGit(["rev-parse", "--show-toplevel"]).standardOutput;
}
// running git commands
// args: array of string arguments (e.g. ["status", "--porcelain"])
// optional result: { exitCode: number, standardOutput: string, standardError: string }
// if result is specified, it's merged with the new output
function runGit(args, result){
result = result ? result : { exitCode: 0, standardOutput: "", standardError: "" };
var options = { workingDir: projectRoot(), timeout: 100000, args: args };
var executable = /* gitSettings.gitExecutable ? gitSettings.gitExecutable : */ "git";
var desc = args.map(it => it.includes(" ") ? '"' + it + '"' : it).join(" ");
try{
var newResult = studio.system.start(executable, options);
result.exitCode = newResult.exitCode;
result.standardOutput += newLineFixup(newResult.standardOutput);
result.standardError += newLineFixup(newResult.standardError);
console.log("runGit: " + desc + ":\n" + newResult.standardOutput);
}catch(err){
result.exitCode = -1;
if(err.errorCode === studio.system.processError.FailedToStart)
result.standardError = "Failed to start the Git command-line client. Please make sure the Git command-line client has been installed on your system.";
else
result.standardError = err.errorString;
}
if(result.exitCode !== 0){
console.error("runGit: error running command: " + /*hidePassword*/ desc);
console.error("runGit: " + result.standardError);
}
return result;
}
// parsing git status
// git status entry: { filename: string, mode: string }
var filemodes = {
' ': { desc: "unmodified", colour: "White" },
'M': { desc: "modified", colour: "Gold" },
'T': { desc: "changed type", colour: "DarkOrange" },
'A': { desc: "added", colour: "Green" },
'D': { desc: "deleted", colour: "#8a0500" },
'R': { desc: "renamed", colour: "DarkOrange" },
'C': { desc: "copied", colour: "DarkOrange" },
'U': { desc: "unmerged", colour: "Red" },
'?': { desc: "unadded", colour: "LightGray" }
}
// ...and "dirty"
function parseEntry(entry){
var fst = entry[0];
if(fst == '#'){
return undefined;
}
var snd = entry[1];
var rst = entry.substr(3);
var mode = filemodes[snd];
if(fst != ' ' && fst != '?'){
if(fst == "D" && snd == " ")
mode = { desc: "deleted from git", colour: "#8a0500" };
else
mode = { desc: "dirty", colour: "Red" };
}
return { filename: rst, mode: mode };
}
// returns { entries: [git entry], branch: string, upstreamBranch: string }
function parseStatus(text){
var branch = "";
var upstreamBranch = "";
lines = text.split("\n");
if(lines[0].startsWith("##")){
var branchLine = lines[0].substr(3);
var parts = branchLine.split("...");
branch = parts[0];
upstreamBranch = parts[1];
lines.shift();
}
return { entries: lines.filter(it => it.length > 0).map(parseEntry), branch: branch, upstreamBranch: upstreamBranch };
}
// UI
studio.menu.addMenuItem({
name: "Git Status",
execute: function(){
// make sure project is saved first
studio.project.save();
// get git fetch data
var result = runGit(["status", "--porcelain", "--branch"]);
var data = parseStatus(result.standardOutput);
// find diff between branches
var diffResult = runGit(["rev-list", "--count", "HEAD.." + data.upstreamBranch]);
var diff = parseInt(diffResult.standardOutput.trim());
var diffText = (diff == 0 ? "Up to date with <tt>{0}</tt>" : "<font color=\"Red\">Behind <tt>{0}</tt> by {1} commits</font>").format(data.upstreamBranch, diff);
studio.ui.showModalDialog({
windowTitle: "Git Status",
windowWidth: 420,
windowHeight: 600,
widgetType: studio.ui.widgetType.Layout,
layout: studio.ui.layoutType.VBoxLayout,
spacing: 5,
contentsMargins: { left: 10, right: 10, top: 10, bottom: 10 },
items: [
{
alignment: studio.ui.alignment.AlignTop,
stretchFactor: 0,
widgetType: studio.ui.widgetType.Label,
text: "Current branch: <tt>" + data.branch + "</tt> <br />" + diffText + "</tt>"
},
{
alignment: studio.ui.alignment.AlignHCenter,
stretchFactor: 0,
minimumWidth: 400,
sizePolicy: { horizontalPolicy: studio.ui.sizePolicy.Expanding, verticalPolicy: studio.ui.sizePolicy.Fixed },
widgetType: studio.ui.widgetType.Label,
text: "<hr />"
},
{
alignment: studio.ui.alignment.AlignTop,
stretchFactor: 0,
widgetType: studio.ui.widgetType.Label,
text: "{0} changed file(s)".format(data.entries.length)
},
{
alignment: studio.ui.alignment.AlignTop,
stretchFactor: 1,
maximumHeight: 500,
sizePolicy: studio.ui.sizePolicy.Maximum,
widgetType: studio.ui.widgetType.Layout,
layout: studio.ui.layoutType.VBoxLayout,
contentsMargins: { left: 0, right: 0, top: 10, bottom: 10 },
spacing: 0,
items: data.entries.map((it, i) => {
var titleText = "";
// special case event appearances
var eventRegex = /\/Metadata\/[a-zA-Z]*\/{([a-zA-Z0-9\-]+)}.xml$/;
var eventUuid = it.filename.match(eventRegex);
if(eventUuid && eventUuid.length > 1){
var entity = studio.project.lookup(eventUuid[1]);
titleText = "<span style=\"text-decoration: underline;\">" + entity.entity + ": " + entity.name + "</span><br />";
}
// alternate colours
var c = i % 2 ? "dimgray" : "gray";
return {
widgetType: studio.ui.widgetType.Label,
text: "<div style=\"background-color: {2};\"> {4}<tt>{0}</tt> </div> <div style=\"text-align: right; background-color: {2};\"> <font color=\"{3}\" style=\"\">{1}</font> </div>".format(it.filename, it.mode.desc, c, it.mode.colour, titleText)
};
})
},
{
alignment: studio.ui.alignment.AlignRight,
widgetType: studio.ui.widgetType.PushButton,
text: "Pull",
onClicked: function(){
var result = runGit(["pull", "--ff-only", "-q"]);
if(result.exitCode != 0)
alert("Failed to pull cleanly; there may be file conflicts.");
else
alert("Pulled successfully!");
this.closeDialog();
},
isEnabled: diff != 0
},
{
alignment: studio.ui.alignment.AlignBottom,
stretchFactor: 0,
widgetType: studio.ui.widgetType.Layout,
layout: studio.ui.layoutType.HBoxLayout,
contentsMargins: { left: 0, right: 0, top: 0, bottom: 0 },
items: [
{
alignment: studio.ui.alignment.AlignLeft,
minimumWidth: 260,
sizePolicy: { horizontalPolicy: studio.ui.sizePolicy.MinimumExpanding, verticalPolicy: studio.ui.sizePolicy.Fixed },
widgetId: "commit_message",
widgetType: studio.ui.widgetType.LineEdit,
isEnabled: diff == 0 && data.entries.length > 0
},
{
alignment: studio.ui.alignment.AlignRight,
widgetType: studio.ui.widgetType.PushButton,
text: "Commit All and Push",
onClicked: function(){
runGit(["add", "."]);
var commitMessage = this.findWidget("commit_message").text();
if(commitMessage.trim() == ""){
alert("Add a non-empty commit message first.");
return;
}
var result = runGit(["commit", "-qam", commitMessage]);
if(result.exitCode != 0){
alert("Failed to commit; send your log to the script developer.");
return;
}
var result = runGit(["push", "-q", "--no-verify"]);
if(result.exitCode != 0)
alert("Failed to push; send your log to the script developer.");
else
alert("Pushed changes successfully!");
this.closeDialog();
},
isEnabled: diff == 0 && data.entries.length > 0
}
]
}
]
});
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment