// valac -o /tmp/ggit Git.vala --pkg libgit2-glib-1.0 --pkg libsoup-2.4 --pkg gee-0.8 -g void main() { GLib.Log.set_handler(null, GLib.LogLevelFlags.LEVEL_DEBUG | GLib.LogLevelFlags.LEVEL_WARNING | GLib.LogLevelFlags.LEVEL_INFO, (dom, lvl, msg) => { // should we debug.. print("%s\n", msg); } ); Ggit.init(); var a = new GitLive.Repo("/home/alan/gitlive/gitlive"); a.diffhead(); //a.fetchAll(); return; /* GLib.Timeout.add (1, () => { GLib.debug("Meanwhile"); return true; }, GLib.Priority.HIGH); var loop = new MainLoop(); var a = new GitLive.Repo("/home/alan/gitlive/web.coba"); GLib.debug("Starting"); a.loadRemoteHeads.begin(true, (obj,res) => { a.loadRemoteHeads.end(res); GLib.debug("got results"); a.loadLocalBranches(); loop.quit(); }); loop.run(); //a.mergeMasterIntoHead(); //a.walkDiff(); //return; //a.is_managed(); //a.is_autocommit(); //a.fetchAll(); // /* var a = new GitLive.Repo("/home/alan/git/test1-clone"); //a.fetchAll(); var str = (new GLib.DateTime.now_utc()).format("%Y-%m-%d %H:%M:%S"); GLib.FileUtils.set_contents("/home/alan/git/test1-clone/test1", str); var ix = a.repo.get_index(); ix.add_path("test1"); ix.write(); var treeoid = ix.write_tree(); var head = a.repo.get_head(); var parent = head.lookup() as Ggit.Commit; Ggit.Commit[] parents = (parent == null) ? new Ggit.Commit[] {} : new Ggit.Commit[] { parent }; var tree = a.repo.lookup(treeoid,typeof (Ggit.Tree)) as Ggit.Tree;// odd format.. var sig = new Ggit.Signature.now("Alan Knowles", "alan@roojs.com"); a.repo.create_commit("HEAD", sig, sig, null, "test commit " + str, tree, parents); */ // mmh.. no git add/commit in library... // string[] spawn_args = {"git", "commit", "-m", "test", "-a"}; //string[] spawn_env = Environ.get (); // Process.spawn_sync ("/home/alan/git/test1-clone", spawn_args, spawn_env, SpawnFlags.SEARCH_PATH, null); //a.pushAll(); } namespace GitLive { public class Repo : Object { string name = ""; public Ggit.Repository repo; Callbacks callbacks; public Repo(string path) { this.name = GLib.Path.get_basename(path); this.repo = Ggit.Repository.open(GLib.File.new_for_path(path)); this.callbacks = new Callbacks(this); } Ggit.Branch head = null; Gee.ArrayList branches = null; Ggit.RemoteHead[] remote_heads = null; public void loadLocalBranches(bool force = false) { if (!force && this.branches != null) { return; } this.branches = new Gee.ArrayList(); var r = this.repo.enumerate_branches(Ggit.BranchType.LOCAL); while (r.next()) { var br = r.get() as Ggit.Branch; if (br == null) { continue; } if (br.is_tag()) { continue; } //var head = this.repo.revparse("refs/heads/" + br.get_name() ).get_id(); //var rhead = this.repo.revparse(br.get_upstream().get_name() ).get_id(); try { GLib.debug("got branch: N=%s", br.get_name() ); GLib.debug("is_head %s", br.is_head() ? "Y" : "n"); GLib.debug("upstream = %s", br.get_upstream() == null ? "??" : br.get_upstream().get_name().substring(20)); GLib.debug("oid = %s",br.get_target().to_string()); this.branches.add(br); if (br.is_head()) { GLib.debug("HEAD= %s", br.get_name()); this.head = br; } } catch (Error e) { GLib.debug("Skip branch"); } } } public bool is_managed() { GLib.debug("is_managed: %d", this.repo.get_config().get_int32("gitlive.managed")); return this.repo.get_config().get_int32("gitlive.managed") == 1; } public bool is_autocommit () { GLib.debug("is_autocommit: %d", this.repo.get_config().get_int32("gitlive.autocommit")); return this.repo.get_config().get_int32("gitlive.autocommit") == 1; } public void set_autocommit(bool val) { this.repo.get_config().set_int32("gitlive.autocommit", val ? 1 : 0); } public void walkDiff() { this.loadLocalBranches(); var oid = this.repo.revparse(this.head.get_name() ).get_id() ; var moid = this.repo.revparse("refs/heads/master" ).get_id() ; var a = new Ggit.RevisionWalker(this.repo); a.set_sort_mode(Ggit.SortMode.TOPOLOGICAL); a.push(oid); a.hide(moid); var last = oid; for (var noid = a.next(); noid != null; noid= a.next()) { //var commit = this.repo.lookup(noid, typeof(Ggit.Commit)) as Ggit.Commit; GLib.debug("rev: %s", noid.to_string() ); last = noid; } var commit = this.repo.lookup(last, typeof(Ggit.Commit)) as Ggit.Commit; var parent = commit.get_parents(); GLib.debug("parent = %s", parent.get_id(0).to_string()); var master_rev = parent.get_id(0); var master_commit = this.repo.lookup_commit(master_rev);; var head_commit = this.repo.lookup_commit(oid); var master_tree = master_commit.get_tree(); var head_tree = head_commit.get_tree(); var diff = new Ggit.Diff.tree_to_tree(this.repo, master_tree, head_tree, new Ggit.DiffOptions()); diff.print(Ggit.DiffFormatType.PATCH, ( delta, hunk, line) => { GLib.debug("%d: %s, %s", line.get_new_lineno(), line.get_origin().to_string(), line.get_text()); return 0; }); // let's try a merge.. var mo = new Ggit.MergeOptions(); mo.set_file_favor(Ggit.MergeFileFavor.THEIRS); var ix = this.repo.merge_trees(master_tree, master_tree, head_tree, mo); if (ix.has_conflicts()) { GLib.debug("merge has conflicts"); return; } var treeoid = ix.write_tree(); var parents = new Ggit.Commit[] { master_commit }; var new_tree = this.repo.lookup(treeoid,typeof (Ggit.Tree)) as Ggit.Tree; var sig = new Ggit.Signature.now( this.repo.get_config().get_string("user.name"), this.repo.get_config().get_string("user.email") ); this.repo.create_commit("HEAD", sig, sig, null, "Test Merge", new_tree, parents); } public void diffhead() { var r = this.repo.enumerate_branches(Ggit.BranchType.LOCAL); Ggit.Branch? head = null; while (r.next()) { var gbr = r.get() as Ggit.Branch; if (gbr.is_head()) { head = gbr; } } GLib.debug("checking head=%s",head == null ? "EMPTY" : head.get_name()); var br = this.repo.lookup_branch(head.get_name(),Ggit.BranchType.LOCAL); var commit = this.repo.lookup_commit(br.get_target()); var diff = new Ggit.Diff.tree_to_workdir(this.repo, commit.get_tree(), 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("%s", ret); } public void mergeMasterIntoHead() { // assumes head is not master... this.loadLocalBranches(); GLib.debug("head rev = %s", this.head.get_name()); var head_oid = this.repo.revparse(this.head.get_name() ).get_id() ; //var master_oid = this.repo.revparse("refs/heads/master" ).get_id() ; var master_oid = this.repo.revparse("HEAD" ).get_id() ; var master_commit = this.repo.lookup_commit(master_oid);; var head_commit = this.repo.lookup_commit(head_oid); var anc_oid = this.repo.merge_base(master_commit.get_id(), head_commit.get_id()); var anc_commit = this.repo.lookup_commit(anc_oid); var anc_tree = anc_commit.get_tree(); var mo = new Ggit.MergeOptions(); var co = new Ggit.CheckoutOptions(); var the_ref = this.repo.lookup_reference_dwim("refs/heads/master"); var ac = new Ggit.AnnotatedCommit.from_ref(this.repo, the_ref); var commits = new Ggit.AnnotatedCommit[] { ac }; this.repo.merge(commits, mo, co); var cfg = this.repo.get_config().snapshot(); var sig = new Ggit.Signature.now( cfg.get_string("user.name"), cfg.get_string("user.email") ); var new_head = this.repo.get_head(); var oid = new_head.get_target(); var ix = this.repo.get_index(); ix.write(); var treeoid = ix.write_tree(); var new_tree = this.repo.lookup(treeoid,typeof (Ggit.Tree)) as Ggit.Tree; var parent = new_head.lookup() as Ggit.Commit; Ggit.Commit[] parents = new Ggit.Commit[] { parent }; this.repo.create_commit("refs/heads/" + this.head.get_name(), sig, sig, null, "Test Merge", new_tree, parents); } /* public bool doMergeClose(string commit_message) { this.loadLocalBranches(true); var oldbranch = this.head.get_name(); // going to asume this is merge trees.. 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(); } */ public bool is_auto_branch () { if (this.name == "gitlog") { return false; } // check remote... if (this.is_managed()) { return true; } return false; } public async void loadRemoteHeads(bool force = false) { SourceFunc callback = loadRemoteHeads.callback; ThreadFunc run = () => { if (!force && this.remote_heads != null) { return true;; } var r = this.repo.lookup_remote("origin"); r.connect(Ggit.Direction.FETCH, this.callbacks, null, null); yield; this.remote_heads = r.list(); foreach(var br in this.remote_heads) { if (!br.get_name().has_prefix("refs/heads/")) { continue; } GLib.debug("Remote: name=%s oid=%s local_oid=%s is_local=%s", br.get_name(), br.get_oid().to_string(), br.get_local_oid().to_string(), br.is_local() ? "Y" : "n" ); } Idle.add((owned) callback); return true;; }; new Thread("loadRemoteHeads-" , run); yield; } Ggit.Branch? getBranch(string remote_name, string remote_branch_name) { //GLib.debug("bn=%s",remote_branch_name); if (remote_branch_name.has_prefix("refs/remotes/")) { return null; } var target = remote_branch_name.replace("refs/heads/", remote_name+"/") .replace("refs/remotes/", ""); if (target == "HEAD") { target = remote_name +"/master"; } foreach(var br in this.branches) { // GLib.debug("test:%s=%s", br.get_upstream().get_shorthand() , target); if ( br.get_upstream().get_shorthand() == target) { return br; } } //GLib.debug("missing %s", remote_branch_name); return null; } public void fetchAll() { this.loadLocalBranches(); this.loadRemoteHeads(); // remotes probably will not work with http auth.. //var ar = this.repo.list_remotes(); //foreach(var n in ar) { var n = "origin"; GLib.debug("got remote '%s'", n); var r = this.repo.lookup_remote(n); GLib.debug("connecting '%s'", r.get_url()); try { string[] h = { "a = 1" }; r.connect(Ggit.Direction.FETCH, this.callbacks, null, null); } catch (Error e) { GLib.debug("Got Error Message: %s", e.message); return; } string[] far = {}; foreach(var rh in this.remote_heads) { if (rh.get_name().has_prefix("refs/remotes/")) { continue; } var br = this.getBranch(n, rh.get_name()); /*GLib.debug("got heads: name=%s rev=%s localrev=%s", rh.get_name(), rh.get_oid().to_string(), br == null ? "?": this.repo.revparse(br.get_name() ).get_id().to_string() ); */ //var loc_oid = this.repo.revparse(br.get_name() ).get_id(); var loc_oid = br.get_target(); size_t ahead, behind; this.repo.get_ahead_behind( loc_oid, rh.get_oid(), out ahead, out behind ); if (rh.get_oid().to_string() == this.repo.revparse(br.get_name() ).get_id().to_string()) { continue; } if (ahead > 0) { continue; } far += ("+refs/heads/" + br.get_name()) + ":refs/remotes/" + n + "/" + rh.get_name().replace("refs/heads/",""); } if (far.length < 1) { GLib.debug("no fetch required.. it's uptodate"); r.disconnect(); return; } GLib.debug("getting remote specs '%s', %d", n, far.length); /* var far = r.get_fetch_specs(); */ foreach(var rs in far) { GLib.debug("got remote spec: %s", rs); } var options = new Ggit.FetchOptions(); options.set_remote_callbacks(this.callbacks); //yield Async.thread(() => { r.download(far, options); //}); r.disconnect(); //r.download( } public void pushAll() { this.loadLocalBranches(); this.loadRemoteHeads(); // remotes probably will not work with http auth.. //var ar = this.repo.list_remotes(); var n = "origin"; GLib.debug("got remote '%s'", n); var r = this.repo.lookup_remote(n); GLib.debug("connecting '%s'", r.get_url()); r.connect(Ggit.Direction.PUSH, this.callbacks, null, null); //GLib.debug("getting specs '%s'", n); /* this.repo.add_remote_push( "origin", "+%s:%s".printf(head.get_shorthand(),head.get_name()) ); */ string[] far = {}; var heads = r.list(); foreach(var rh in heads) { if (rh.get_name().has_prefix("refs/remotes/")) { continue; } var br = this.getBranch(n, rh.get_name()); /* GLib.debug("got heads: name=%s rev=%s localrev=%s", rh.get_name(), rh.get_oid().to_string(), br == null ? "?": this.repo.revparse(br.get_name() ).get_id().to_string() );*/ var loc_oid = this.repo.revparse(br.get_name() ).get_id(); size_t ahead, behind; this.repo.get_ahead_behind( loc_oid, rh.get_oid(), out ahead, out behind ); if (rh.get_oid().to_string() == this.repo.revparse(br.get_name() ).get_id().to_string()) { continue; } if (behind > 0) { continue; } far += ("+refs/heads/" + br.get_name()) + ":"+ rh.get_name(); } if (far.length < 1) { GLib.debug("no push required.. it's uptodate"); r.disconnect(); return; } /*var head = this.repo.get_head(); string[] far = {}; far += "+%s:%s".printf(head.get_name(),head.get_name()); */ foreach(var rs in far) { GLib.debug("got remote spec: %s", rs); } var popts = new Ggit.PushOptions(); popts.callbacks = this.callbacks; GLib.debug("Push?"); r.upload(far,popts); GLib.debug("Push done?"); r.disconnect(); //r.download( } } private class Callbacks : Ggit.RemoteCallbacks { //private Remote d_remote; private Ggit.RemoteCallbacks? d_proxy = null; public delegate void TransferProgress(Ggit.TransferProgress stats); //private TransferProgress? d_transfer_progress; public Callbacks( Repo repo) //, Ggit.RemoteCallbacks? proxy) //,Remote remote, owned TransferProgress? transfer_progress) { //d_remote = remote; //d_proxy = proxy; //d_transfer_progress = (owned)transfer_progress; } protected override void progress(string message) { GLib.debug("progress: %s", message); if (d_proxy != null) { d_proxy.progress(message); } } protected override void transfer_progress(Ggit.TransferProgress stats) { GLib.debug("transfer_progress"); /* if (d_transfer_progress != null) { d_transfer_progress(stats); } if (d_proxy != null) { d_proxy.transfer_progress(stats); } */ } protected override void update_tips(string refname, Ggit.OId a, Ggit.OId b) { GLib.debug("update_tips"); //d_remote.tip_updated(refname, a, b); if (d_proxy != null) { d_proxy.update_tips(refname, a, b); } } protected override void completion(Ggit.RemoteCompletionType type) { GLib.debug("completion"); if (d_proxy != null) { d_proxy.completion(type); } } protected override Ggit.Cred? credentials(string url, string? username_from_url, Ggit.Credtype allowed_types) throws Error { GLib.debug("get credentials %s UN=%s", url, username_from_url); var uri = new Soup.URI(url); if (uri != null) { var ret = this.netrc(uri.get_host()); if (ret != null) { return ret; } //return new Ggit.CredPlaintext(username_from_url, "test"); } return null; /*var provider = d_remote.credentials_provider; if (provider != null) { ret = provider.credentials(url, username_from_url, allowed_types); } if (ret == null && d_proxy != null) { ret = d_proxy.credentials(url, username_from_url, allowed_types); } return ret; */ } public Ggit.Cred netrc(string domain) { string str; GLib.FileUtils.get_contents(GLib.Environment.get_home_dir() + "/.netrc", out str); var lines = str.split("\n"); for(var i=0; i< lines.length; i++) { // assumes one line per entry.. if not we are buggered... //GLib.debug("got %s" , lines[i]); var bits = Regex.split_simple ("[ \t]+", lines[i].strip()); if (bits.length < 6 || bits[0] != "machine" || bits[1] != domain) { continue; } GLib.debug("found password?"); // we are gussing.... return new Ggit.CredPlaintext(bits[3], bits[5]); } return null; } } }