X-Git-Url: http://git.roojs.org/?p=gitlive;a=blobdiff_plain;f=GitRepo.vala;h=fa5ce1a7c17332072d2ae81d70e7035a78f57913;hp=a12a71970582b95c58b1177fdb781657b2c2d4d2;hb=HEAD;hpb=c809aa64f9643fee8914881313a557aca790e741 diff --git a/GitRepo.vala b/GitRepo.vala index a12a7197..a4d1bfd3 100644 --- a/GitRepo.vala +++ b/GitRepo.vala @@ -7,24 +7,62 @@ * * */ +static GitRepo _GitRepo; + public class GitRepo : Object { - - public Array cmds; + + public Gee.ArrayList cmds; public string name; public string gitdir; public string git_working_dir; public bool debug = false; + public bool has_local_changes = false; + public string host = ""; + public string git_status; + public string git_diff; + public string ahead_or_behind = ""; public Gee.HashMap ignore_files; + + public GitBranch _currentBranch; + public GitBranch getCurrentBranch() { + if (this._currentBranch == null) { + Posix.usleep(100); // try and slow down branch loading + this.loadBranches(); + } + if (this._currentBranch == null) { + GLib.error("could not work out current branch? : %s ",this.git_working_dir ); + } + return this._currentBranch; + } + + + public Gee.HashMap branches; // accessed in GitBranch.. + public RooTicket? activeTicket; + public Gee.HashMap cache; + public Gee.HashMap config_cache; + + public Ggit.Repository repo; + public Ggit.RemoteHead[] remote_heads = null; + + public static GitRepo singleton() + { + if (_GitRepo == null) { + _GitRepo = new GitRepo.single(); + _GitRepo.cache = new Gee.HashMap(); + } + return _GitRepo; + } + /** * index of.. matching gitpath.. */ public static int indexOf( Array repos, string gitpath) { // make a fake object to compare against.. - var test_repo = new GitRepo(gitpath); + var test_repo = GitRepo.get_sync(gitpath); for(var i =0; i < repos.length; i++) { if (repos.index(i).gitdir == test_repo.gitdir) { @@ -35,15 +73,18 @@ public class GitRepo : Object } + + + public static Array list() { - + //if (GitRepo.list_cache != null) { // unowned Array ret = GitRepo.list_cache; // return ret; //} - + var cache = GitRepo.singleton().cache; var list_cache = new Array(); var dir = Environment.get_home_dir() + "/gitlive"; @@ -101,19 +142,53 @@ public class GitRepo : Object continue; } - list_cache.append_val(new GitRepo( sp )) ; - + var rep = GitRepo.get_sync( sp ); + list_cache.append_val(rep); } return list_cache; - - -} - - - + } + + public static GitRepo get_sync(string path) + { + GitRepo ret; + var cache = GitRepo.singleton().cache; + if (cache.has_key(path)) { + ret = cache.get(path); + } else { + ret = new GitRepo(path); + } + return ret; + } + + public static async GitRepo? get(string path) + { + + SourceFunc callback = GitRepo.get.callback; + GitRepo ret = null; + ThreadFunc run = () => { + + var cache = GitRepo.singleton().cache; + if (cache.has_key(path)) { + ret = cache.get(path); + } else { + ret = new GitRepo(path); + } + Idle.add((owned) callback); + return true; + + }; + new Thread("thread-new-gitrepo-" + path, run); + yield; + return ret; + + } + + private GitRepo.single() { + // used to create the signleton + } /** * constructor: * @@ -122,7 +197,8 @@ public class GitRepo : Object * */ - public GitRepo(string path) { + private GitRepo(string path) + { // cal parent? this.name = File.new_for_path(path).get_basename(); this.ignore_files = new Gee.HashMap(); @@ -132,31 +208,524 @@ public class GitRepo : Object if (!FileUtils.test(this.gitdir , FileTest.IS_DIR)) { this.gitdir = path; // naked... } - this.cmds = new Array (); - //Repo.superclass.constructor.call(this,cfg); + this.cmds = new Gee.ArrayList (); + var cache = GitRepo.singleton().cache; + //Repo.superclass.constructor.call(this,cfg); + if ( !cache.has_key(path) ) { + cache.set( path, this); + } + + this.repo = Ggit.Repository.open(GLib.File.new_for_path(path)); + + var r = this.repo.lookup_remote("origin"); + + + //var r = this.git({ "remote" , "get-url" , "--push" , "origin"}); + var uri = new Soup.URI(r.get_url()); + this.host = uri.get_host(); + this.init_config(); + + this.loadBranches(); + this.loadActiveTicket(); + this.loadStatus(); } + public bool is_master_branch() + { + // special branches that do not allow autopushing now... + return this.getCurrentBranch().name == "master" || this.getCurrentBranch().name == "roojs"; + + } + public void init_config() + { + this.config_cache = new Gee.HashMap(); + // managed = + if (this.get_config("managed") == "") { + this.set_config("managed", this.host == "git.roojs.com" ? "1" : "0"); + } + if (this.get_config("autocommit") == "") { + this.set_config("autocommit", this.host == "git.roojs.com" ? "1" : "0"); + } + if (this.get_config("autopush") == "") { + this.set_config("autopush", this.host == "git.roojs.com" ? "1" : "0"); + } + + } + + + + public string get_config(string key) + { + + if (this.config_cache.has_key(key)) { + //GLib.debug("get_config %s = '%s'", key, this.config_cache.get(key)); + return this.config_cache.get(key); + } + try { + var cfg = this.repo.get_config().snapshot(); + var ret = cfg.get_string("gitlive." + key).strip(); + return ret; + } catch (Error e) { + this.repo.get_config().set_string("gitlive." + key, ""); + //this.config_cache.set(key, ""); + //GLib.debug("get_config (fail) %s = '%s'", key, ""); + return ""; // happens when there is nothing set... + } + + } + public void set_config(string key, string value) + { + this.repo.get_config().set_string("gitlive." + key, value); + //this.git({ "config" , "gitlive." + key, value }); + //this.config_cache.set(key,value); + } + + public bool is_managed() + { + return this.get_config("managed") == "1"; + } + public bool is_autocommit () + { + return this.get_config("autocommit") == "1"; + } + + public void set_autocommit(bool val) + { + this.set_config("autocommit", val ? "1" : "0"); + + } + + public bool is_auto_branch () + { + if (this.name == "gitlog") { + return false; + } + // check remote... + if (this.is_managed()) { + return true; + } + return false; + + + } + + public void set_auto_branch(bool val) + { + if (this.name == "gitlog") { + return; + } + this.set_config("managed", val ? "1" : "0"); + + } + public bool is_autopush () + { + return this.get_config("autopush") == "1"; + } + public void set_autopush(bool val) { - return !FileUtils.test(this.gitdir + "/".gitlive-disable-autocommit , FileTest.IS_DIR); + this.set_config("autopush", val ? "1" : "0"); } + + public void loadStatus() + { + // status??/ + var r = this.git({ "status" , "--porcelain" }); + this.git_status = r; + this.has_local_changes = r.length > 0; + + //var rs = this.git({ "status" , "-sb" }); + var cb = this.getCurrentBranch(); + + this.ahead_or_behind = cb.ahead > 0 ? "A" : (cb.behind > 0 ? "B" : ""); + + + this.git_diff = this.diffWorking(); + } + + + public void loadBranches() + { + + GitBranch.loadBranches(this); + } + + + public bool hasBranchCalled(string name) + { + foreach( var br in this.branches.values) { + if (br.name == name) { + return true; + } + + } + return false; + } + + public string branchesToString() + { + var ret = ""; + foreach( var br in this.branches.values) { + if (br.name == "") { + continue; + } + var col = /hours/.match(br.age) ? "#ff851b" : "#ffdc00"; // orange or yellow + ret += ret.length > 0 ? "\n" : ""; + ret += br.name + " " + br.age + ""; + + } + return ret; + + } + public static void doMerges(string action, string ticket_id, string commit_message) + { + GitMonitor.gitmonitor.stop(); + + var commitrevs = ""; + var sucess = true; + foreach(var repo in GitRepo.singleton().cache.values) { + if (repo.activeTicket != null && repo.activeTicket.id == ticket_id) { + var res = repo.doMerge(action,ticket_id, commit_message); + if (!res) { + sucess = false; + continue; + } + commitrevs += commitrevs.length > 0 ? " " : ""; + commitrevs += repo.getCurrentBranch().lastrev; + } + } + if (sucess && action == "CLOSE") { + RooTicket.singleton().getById(ticket_id).close(commitrevs); + } + GitMonitor.gitmonitor.start(); + } + + + public bool doMerge(string action, string ticket_id, string commit_message) + { + // in theory we should check to see if other repo's have got the same branch and merge all them at the same time. + // also need to decide which branch we will merge into? + var master = this.hasBranchCalled("roojs") ? "roojs" : "master"; + + var ret = ""; + if (action == "CLOSE" || action == "LEAVE") { + + try { + var oldbranch = this.getCurrentBranch().name; + this.setActiveTicket(null, master); + string [] cmd = { "merge", "--squash", oldbranch }; + this.git( cmd ); + cmd = { "commit", "-a" , "-m", commit_message }; + this.git( cmd ); + this.push(); + this.loadBranches(); // updates lastrev.. + + var notification = new Notify.Notification( + "Merged branch %s to %s".printf(oldbranch, master), + "", + "dialog-information" + + ); + + notification.set_timeout(5); + notification.show(); + + // close ticket.. + return true; + + } catch (Error e) { + + GitMonitor.gitmonitor.pauseError(e.message); + return false; + } + // error~?? -- show the error dialog... + return false; + } + if (action == "MASTER") { + // merge master into ours.. + try { + string[] cmd = { "merge", master}; + this.git( cmd ); + var notification = new Notify.Notification( + "Merged code from %s to %s".printf(master,this.getCurrentBranch().name), + "", + "dialog-information" + + ); + notification.set_timeout(5); + notification.show(); + + return true; + } catch (Error e) { + GitMonitor.gitmonitor.pauseError(e.message); + return false; + } + } + if (action == "EXIT") { + try { + var oldbranch = this.getCurrentBranch().name; + this.setActiveTicket(null, master); + this.loadBranches(); + var notification = new Notify.Notification( + "Left branch %s".printf(oldbranch), + "", + "dialog-information" + + ); + notification.set_timeout(5); + notification.show(); + + return true; + } catch (Error e) { + GitMonitor.gitmonitor.pauseError(e.message); + + return false; + } + // error~?? -- show the error dialog... + + } + return false; + } + + public void loadActiveTicket() + { + this.activeTicket = null; + var ticket_id = this.get_config("ticket"); + + if (ticket_id.length < 1) { + return; + } + this.activeTicket = RooTicket.singleton().getById(ticket_id.strip()); + + + } + + + + public bool setActiveTicket(RooTicket? ticket, string branchname) + { + this.set_config("ticket", ""); + if (!this.createBranchNamed(branchname)) { + return false; + } + this.set_config("ticket", ticket == null ? "": ticket.id); + this.activeTicket = ticket; + return true; + } + + public bool createBranchNamed(string branchname) + { + + + if (this.branches.has_key(branchname)) { + this.switchToExistingBranchNamed(branchname); + + } else { + this.createNewBranchNamed(branchname); + + } + var notification = new Notify.Notification( + "Changed to branch %s".printf(branchname), + "", + "dialog-information" + + ); + + notification.set_timeout(5); + notification.show(); + + + this.loadBranches(); // update branch list... + //GitMonitor.gitmonitor.runQueue(); // no point - we have hidden the queue.. + return true; + } + bool switchToExistingBranchNamed(string branchname) + { + var master = this.hasBranchCalled("roojs") ? "roojs" : "master"; + var stash = false; + // this is where it get's tricky... + string files = ""; + try { + string[] cmd = { "ls-files" , "-m" }; // list the modified files.. + files = this.git(cmd); + stash = files.length> 1 ; + + + cmd = { "stash" }; + if (stash) { this.git(cmd); } + + this.pull(); + + cmd = { "checkout", branchname }; + this.git(cmd); + } catch(Error e) { + GitMonitor.gitmonitor.pauseError(e.message); + return false; + } + try { + if (branchname != master) { + string[] cmd = { "merge", master }; + this.git(cmd); + this.push(); + + } + + } catch(Error e) { + string[] cmd = { "checkout", master }; + this.git(cmd); + GitMonitor.gitmonitor.pauseError( + "Use\n\na) git checkout %s\nb) git mergetool\nc) git commit\nd) git push\n d) stash pop \ne) start gitlive again\n".printf( + branchname) + + e.message + ); + return false; + + } + try { + string[] cmd = { "stash", "pop" }; + if (stash) { + this.git(cmd); + var fl = files.split("\n"); + cmd = { "commit", "-m" , "Changed " + string.joinv("",fl) }; + foreach(var f in fl) { + if (f.length < 1) continue; + cmd += f; + } + this.git(cmd); + } + + + + } catch(Error ee) { + GitMonitor.gitmonitor.pauseError(ee.message); + return false; + } + this.push(); + return true; + + } + + + + bool createNewBranchNamed(string branchname) + { + var stash = false; + try { + string[] cmd = { "ls-files" , "-m" }; // list the modified files.. + var files = this.git(cmd); + stash = files.length> 1 ; + + cmd = { "checkout", "-b" , branchname }; + this.git(cmd); + + cmd = { "push", "-u" , "origin" ,"HEAD" }; + this.git(cmd); + if (stash) { + + var fl = files.split("\n"); + cmd = { "commit", "-m" , "Changed " + string.joinv("",fl) }; + foreach(var f in fl) { + if (f.length < 1) continue; + cmd += f; + } + this.git(cmd); + this.push(); + } + + + } catch(Error ee) { + GitMonitor.gitmonitor.pauseError(ee.message); + return false; + } + return true; + + } + + public static string previewMerges(string ticket_id) + { + var ret = ""; + foreach(var repo in GitRepo.singleton().cache.values) { + if (repo.activeTicket == null || repo.activeTicket.id != ticket_id) { + continue; + } + ret += repo.previewMerge() + "\n\n"; + + } + return ret; + + } + + + + public string previewMerge() + { + try { + var master = this.hasBranchCalled("roojs") ? "roojs" : "master"; + var lines = this.git({"log", master + "...", "--pretty=format:%H %P" }).split("\n");; + var head = this.git({"log", "-1", "--pretty=format:%H %P" }); + var start = head.split(" ")[0]; + var end = ""; + for (var i = 0; i < lines.length; i++) { + var cols = lines[i].split(" "); + if (cols.length > 2) { + end = cols[0]; + break; + } + } + if (end == "") { + var last = lines.length > 0 ? lines[lines.length-1] : ""; + end = last.split(" ")[1]; + } + + return this.git({ "diff" , (end+".."+start), "--no-color" }); + } catch(Error ee) { + GitMonitor.gitmonitor.pauseError(ee.message); + return "Error getting diff"; + } + + } + + + public string diffWorking() + { + var cb = this.getCurrentBranch(); + GLib.debug("Calling diff working"); + var diff = new Ggit.Diff.tree_to_workdir(this.repo, cb.getTree(), new Ggit.DiffOptions()); + var ret = ""; + diff.print(Ggit.DiffFormatType.PATCH, (delta, hunk, line) => { + switch(line.get_origin()) { + case Ggit.DiffLineType.ADDITION: ret+="+"; break; + case Ggit.DiffLineType.DELETION: ret+="-";break; + case Ggit.DiffLineType.CONTEXT: ret+=" ";break; + case Ggit.DiffLineType.HUNK_HDR: break; + case Ggit.DiffLineType.FILE_HDR: break; + default: ret+=" ";break; + } + ret += " " + line.get_text() ; + return 0; + }); + //GLib.debug("returning %s", ret); + return ret; + } + + /** * add: * add files to track. * * @argument {Array} files the files to add. */ - public string add ( Array files ) throws Error, SpawnError + public string add ( Gee.ArrayList files ) throws Error, SpawnError { // should really find out if these are untracked files each.. // we run multiple versions to make sure that if one failes, it does not ignore the whole lot.. // not sure if that is how git works.. but just be certian. var ret = ""; - for (var i = 0; i < files.length;i++) { - var f = files.index(i).vname; + for (var i = 0; i < files.size;i++) { + var f = files.get(i).vname; try { string[] cmd = { "add", f }; this.git( cmd ); @@ -196,7 +765,7 @@ public class GitRepo : Object * * @argument {Array} files the files to add. */ - public string remove ( Array files ) throws Error, SpawnError + public string remove ( Gee.ArrayList files ) throws Error, SpawnError { // this may fail if files do not exist.. // should really find out if these are untracked files each.. @@ -204,8 +773,8 @@ public class GitRepo : Object // not sure if that is how git works.. but just be certian. var ret = ""; - for (var i = 0; i < files.length;i++) { - var f = files.index(i).vname; + for (var i = 0; i < files.size;i++) { + var f = files.get(i).vname; try { string[] cmd = { "rm", "-f" , f }; this.git( cmd ); @@ -233,7 +802,7 @@ public class GitRepo : Object * */ - public string commit ( string message, Array files ) throws Error, SpawnError + public string commit ( string message, Gee.ArrayList files ) throws Error, SpawnError { @@ -257,8 +826,8 @@ public class GitRepo : Object */ string[] args = { "commit", "-m" }; args += (message.length > 0 ? message : "Changed" ); - for (var i = 0; i< files.length ; i++ ) { - args += files.index(i).vname; // full path? + for (var i = 0; i< files.size ; i++ ) { + args += files.get(i).vname; // full path? } return this.git(args); @@ -285,7 +854,7 @@ public class GitRepo : Object public void pull_async(GitAsyncCallback cb) { - string[] cmd = { "pull" , "--no-edit" }; + string[] cmd = { "pull" , "--no-edit" }; this.git_async( cmd , cb); @@ -302,7 +871,7 @@ public class GitRepo : Object public string push () throws Error, SpawnError { // should - return this.git({ "push", "origin", "HEAD" }); + return this.git({ "push" }); } @@ -346,16 +915,19 @@ public class GitRepo : Object //if (File.exists(this.repo + '/.git/config')) { //env.push("GITPATH=" + this.repo ); //} - - + var cfg = new SpawnConfig(this.git_working_dir , args , env); - + //cfg.debug = true; // may throw error... var sp = new Spawn(cfg); - - GLib.debug( "GOT: %s" , sp.output); + //GLib.debug( "GOT result: %d" , sp.result); + + // diff output is a bit big.. + if (args_in[0] != "diff") { + GLib.debug( "GOT: %s" , sp.output); + } // parse output for some commands ? return sp.output; } @@ -416,4 +988,158 @@ public class GitRepo : Object } + + public async void doUpdate() + { + SourceFunc callback = this.doUpdate.callback; + GitRepo ret = null; + ThreadFunc run = () => { + + + // update the branches.. + this.loadBranches(); + + //GLib.debug("connecting '%s'", r.get_url()); + string[] far = {}; + foreach(var br in this.branches.values) { + if (br.remote == "" || br.remoterev == br.lastrev) { + continue; + } + far += ("+refs/heads/" + br.name + ":refs/remotes/" + br.remote); + } + if (far.length > 0) { + GLib.debug("PUlling %s", this.name); + var r = this.repo.lookup_remote("origin"); + r.connect(Ggit.Direction.FETCH, new GitCallbacks(this), null, null); + var options = new Ggit.FetchOptions(); + options.set_remote_callbacks( new GitCallbacks(this)); + r.download(far, options); + r.disconnect(); + } + this.loadStatus(); + + + Idle.add((owned) callback); + return true; + + }; + new Thread("thread-new-gitpull-" + this.name, run); + yield; + + + } + + + public static void updateAllAsync(string after) + { + + + var doing = new Gee.HashMap();; + + var tr = GitRepo.singleton().cache; + + var update_all_total = tr.size; + foreach(var repo in tr.values) { + if (!repo.is_managed()) { + update_all_total--; + continue; + } + doing.set(repo.name, true); + repo.doUpdate.begin((obj, res) => { + repo.doUpdate.end(res); + doing.set(repo.name, false); + + foreach(var b in doing.keys) { + if (doing.get(b)) { + GLib.debug("pending: %s", b); + return; + } + } + + + switch (after) { + case "show_clones": + Clones.singleton().show(); + break; + default: + GLib.debug("Unkown call after load = %s", update_all_after); + break; + } + }); + } + + + } + + + + + public void update_async(GitAsyncCallback cb) + { + string[] cmd = { "fetch" , "--all" }; + this.git_async( cmd , cb); + + } + + + static uint update_all_total = 0; + static string update_all_after = ""; + + public static void updateAll(string after) + { + update_all_after = after; + var tr = GitRepo.singleton().cache; + + + update_all_total = tr.size; + foreach(var repo in tr.values) { + if (!repo.is_managed()) { + update_all_total--; + continue; + } + repo.update_async(updateAllCallback); + } + GLib.debug("calls total = %d", (int) update_all_total); + } + + + + public static void updateAllCallback(GitRepo repo, int err, string res) + { + repo.loadBranches(); + repo.loadStatus(); + + update_all_total--; + GLib.debug("calls remaining = %d", (int)update_all_total); + if (update_all_total > 0 ) { + + return; + } + GLib.debug("call after load = %s", update_all_after); + + switch (update_all_after) { + case "show_clones": + Clones.singleton().show(); + break; + default: + GLib.debug("Unkown call after load = %s", update_all_after); + break; + } + return; + } + + public void loadRemoteHeads(bool force = false) + { + + if (!force && this.remote_heads != null) { + return; + } + var r = this.repo.lookup_remote("origin"); + var cb = new GitCallbacks(this); + r.connect(Ggit.Direction.FETCH, cb, null, null); + this.remote_heads = r.list(); + r.disconnect(); + + } + }