X-Git-Url: http://git.roojs.org/?p=gitlive;a=blobdiff_plain;f=Git.vala;fp=Git.vala;h=795eabbb1ae3dd342dad56151d92586e2a41509b;hp=0000000000000000000000000000000000000000;hb=c245640608475ef3270eb6a48c9af03b274d9996;hpb=1b65514c93e260c86b738acbb13cf8ec2a39d596 diff --git a/Git.vala b/Git.vala new file mode 100644 index 00000000..795eabbb --- /dev/null +++ b/Git.vala @@ -0,0 +1,712 @@ + +// 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; + + + + + } + + } + + + +} \ No newline at end of file