[git] web/qa.git branch master updated. afcb958

From: Date: Sun, 04 Mar 2012 22:46:39 +0000
Subject: [git] web/qa.git branch master updated. afcb958
Groups: php.qa 
Request: Send a blank email to php-qa+get-66124@lists.php.net to get a copy of this message
The branch, master on web/qa.git has been updated via afcb95867b06d3926a2e43cd1fd7f583b0191455 (commit) via 7070ea56fd28e97946f5e601cb4ca8c9d20c2426 (commit) via 1dcf2d86aa02cafdbd449ecb30575ac0a0962f08 (commit) via 5fa7cddbadb43eb32844048f0ab43c4c1bac9223 (commit) via d76a8d18ad1e457bed8c51c0e3f50fd7dfff4cda (commit) via f7f02b3e7ff2ab3fa03af8870ea995132a8b5704 (commit) via e5249845ad55e5971a91e933469ae6f70547d2cb (commit) via 5769df1c481662dd4a584e107e622425798d67a3 (commit) via a84c7d8ff5c7c90b849466563ce9990ebbd7a0ae (commit) via a480e07e46ab2d69061c95bb758c662304a0e8b1 (commit) via 9dbb4d0319d9eda5577b87e1f97283bdbf2f5d3d (commit) via 0c1c3e6112cc401b92e74d0cd5d0022842f2a1d3 (commit) via b267f73d34af9025f1c1d0356625c79f7c7b05da (commit) via fb803e22619dcf65330f0902b6a8050441184f49 (commit) via 4ecd34f5bd3cbc617662e6cb2be91a3a6441ccef (commit) via 90fd22d1730b42065af9aebec4e4bff39980a1ef (commit) via 380e82d0e854d4ba244d09977f75c98241a9fd83 (commit) via 9aecf5bb00644717e28fdee3d32476b366f36506 (commit) via 68039495c021b84bd3934b53fa8531e4501c36cd (commit) via 122550c83b0c28e0b1e735b624e68a5f97a7dce1 (commit) via 5b3af0fc444e5681a1a024d1991c53453fa4a89f (commit) via 03ed80422340d5ae56e27773392ecb6717e3684f (commit) via 74fca45cbcdf576e57e4e09af394efcae1e760d5 (commit) from f86cd454b3a03c71fe95bb5aa4dcaff785b3ff1e (commit) http://git.php.net/?p=web/qa.git;a=log;h=afcb95867b06d3926a2e43cd1fd7f583b0191455;hp=f86cd454b3a03c71fe95bb5aa4dcaff785b3ff1e Summary of changes: .gitignore | 1 + gfx/github.ico | Bin 0 -> 1150 bytes include/functions.php | 3 +- pulls/api.php | 186 ++++++++++++++ pulls/config.php.in | 5 + pulls/index.php | 137 +++++++++++ pulls/jquery.ba-bbq.min.js | 18 ++ pulls/jsrender.js | 573 ++++++++++++++++++++++++++++++++++++++++++++ pulls/pullrequests.js | 154 ++++++++++++ 9 files changed, 1076 insertions(+), 1 deletions(-) create mode 100644 gfx/github.ico create mode 100644 pulls/api.php create mode 100644 pulls/config.php.in create mode 100644 pulls/index.php create mode 100644 pulls/jquery.ba-bbq.min.js create mode 100644 pulls/jsrender.js create mode 100644 pulls/pullrequests.js -- Log ---------------------------------------- commit afcb95867b06d3926a2e43cd1fd7f583b0191455 Merge: f86cd45 7070ea5 Author: Johannes Schlüter <johannes@php.net> Date: Sun Mar 4 23:35:39 2012 +0100 Add GitHub Pull Request Management stuff. * pull-request/2: (22 commits) Fix WS. Do some nicer error handling. More cleanup for hide/show logic. Bind show/hide on hashchange event Fix event handling. Refresh correct thing after closing a pull request and fix event handling Use URL hash for history. Use config.php here, too. Move config in a file. Avoid errors if no action was provided. Fix HTML. Fid indention. Split JavaScript stuff in a extra file Ask for login when needed. Use a local icon. Use correct preposition Store password a bit safer. Add Pull Request UI to navigation. WS. Add login stuff and update routines. ... diff --cc .gitignore index 8322433,28a1930..0c0821b --- a/.gitignore +++ b/.gitignore @@@ -1,2 -1,2 +1,3 @@@ reports/db/*.sqlite +reports/db/*.cache + pulls/config.php commit 7070ea56fd28e97946f5e601cb4ca8c9d20c2426 Author: Johannes Schlueter <johannes@schlueters.de> Date: Sun Mar 4 23:31:41 2012 +0100 Fix WS. diff --git a/include/functions.php b/include/functions.php index b0eeb33..e231193 100644 --- a/include/functions.php +++ b/include/functions.php @@ -34,8 +34,8 @@ function common_header() { | <a href="/rc.php" class="head_links">Release Candidates</a> | <a href="/howtohelp.php" class="head_links">How to Help</a> | <a href="/handling-bugs.php" class="head_links">Handling Bug Reports</a> - | <a href="/reports/" class="head_links">Test Reports</a> - | <a href="/pulls/" class="head_links">Manage Pull Requests</a> + | <a href="/reports/" class="head_links">Test Reports</a> + | <a href="/pulls/" class="head_links">Manage Pull Requests</a> </td> <td valign="bottom" align="right" class="head_links">&nbsp;</td> </tr> commit 1dcf2d86aa02cafdbd449ecb30575ac0a0962f08 Author: Johannes Schlueter <johannes@schlueters.de> Date: Sun Mar 4 23:14:56 2012 +0100 Do some nicer error handling. diff --git a/pulls/index.php b/pulls/index.php index 2d88047..f70e8a8 100644 --- a/pulls/index.php +++ b/pulls/index.php @@ -2,7 +2,7 @@ include("../include/functions.php"); include("../include/release-qa.php"); -require("./config.php"); +@include("./config.php"); $TITLE = "PHP-QA: GitHub Pull Requests"; $SITE_UPDATE = date("D M d H:i:s Y T", filectime(__FILE__)); @@ -105,6 +105,18 @@ $ git push origin master # everything okay? good, let's push it <span id="notloggedin"><a href="#">Login</a></span> </div> <h1>Github Pull Requests</h1> +<?php +if (empty($_ENV['AUTH_TOKEN'])) { + echo '<div style="width: 100%; border: 2px solid red; padding:10px;"><b>Error:</b> AUTH_TOKEN not set</div><br>'; +} + +if (!constant('GITHUB_PASS')) { + echo '<div style="width: 100%; border: 2px solid red; padding:10px;"><b>Error:</b> config.php not configured correctly.</div><br>'; + common_footer(); + exit; +} + +?> <div id="backToRepolist"><a href="#">&lt;&lt;&lt-- Repos</a></div> <div id="mainContent"><ul id="repolist"></ul>The PHP project is using github to mirror its git repostories from <a href="http://git.php.net">git.php.net</a>.</div> <div id="repoContent"></div> commit 5fa7cddbadb43eb32844048f0ab43c4c1bac9223 Author: Johannes Schlueter <johannes@schlueters.de> Date: Sun Mar 4 23:07:05 2012 +0100 More cleanup for hide/show logic. diff --git a/pulls/pullrequests.js b/pulls/pullrequests.js index d9cd2dc..f1cbbd8 100644 --- a/pulls/pullrequests.js +++ b/pulls/pullrequests.js @@ -9,6 +9,7 @@ repoList.prototype.showList = function() { $("#repolist").fadeIn(); $("#backToRepolist").hide(); $("#repoContent").fadeOut(); + $("#mainContent").show(); this.refreshView(); } @@ -20,7 +21,6 @@ repoList.prototype.hideList = function() { repoList.prototype.refreshView = function() { $("#repolist").html($("#repoListItemTemplate").render(this.data.data)); - $("#mainContent").show(); $("li", $("#repolist")).click(function(ev) { var reponame = loadRepo($(this).attr("repo")); $.bbq.pushState({ repo: reponame }); ev.preventDefault(); }); } repoList.prototype.update = function() { @@ -115,7 +115,6 @@ function loadRepo(repo) { url: GITHUB_BASEURL+'repos/'+GITHUB_ORG+"/"+repo+"/pulls", success: function (data) { $("#loading").hide(); - $("#mainContent").hide(); $("#repoContent").html( $("#repoOverviewTemplate").render([{repoName: repo, pullList: $("#pullRequestListItem").render(data.data)}])); $(".pullinstructions").click(function(ev) { $('<div></div>').html($("#pullInstructionTemplate").render({ repo: repo, number: $(this).attr("number")})) commit d76a8d18ad1e457bed8c51c0e3f50fd7dfff4cda Author: Johannes Schlueter <johannes@schlueters.de> Date: Sun Mar 4 23:04:32 2012 +0100 Bind show/hide on hashchange event diff --git a/pulls/pullrequests.js b/pulls/pullrequests.js index 22a5e00..d9cd2dc 100644 --- a/pulls/pullrequests.js +++ b/pulls/pullrequests.js @@ -95,11 +95,11 @@ $(document).ready(function() { login = new loginHandler(); repos = new repoList(GITHUB_BASEURL, GITHUB_ORG); repos.update(); - repos.showList(); $(window).bind( "hashchange", function(e) { if ($.bbq.getState( "repo" )) { loadRepo($.bbq.getState("repo")); + repos.hideList(); } else { repos.showList(); } @@ -114,7 +114,6 @@ function loadRepo(repo) { dataType: 'jsonp', url: GITHUB_BASEURL+'repos/'+GITHUB_ORG+"/"+repo+"/pulls", success: function (data) { - repos.hideList(); $("#loading").hide(); $("#mainContent").hide(); $("#repoContent").html( $("#repoOverviewTemplate").render([{repoName: repo, pullList: $("#pullRequestListItem").render(data.data)}])); commit f7f02b3e7ff2ab3fa03af8870ea995132a8b5704 Author: Johannes Schlueter <johannes@schlueters.de> Date: Sun Mar 4 02:25:52 2012 +0100 Fix event handling. diff --git a/pulls/pullrequests.js b/pulls/pullrequests.js index 05db0b6..22a5e00 100644 --- a/pulls/pullrequests.js +++ b/pulls/pullrequests.js @@ -118,9 +118,10 @@ function loadRepo(repo) { $("#loading").hide(); $("#mainContent").hide(); $("#repoContent").html( $("#repoOverviewTemplate").render([{repoName: repo, pullList: $("#pullRequestListItem").render(data.data)}])); - $(".pullinstructions").click(function() { + $(".pullinstructions").click(function(ev) { $('<div></div>').html($("#pullInstructionTemplate").render({ repo: repo, number: $(this).attr("number")})) .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')', width: 800 }); + ev.preventDefault(); }); $(".updatepullrequest").click(function(ev) { var dia = $('<div></div>').html($("#updatePullRequestTemplate").render({})) @@ -133,7 +134,7 @@ function loadRepo(repo) { $.bbq.pushState({ repo: repo }); } }); - + } function updateRepo(reponame, num, dia) { commit e5249845ad55e5971a91e933469ae6f70547d2cb Author: Johannes Schlueter <johannes@schlueters.de> Date: Sun Mar 4 01:33:59 2012 +0100 Refresh correct thing after closing a pull request and fix event handling diff --git a/pulls/pullrequests.js b/pulls/pullrequests.js index 59d9e2d..05db0b6 100644 --- a/pulls/pullrequests.js +++ b/pulls/pullrequests.js @@ -2,14 +2,14 @@ function repoList(baseurl, org) { this.url = baseurl+'orgs/'+org+'/repos'; this.data = {data:[]}; var t = this; - $("#backToRepolist").click(function () { $.bbq.removeState('repo'); }); + $("#backToRepolist").click(function (ev) { $.bbq.removeState('repo'); ev.preventDefault();}); } repoList.prototype.showList = function() { $("#repolist").fadeIn(); $("#backToRepolist").hide(); $("#repoContent").fadeOut(); - repos.refreshView(); + this.refreshView(); } repoList.prototype.hideList = function() { @@ -21,7 +21,7 @@ repoList.prototype.hideList = function() { repoList.prototype.refreshView = function() { $("#repolist").html($("#repoListItemTemplate").render(this.data.data)); $("#mainContent").show(); - $("li", $("#repolist")).click(function() { var reponame = loadRepo($(this).attr("repo")); $.bbq.pushState({ repo: reponame }); }); + $("li", $("#repolist")).click(function(ev) { var reponame = loadRepo($(this).attr("repo")); $.bbq.pushState({ repo: reponame }); ev.preventDefault(); }); } repoList.prototype.update = function() { var t = this; @@ -38,12 +38,14 @@ function loginHandler() { this.user = false; this.logindialog = $("#loginDialog").dialog({autoOpen: false}); this.checkLoggedIn(); - $("#notloggedin").click(function() { + $("#notloggedin").click(function(ev) { t.showLoginForm(); + ev.preventDefault(); } ); - $("#loginBtn").click(function() { + $("#loginBtn").click(function(ev) { t.logindialog.dialog("close"); t.login(); + ev.preventDefault(); } ); } @@ -73,8 +75,9 @@ loginHandler.prototype.updateLoginState = function(d) { $("#checkinglogin").hide(); $("#loggedin").html("Welcome "+d.user+" (<a href='#'>Logout</a>)").fadeIn(); $("#notloggedin").hide(); - $("#loggedin a").click(function() { + $("#loggedin a").click(function(ev) { $.ajax({ url: API_URL+'?action=logout', success: function(d) { t.updateLoginState(d); } }); + ev.preventDefault(); }); this.user = d.user; } else { @@ -119,10 +122,11 @@ function loadRepo(repo) { $('<div></div>').html($("#pullInstructionTemplate").render({ repo: repo, number: $(this).attr("number")})) .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')', width: 800 }); }); - $(".updatepullrequest").click(function() { + $(".updatepullrequest").click(function(ev) { var dia = $('<div></div>').html($("#updatePullRequestTemplate").render({})) .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')' }); - $("button", dia).click(function(r, n, dia) { return function() { updateRepo(r, n, dia); }}(repo, $(this).attr("number"), dia) ); + $("button", dia).click(function(r, n, dia) { return function(ev) { updateRepo(r, n, dia); ev.preventDefault();}}(repo, $(this).attr("number"), dia) ); + ev.preventDefault(); }); $("#repoPullList").accordion({ autoHeight: false }); $("#repoContent").show(); @@ -145,7 +149,7 @@ function updateRepo(reponame, num, dia) { id: num, state: $("select", dia).attr("value"), comment: $("textarea", dia).attr("value") - }, success: function(d) { repos.update(); } }); + }, success: function(d) { loadRepo(reponame); } }); dia.dialog("destroy"); } commit 5769df1c481662dd4a584e107e622425798d67a3 Author: Johannes Schlueter <johannes@schlueters.de> Date: Sat Mar 3 23:55:36 2012 +0100 Use URL hash for history. diff --git a/pulls/index.php b/pulls/index.php index c113a3b..2d88047 100644 --- a/pulls/index.php +++ b/pulls/index.php @@ -51,6 +51,7 @@ common_header(); <script type="text/javascript" src="http://code.jquery.com/jquery-1.7.1.min.js"></script> <script type="text/javascript" src="http://code.jquery.com/ui/1.8.18/jquery-ui.min.js"></script> <script type="text/javascript" src="jsrender.js"></script> + <script type="text/javascript" src="jquery.ba-bbq.min.js"></script> <script id="repoListItemTemplate" type="text/x-jquery-tmpl"> <li repo="{{=name}}"><b><a href="#">{{=name}}:</a></b> {{=description}} ({{=open_issues}})</li> </script> diff --git a/pulls/jquery.ba-bbq.min.js b/pulls/jquery.ba-bbq.min.js new file mode 100644 index 0000000..80de36f --- /dev/null +++ b/pulls/jquery.ba-bbq.min.js @@ -0,0 +1,18 @@ +/* + * jQuery BBQ: Back Button & Query Library - v1.3pre - 8/26/2010 + * http://benalman.com/projects/jquery-bbq-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ +(function($,r){var h,n=Array.prototype.slice,t=decodeURIComponent,a=$.param,j,c,m,y,b=$.bbq=$.bbq||{},s,x,k,e=$.event.special,d="hashchange",B="querystring",F="fragment",z="elemUrlAttr",l="href",w="src",p=/^.*\?|#.*$/g,u,H,g,i,C,E={};function G(I){return typeof I==="string"}function D(J){var I=n.call(arguments,1);return function(){return J.apply(this,I.concat(n.call(arguments)))}}function o(I){return I.replace(H,"$2")}function q(I){return I.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(K,P,I,L,J){var R,O,N,Q,M;if(L!==h){N=I.match(K?H:/^([^#?]*)\??([^#]*)(#?.*)/);M=N[3]||"";if(J===2&&G(L)){O=L.replace(K?u:p,"")}else{Q=m(N[2]);L=G(L)?m[K?F:B](L):L;O=J===2?L:J===1?$.extend({},L,Q):$.extend({},Q,L);O=j(O);if(K){O=O.replace(g,t)}}R=N[1]+(K?C:O||!N[1]?"?":"")+O+M}else{R=P(I!==h?I:location.href)}return R}a[B]=D(f,0,q);a[F]=c=D(f,1,o);a.sorted=j=function(J,K){var I=[],L={};$.each(a(J,K).split("&"),function(P,M){var O=M.replace(/(?:%5B|=).*$/,""),N=L[O];if(!N){N=L[O]=[];I.pu! sh(O)}N.push(M)});return $.map(I.sort(),function(M){return L[M]}).join("&")};c.noEscape=function(J){J=J||"";var I=$.map(J.split(""),encodeURIComponent);g=new RegExp(I.join("|"),"g")};c.noEscape(",/");c.ajaxCrawlable=function(I){if(I!==h){if(I){u=/^.*(?:#!|#)/;H=/^([^#]*)(?:#!|#)?(.*)$/;C="#!"}else{u=/^.*#/;H=/^([^#]*)#?(.*)$/;C="#"}i=!!I}return i};c.ajaxCrawlable(0);$.deparam=m=function(L,I){var K={},J={"true":!0,"false":!1,"null":null};$.each(L.replace(/\+/g," ").split("&"),function(O,T){var N=T.split("="),S=t(N[0]),M,R=K,P=0,U=S.split("]["),Q=U.length-1;if(/\[/.test(U[0])&&/\]$/.test(U[Q])){U[Q]=U[Q].replace(/\]$/,"");U=U.shift().split("[").concat(U);Q=U.length-1}else{Q=0}if(N.length===2){M=t(N[1]);if(I){M=M&&!isNaN(M)?+M:M==="undefined"?h:J[M]!==h?J[M]:M}if(Q){for(;P<=Q;P++){S=U[P]===""?R.length:U[P];R=R[S]=P<Q?R[S]||(U[P+1]&&isNaN(U[P+1])?{}:[]):M}}else{if($.isArray(K[S])){K[S].push(M)}else{if(K[S]!==h){K[S]=[K[S],M]}else{K[S]=M}}}}else{if(S){K[S]=I?h:""}}});return K};f! unction A(K,I,J){if(I===h||typeof I==="boolean"){J=I;I=a[K?F:B! ]()}else{I=G(I)?I.replace(K?u:p,""):I}return m(I,J)}m[B]=D(A,0);m[F]=y=D(A,1);$[z]||($[z]=function(I){return $.extend(E,I)})({a:l,base:l,iframe:w,img:w,input:w,form:"action",link:l,script:w});k=$[z];function v(L,J,K,I){if(!G(K)&&typeof K!=="object"){I=K;K=J;J=h}return this.each(function(){var O=$(this),M=J||k()[(this.nodeName||"").toLowerCase()]||"",N=M&&O.attr(M)||"";O.attr(M,a[L](N,K,I))})}$.fn[B]=D(v,B);$.fn[F]=D(v,F);b.pushState=s=function(L,I){if(G(L)&&/^#/.test(L)&&I===h){I=2}var K=L!==h,J=c(location.href,K?L:{},K?I:2);location.href=J};b.getState=x=function(I,J){return I===h||typeof I==="boolean"?y(I):y(J)[I]};b.removeState=function(I){var J={};if(I!==h){J=x();$.each($.isArray(I)?I:arguments,function(L,K){delete J[K]})}s(J,2)};e[d]=$.extend(e[d],{add:function(I){var K;function J(M){var L=M[F]=c();M.getState=function(N,O){return N===h||typeof N==="boolean"?m(L,N):m(L,O)[N]};K.apply(this,arguments)}if($.isFunction(I)){K=I;return J}else{K=I.handler;I.handler=J}}})})(jQue! ry,this); +/* + * jQuery hashchange event - v1.3 - 7/21/2010 + * http://benalman.com/projects/jquery-hashchange-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ +(function($,e,b){var c="hashchange",h=document,f,g=$.event.special,i=h.documentMode,d="on"+c in e&&(i===b||i>7);function a(j){j=j||location.href;return"#"+j.replace(/^[^#]*#?(.*)$/,"$1")}$.fn[c]=function(j){return j?this.bind(c,j):this.trigger(c)};$.fn[c].delay=50;g[c]=$.extend(g[c],{setup:function(){if(d){return false}$(f.start)},teardown:function(){if(d){return false}$(f.stop)}});f=(function(){var j={},p,m=a(),k=function(q){return q},l=k,o=k;j.start=function(){p||n()};j.stop=function(){p&&clearTimeout(p);p=b};function n(){var r=a(),q=o(m);if(r!==m){l(m=r,q);$(e).trigger(c)}else{if(q!==m){location.href=location.href.replace(/#.*/,"")+q}}p=setTimeout(n,$.fn[c].delay)}$.browser.msie&&!d&&(function(){var q,r;j.start=function(){if(!q){r=$.fn[c].src;r=r&&r+a();q=$('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){r||l(a());n()}).attr("src",r||"javascript:0").insertAfter("body")[0].contentWindow;h.onpropertychange=function(){try{if(event.propertyName==="title! "){q.document.title=h.title}}catch(s){}}}};j.stop=k;o=function(){return a(q.location.href)};l=function(v,s){var u=q.document,t=$.fn[c].domain;if(v!==s){u.title=h.title;u.open();t&&u.write('<script>document.domain="'+t+'"<\/script>');u.close();q.location.hash=v}}})();return j})()})(jQuery,this); \ No newline at end of file diff --git a/pulls/pullrequests.js b/pulls/pullrequests.js index 3e0de6e..59d9e2d 100644 --- a/pulls/pullrequests.js +++ b/pulls/pullrequests.js @@ -2,7 +2,7 @@ function repoList(baseurl, org) { this.url = baseurl+'orgs/'+org+'/repos'; this.data = {data:[]}; var t = this; - $("#backToRepolist").click(function () { t.showList(); }); + $("#backToRepolist").click(function () { $.bbq.removeState('repo'); }); } repoList.prototype.showList = function() { @@ -14,13 +14,14 @@ repoList.prototype.showList = function() { repoList.prototype.hideList = function() { $("#repolist").fadeOut(); + $("#mainContent").hide(); $("#backToRepolist").fadeIn(); } repoList.prototype.refreshView = function() { $("#repolist").html($("#repoListItemTemplate").render(this.data.data)); $("#mainContent").show(); - $("li", $("#repolist")).click(function() { loadRepo($(this).attr("repo")); }); + $("li", $("#repolist")).click(function() { var reponame = loadRepo($(this).attr("repo")); $.bbq.pushState({ repo: reponame }); }); } repoList.prototype.update = function() { var t = this; @@ -92,6 +93,16 @@ $(document).ready(function() { repos = new repoList(GITHUB_BASEURL, GITHUB_ORG); repos.update(); repos.showList(); + + $(window).bind( "hashchange", function(e) { + if ($.bbq.getState( "repo" )) { + loadRepo($.bbq.getState("repo")); + } else { + repos.showList(); + } + }); + + $(window).trigger( "hashchange" ); }); function loadRepo(repo) { @@ -115,6 +126,7 @@ function loadRepo(repo) { }); $("#repoPullList").accordion({ autoHeight: false }); $("#repoContent").show(); + $.bbq.pushState({ repo: repo }); } }); commit a84c7d8ff5c7c90b849466563ce9990ebbd7a0ae Author: Johannes Schlueter <johannes@schlueters.de> Date: Sat Mar 3 22:27:05 2012 +0100 Use config.php here, too. diff --git a/pulls/api.php b/pulls/api.php index 06694f3..c6f747c 100644 --- a/pulls/api.php +++ b/pulls/api.php @@ -1,9 +1,5 @@ <?php - -const GITHUB_BASEURL = 'https://api.github.com/'; -const GITHUB_ORG = 'php'; -const GITHUB_USER = 'johannes'; -define('GITHUB_PASS', include('passwd.php')); // FETCH FROM ENVIRONMENT +require('./config.php'); if ($_SERVER['SERVER_NAME'] === 'schlueters.de') { define('DEV', true); commit a480e07e46ab2d69061c95bb758c662304a0e8b1 Author: Johannes Schlueter <johannes@schlueters.de> Date: Sat Mar 3 22:23:30 2012 +0100 Move config in a file. diff --git a/.gitignore b/.gitignore index f981b00..28a1930 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ reports/db/*.sqlite +pulls/config.php diff --git a/pulls/config.php.in b/pulls/config.php.in new file mode 100644 index 0000000..ec65f53 --- /dev/null +++ b/pulls/config.php.in @@ -0,0 +1,5 @@ +<?php +const GITHUB_BASEURL = 'https://api.github.com/'; +const GITHUB_ORG = 'php'; +const GITHUB_USER = '....'; +const GITHUB_PASS = '....'; diff --git a/pulls/index.php b/pulls/index.php index 4e08625..c113a3b 100644 --- a/pulls/index.php +++ b/pulls/index.php @@ -2,6 +2,8 @@ include("../include/functions.php"); include("../include/release-qa.php"); +require("./config.php"); + $TITLE = "PHP-QA: GitHub Pull Requests"; $SITE_UPDATE = date("D M d H:i:s Y T", filectime(__FILE__)); @@ -91,8 +93,8 @@ $ git push origin master # everything okay? good, let's push it <button>Go</button> </script> <script type="text/javascript"> - var GITHUB_BASEURL = "https://api.github.com/"; - var GITHUB_ORG = "php"; + var GITHUB_BASEURL = <?php echo json_encode(GITHUB_BASEURL); ?>; + var GITHUB_ORG = <?php echo json_encode(GITHUB_ORG); ?>; var API_URL = "api.php"; </script> <script src="pullrequests.js"></script> commit 9dbb4d0319d9eda5577b87e1f97283bdbf2f5d3d Author: Johannes Schlueter <johannes@schlueters.de> Date: Sat Mar 3 15:54:40 2012 +0100 Avoid errors if no action was provided. diff --git a/pulls/api.php b/pulls/api.php index 5035a02..06694f3 100644 --- a/pulls/api.php +++ b/pulls/api.php @@ -180,11 +180,11 @@ $accepted_actions = array( 'loggedin', 'ghupdate' ); -if (in_array($_REQUEST['action'], $accepted_actions)) { +if (isset($_REQUEST['action']) && in_array($_REQUEST['action'], $accepted_actions)) { $action = $_REQUEST['action']; $action(); } else { header('HTTP/1.0 400 Bad Request'); - die(json_encode(array('success' => false, 'errors' => array("Unknown method $_REQUEST[action]")))); + die(json_encode(array('success' => false, 'errors' => array("Unknown method")))); } commit 0c1c3e6112cc401b92e74d0cd5d0022842f2a1d3 Author: Johannes Schlueter <johannes@schlueters.de> Date: Sat Mar 3 15:27:09 2012 +0100 Fix HTML. diff --git a/pulls/index.php b/pulls/index.php index c5209b3..4e08625 100644 --- a/pulls/index.php +++ b/pulls/index.php @@ -99,7 +99,7 @@ $ git push origin master # everything okay? good, let's push it <div id="loginstatus"> <span id="checkinglogin">(checking login state ...)</span> <span id="loggedin"></span> - <span id="notloggedin"><a href="#">Login</span> + <span id="notloggedin"><a href="#">Login</a></span> </div> <h1>Github Pull Requests</h1> <div id="backToRepolist"><a href="#">&lt;&lt;&lt-- Repos</a></div> commit b267f73d34af9025f1c1d0356625c79f7c7b05da Author: Johannes Schlueter <johannes@schlueters.de> Date: Sat Mar 3 15:14:54 2012 +0100 Fid indention. diff --git a/pulls/pullrequests.js b/pulls/pullrequests.js index 71334b4..3e0de6e 100644 --- a/pulls/pullrequests.js +++ b/pulls/pullrequests.js @@ -1,138 +1,139 @@ - function repoList(baseurl, org) { - this.url = baseurl+'orgs/'+org+'/repos'; - this.data = {data:[]}; - var t = this; - $("#backToRepolist").click(function () { t.showList(); }); - } +function repoList(baseurl, org) { + this.url = baseurl+'orgs/'+org+'/repos'; + this.data = {data:[]}; + var t = this; + $("#backToRepolist").click(function () { t.showList(); }); +} - repoList.prototype.showList = function() { - $("#repolist").fadeIn(); - $("#backToRepolist").hide(); - $("#repoContent").fadeOut(); - repos.refreshView(); - } +repoList.prototype.showList = function() { + $("#repolist").fadeIn(); + $("#backToRepolist").hide(); + $("#repoContent").fadeOut(); + repos.refreshView(); +} - repoList.prototype.hideList = function() { - $("#repolist").fadeOut(); - $("#backToRepolist").fadeIn(); - } +repoList.prototype.hideList = function() { + $("#repolist").fadeOut(); + $("#backToRepolist").fadeIn(); +} - repoList.prototype.refreshView = function() { - $("#repolist").html($("#repoListItemTemplate").render(this.data.data)); - $("#mainContent").show(); - $("li", $("#repolist")).click(function() { loadRepo($(this).attr("repo")); }); - } - repoList.prototype.update = function() { - var t = this; - $("#loading").show(); - $.ajax({ dataType: 'jsonp', url: this.url, success: function(d) { t.setData(d); $("#loading").hide(); } }); - } - repoList.prototype.setData = function(data) { - this.data = data; - this.refreshView(); - } +repoList.prototype.refreshView = function() { + $("#repolist").html($("#repoListItemTemplate").render(this.data.data)); + $("#mainContent").show(); + $("li", $("#repolist")).click(function() { loadRepo($(this).attr("repo")); }); +} +repoList.prototype.update = function() { + var t = this; + $("#loading").show(); + $.ajax({ dataType: 'jsonp', url: this.url, success: function(d) { t.setData(d); $("#loading").hide(); } }); +} +repoList.prototype.setData = function(data) { + this.data = data; + this.refreshView(); +} - function loginHandler() { - var t = this; - this.user = false; - this.logindialog = $("#loginDialog").dialog({autoOpen: false}); - this.checkLoggedIn(); - $("#notloggedin").click(function() { - t.showLoginForm(); - } ); - $("#loginBtn").click(function() { - t.logindialog.dialog("close"); - t.login(); - } ); - } +function loginHandler() { + var t = this; + this.user = false; + this.logindialog = $("#loginDialog").dialog({autoOpen: false}); + this.checkLoggedIn(); + $("#notloggedin").click(function() { + t.showLoginForm(); + } ); + $("#loginBtn").click(function() { + t.logindialog.dialog("close"); + t.login(); + } ); +} - loginHandler.prototype.showLoginForm = function() { - this.logindialog.dialog("open"); - } +loginHandler.prototype.showLoginForm = function() { + this.logindialog.dialog("open"); +} - loginHandler.prototype.login = function() { - var user = $("#userField").attr("value"); - var pass = $("#passField").attr("value"); - var t = this; +loginHandler.prototype.login = function() { + var user = $("#userField").attr("value"); + var pass = $("#passField").attr("value"); + var t = this; - $.ajax({ url: API_URL, type: "POST", data: { action: 'login', user: user, pass: pass }, success: function(d) { t.updateLoginState(d); } }); - } + $.ajax({ url: API_URL, type: "POST", data: { action: 'login', user: user, pass: pass }, success: function(d) { t.updateLoginState(d); } }); +} - loginHandler.prototype.checkLoggedIn = function() { - var t = this; - $("#checkinglogin").show(); - $("#loggedin").hide(); - $("#notloggedin").hide(); - $.ajax({ url: API_URL+'?action=loggedin', success: function(d) { t.updateLoginState(d); } }); - } +loginHandler.prototype.checkLoggedIn = function() { + var t = this; + $("#checkinglogin").show(); + $("#loggedin").hide(); + $("#notloggedin").hide(); + $.ajax({ url: API_URL+'?action=loggedin', success: function(d) { t.updateLoginState(d); } }); +} - loginHandler.prototype.updateLoginState = function(d) { - var t = this; - if (d.success && d.user) { - $("#checkinglogin").hide(); - $("#loggedin").html("Welcome "+d.user+" (<a href='#'>Logout</a>)").fadeIn(); - $("#notloggedin").hide(); - $("#loggedin a").click(function() { - $.ajax({ url: API_URL+'?action=logout', success: function(d) { t.updateLoginState(d); } }); - }); - this.user = d.user; - } else { - $("#checkinglogin").hide(); - $("#loggedin").hide(); - $("#notloggedin").fadeIn(); - this.user = false; - } - } +loginHandler.prototype.updateLoginState = function(d) { + var t = this; + if (d.success && d.user) { + $("#checkinglogin").hide(); + $("#loggedin").html("Welcome "+d.user+" (<a href='#'>Logout</a>)").fadeIn(); + $("#notloggedin").hide(); + $("#loggedin a").click(function() { + $.ajax({ url: API_URL+'?action=logout', success: function(d) { t.updateLoginState(d); } }); + }); + this.user = d.user; + } else { + $("#checkinglogin").hide(); + $("#loggedin").hide(); + $("#notloggedin").fadeIn(); + this.user = false; + } +} - var repos; - var login; +var repos; +var login; - $(document).ready(function() { - login = new loginHandler(); - repos = new repoList(GITHUB_BASEURL, GITHUB_ORG); - repos.update(); - repos.showList(); - }); +$(document).ready(function() { + login = new loginHandler(); + repos = new repoList(GITHUB_BASEURL, GITHUB_ORG); + repos.update(); + repos.showList(); +}); - function loadRepo(repo) { - $("#loading").show(); - $.ajax({ - dataType: 'jsonp', - url: GITHUB_BASEURL+'repos/'+GITHUB_ORG+"/"+repo+"/pulls", - success: function (data) { - repos.hideList(); - $("#loading").hide(); - $("#mainContent").hide(); - $("#repoContent").html( $("#repoOverviewTemplate").render([{repoName: repo, pullList: $("#pullRequestListItem").render(data.data)}])); - $(".pullinstructions").click(function() { - $('<div></div>').html($("#pullInstructionTemplate").render({ repo: repo, number: $(this).attr("number")})) - .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')', width: 800 }); - }); - $(".updatepullrequest").click(function() { - var dia = $('<div></div>').html($("#updatePullRequestTemplate").render({})) - .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')' }); - $("button", dia).click(function(r, n, dia) { return function() { updateRepo(r, n, dia); }}(repo, $(this).attr("number"), dia) ); - }); - $("#repoPullList").accordion({ autoHeight: false }); - $("#repoContent").show(); - } - }); +function loadRepo(repo) { + $("#loading").show(); + $.ajax({ + dataType: 'jsonp', + url: GITHUB_BASEURL+'repos/'+GITHUB_ORG+"/"+repo+"/pulls", + success: function (data) { + repos.hideList(); + $("#loading").hide(); + $("#mainContent").hide(); + $("#repoContent").html( $("#repoOverviewTemplate").render([{repoName: repo, pullList: $("#pullRequestListItem").render(data.data)}])); + $(".pullinstructions").click(function() { + $('<div></div>').html($("#pullInstructionTemplate").render({ repo: repo, number: $(this).attr("number")})) + .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')', width: 800 }); + }); + $(".updatepullrequest").click(function() { + var dia = $('<div></div>').html($("#updatePullRequestTemplate").render({})) + .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')' }); + $("button", dia).click(function(r, n, dia) { return function() { updateRepo(r, n, dia); }}(repo, $(this).attr("number"), dia) ); + }); + $("#repoPullList").accordion({ autoHeight: false }); + $("#repoContent").show(); + } + }); - } +} + +function updateRepo(reponame, num, dia) { + var t = this; + if (!login.user) { + login.showLoginForm(); + return; + } + $("#loading").show(); + $.ajax({ url: API_URL, type: "POST", data: { + action: 'ghupdate', + repo: reponame, + id: num, + state: $("select", dia).attr("value"), + comment: $("textarea", dia).attr("value") + }, success: function(d) { repos.update(); } }); + dia.dialog("destroy"); +} - function updateRepo(reponame, num, dia) { - var t = this; - if (!login.user) { - login.showLoginForm(); - return; - } - $("#loading").show(); - $.ajax({ url: API_URL, type: "POST", data: { - action: 'ghupdate', - repo: reponame, - id: num, - state: $("select", dia).attr("value"), - comment: $("textarea", dia).attr("value") - }, success: function(d) { repos.update(); } }); - dia.dialog("destroy"); - } commit fb803e22619dcf65330f0902b6a8050441184f49 Author: Johannes Schlueter <johannes@schlueters.de> Date: Sat Mar 3 15:08:31 2012 +0100 Split JavaScript stuff in a extra file diff --git a/pulls/index.php b/pulls/index.php index 39128e5..c5209b3 100644 --- a/pulls/index.php +++ b/pulls/index.php @@ -94,146 +94,8 @@ $ git push origin master # everything okay? good, let's push it var GITHUB_BASEURL = "https://api.github.com/"; var GITHUB_ORG = "php"; var API_URL = "api.php"; - - function repoList(baseurl, org) { - this.url = baseurl+'orgs/'+org+'/repos'; - this.data = {data:[]}; - var t = this; - $("#backToRepolist").click(function () { t.showList(); }); - } - - repoList.prototype.showList = function() { - $("#repolist").fadeIn(); - $("#backToRepolist").hide(); - $("#repoContent").fadeOut(); - repos.refreshView(); - } - - repoList.prototype.hideList = function() { - $("#repolist").fadeOut(); - $("#backToRepolist").fadeIn(); - } - - repoList.prototype.refreshView = function() { - $("#repolist").html($("#repoListItemTemplate").render(this.data.data)); - $("#mainContent").show(); - $("li", $("#repolist")).click(function() { loadRepo($(this).attr("repo")); }); - } - repoList.prototype.update = function() { - var t = this; - $("#loading").show(); - $.ajax({ dataType: 'jsonp', url: this.url, success: function(d) { t.setData(d); $("#loading").hide(); } }); - } - repoList.prototype.setData = function(data) { - this.data = data; - this.refreshView(); - } - - function loginHandler() { - var t = this; - this.user = false; - this.logindialog = $("#loginDialog").dialog({autoOpen: false}); - this.checkLoggedIn(); - $("#notloggedin").click(function() { - t.showLoginForm(); - } ); - $("#loginBtn").click(function() { - t.logindialog.dialog("close"); - t.login(); - } ); - } - - loginHandler.prototype.showLoginForm = function() { - this.logindialog.dialog("open"); - } - - loginHandler.prototype.login = function() { - var user = $("#userField").attr("value"); - var pass = $("#passField").attr("value"); - var t = this; - - $.ajax({ url: API_URL, type: "POST", data: { action: 'login', user: user, pass: pass }, success: function(d) { t.updateLoginState(d); } }); - } - - loginHandler.prototype.checkLoggedIn = function() { - var t = this; - $("#checkinglogin").show(); - $("#loggedin").hide(); - $("#notloggedin").hide(); - $.ajax({ url: API_URL+'?action=loggedin', success: function(d) { t.updateLoginState(d); } }); - } - - loginHandler.prototype.updateLoginState = function(d) { - var t = this; - if (d.success && d.user) { - $("#checkinglogin").hide(); - $("#loggedin").html("Welcome "+d.user+" (<a href='#'>Logout</a>)").fadeIn(); - $("#notloggedin").hide(); - $("#loggedin a").click(function() { - $.ajax({ url: API_URL+'?action=logout', success: function(d) { t.updateLoginState(d); } }); - }); - this.user = d.user; - } else { - $("#checkinglogin").hide(); - $("#loggedin").hide(); - $("#notloggedin").fadeIn(); - this.user = false; - } - } - - var repos; - var login; - - $(document).ready(function() { - login = new loginHandler(); - repos = new repoList(GITHUB_BASEURL, GITHUB_ORG); - repos.update(); - repos.showList(); - }); - - function loadRepo(repo) { - $("#loading").show(); - $.ajax({ - dataType: 'jsonp', - url: GITHUB_BASEURL+'repos/'+GITHUB_ORG+"/"+repo+"/pulls", - success: function (data) { - repos.hideList(); - $("#loading").hide(); - $("#mainContent").hide(); - $("#repoContent").html( $("#repoOverviewTemplate").render([{repoName: repo, pullList: $("#pullRequestListItem").render(data.data)}])); - $(".pullinstructions").click(function() { - $('<div></div>').html($("#pullInstructionTemplate").render({ repo: repo, number: $(this).attr("number")})) - .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')', width: 800 }); - }); - $(".updatepullrequest").click(function() { - var dia = $('<div></div>').html($("#updatePullRequestTemplate").render({})) - .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')' }); - $("button", dia).click(function(r, n, dia) { return function() { updateRepo(r, n, dia); }}(repo, $(this).attr("number"), dia) ); - }); - $("#repoPullList").accordion({ autoHeight: false }); - $("#repoContent").show(); - } - }); - - } - - function updateRepo(reponame, num, dia) { - var t = this; - if (!login.user) { - login.showLoginForm(); - return; - } - $("#loading").show(); - $.ajax({ url: API_URL, type: "POST", data: { - action: 'ghupdate', - repo: reponame, - id: num, - state: $("select", dia).attr("value"), - comment: $("textarea", dia).attr("value") - }, success: function(d) { repos.update(); } }); - dia.dialog("destroy"); - } </script> + <script src="pullrequests.js"></script> <div id="loginstatus"> <span id="checkinglogin">(checking login state ...)</span> <span id="loggedin"></span> diff --git a/pulls/index.php b/pulls/pullrequests.js similarity index 54% copy from pulls/index.php copy to pulls/pullrequests.js index 39128e5..71334b4 100644 --- a/pulls/index.php +++ b/pulls/pullrequests.js @@ -1,100 +1,3 @@ -<?php -include("../include/functions.php"); -include("../include/release-qa.php"); - -$TITLE = "PHP-QA: GitHub Pull Requests"; -$SITE_UPDATE = date("D M d H:i:s Y T", filectime(__FILE__)); - -common_header(); - -?> -<table width="70%" border="0" cellspacing="0" cellpadding="0"> - <tr> - <td width="10"><img src="../gfx/spacer.gif" width="10" height="1"></td> - <td width="100%"> - <style> - #loading { - position:fixed; - top:50%; - padding-top:-20px; - height:40px; - left:50%; - padding-left:-50px; - width:100px; - border:1px solid black; - background-color: black; - color: white; - text-align: center; - vertical-align: center; - } - - .ghuser { - border: 1px solid #ffcc66; - float: right; - margin-left: 5px; - margin-bottom: 5px; - } - - .ghuser a { - color: #ffcc66; - text-decoration: none; - } - - #loginstatus { - width: 100%; - text-align: right; - } - </style> - <link href="http://code.jquery.com/ui/1.8.18/themes/ui-lightness/jquery-ui.css" rel="stylesheet" type="text/css"/> - <script type="text/javascript" src="http://code.jquery.com/jquery-1.7.1.min.js"></script> - <script type="text/javascript" src="http://code.jquery.com/ui/1.8.18/jquery-ui.min.js"></script> - <script type="text/javascript" src="jsrender.js"></script> - <script id="repoListItemTemplate" type="text/x-jquery-tmpl"> - <li repo="{{=name}}"><b><a href="#">{{=name}}:</a></b> {{=description}} ({{=open_issues}})</li> - </script> - <script id="repoOverviewTemplate" type="text/x-jquery-tmpl"> - <h2>{{=repoName}}</h2> - <div id="repoPullList"> - {{=pullList!}} - </div> - <!-- iframe style='width:100%; height:300px;border-width:1px;' src='' id='ghframe' name='ghframe'></iframe --> - </script> - <script id="pullRequestListItem" type="text/x-jquery-tmpl"> - <h3><a href='#'>{{=number}}: {{=title}} ({{=state}})</a></h3> - <div class="pullrequest"> - <div class="ghuser"><a href="{{=user.url}}"><img src="{{=user.avatar_url}}"><br>{{=user.login}}</a></div> - <div>Created: {{=created_at}}, LastUpdated: {{=updated_at}}</div> - <div>{{=body}}</div> - <div><a href="{{=html_url}}"><img src="../gfx/github.ico"> On GitHub</a> | - <a href="{{=diff_url}}">Diff</a> | - <a href="#" number="{{=number}}" state="{{=state}}" title="{{=title}}" class="pullinstructions">Show Pull Instructions</a> | - <a href="#" number="{{=number}}" state="{{=state}}" title="{{=title}}" class="updatepullrequest">Update</a> - </div> - </div> - </script> - <script id="pullInstructionTemplate" type="text/x-jquery-tmpl"> - <pre> -$ git fetch git://github.com/php/{{=repo}} pull/{{=number}}/head:pull-request/{{=number}} -$ git log -p pull-request/{{=number}} # REVIEW IT -$ git merge pull-request/{{=number}} # Merge it, add a GOOD commit message -$ make test # you better don't forget that -$ git push origin master # everything okay? good, let's push it - </pre> - </script> - <script id="updatePullRequestTemplate" type="text/x-jquery-tmpl"> - State: <select id="newState"> - <option>open</option> - <option>closed</option> - </select><br> - Please provide a comment for your change:<br> - <textarea id="comment"></textarea><br> - <button>Go</button> - </script> - <script type="text/javascript"> - var GITHUB_BASEURL = "https://api.github.com/"; - var GITHUB_ORG = "php"; - var API_URL = "api.php"; - function repoList(baseurl, org) { this.url = baseurl+'orgs/'+org+'/repos'; this.data = {data:[]}; @@ -233,28 +136,3 @@ $ git push origin master # everything okay? good, let's push it }, success: function(d) { repos.update(); } }); dia.dialog("destroy"); } - </script> - <div id="loginstatus"> - <span id="checkinglogin">(checking login state ...)</span> - <span id="loggedin"></span> - <span id="notloggedin"><a href="#">Login</span> - </div> - <h1>Github Pull Requests</h1> - <div id="backToRepolist"><a href="#">&lt;&lt;&lt-- Repos</a></div> - <div id="mainContent"><ul id="repolist"></ul>The PHP project is using github to mirror its git repostories from <a href="http://git.php.net">git.php.net</a>.</div> - <div id="repoContent"></div> - <div id="loginDialog" title="Login"> - Username: <br><input id="userField"><br> - Password: <br><input id="passField" type="password"><br> - <button id="loginBtn">Login</button> - </div> - <div id="loading">Loading</div> - - </td> - <td width="10"><img src="http://qa.php.net/gfx/spacer.gif" width="10" height="1"></td> - </tr> - </table> -<?php - -common_footer(); - commit 4ecd34f5bd3cbc617662e6cb2be91a3a6441ccef Author: Johannes Schlueter <johannes@schlueters.de> Date: Sat Mar 3 15:04:54 2012 +0100 Ask for login when needed. diff --git a/pulls/index.php b/pulls/index.php index 5150a43..39128e5 100644 --- a/pulls/index.php +++ b/pulls/index.php @@ -135,7 +135,7 @@ $ git push origin master # everything okay? good, let's push it this.logindialog = $("#loginDialog").dialog({autoOpen: false}); this.checkLoggedIn(); $("#notloggedin").click(function() { - t.logindialog.dialog("open"); + t.showLoginForm(); } ); $("#loginBtn").click(function() { t.logindialog.dialog("close"); @@ -143,6 +143,10 @@ $ git push origin master # everything okay? good, let's push it } ); } + loginHandler.prototype.showLoginForm = function() { + this.logindialog.dialog("open"); + } + loginHandler.prototype.login = function() { var user = $("#userField").attr("value"); var pass = $("#passField").attr("value"); @@ -216,7 +220,7 @@ $ git push origin master # everything okay? good, let's push it function updateRepo(reponame, num, dia) { var t = this; if (!login.user) { - window.alert("Login First!"); + login.showLoginForm(); return; } $("#loading").show(); commit 90fd22d1730b42065af9aebec4e4bff39980a1ef Author: Johannes Schlueter <johannes@schlueters.de> Date: Sat Mar 3 04:40:36 2012 +0100 Use a local icon. diff --git a/gfx/github.ico b/gfx/github.ico new file mode 100644 index 0000000..9db08f2 Binary files /dev/null and b/gfx/github.ico differ diff --git a/pulls/index.php b/pulls/index.php index d0498f4..5150a43 100644 --- a/pulls/index.php +++ b/pulls/index.php @@ -65,7 +65,7 @@ common_header(); <div class="ghuser"><a href="{{=user.url}}"><img src="{{=user.avatar_url}}"><br>{{=user.login}}</a></div> <div>Created: {{=created_at}}, LastUpdated: {{=updated_at}}</div> <div>{{=body}}</div> - <div><a href="{{=html_url}}"><img src="https://github.com/favicon.ico"> On GitHub</a> | + <div><a href="{{=html_url}}"><img src="../gfx/github.ico"> On GitHub</a> | <a href="{{=diff_url}}">Diff</a> | <a href="#" number="{{=number}}" state="{{=state}}" title="{{=title}}" class="pullinstructions">Show Pull Instructions</a> | <a href="#" number="{{=number}}" state="{{=state}}" title="{{=title}}" class="updatepullrequest">Update</a> commit 380e82d0e854d4ba244d09977f75c98241a9fd83 Author: Johannes Schlueter <johannes@schlueters.de> Date: Sat Mar 3 04:37:14 2012 +0100 Use correct preposition diff --git a/pulls/api.php b/pulls/api.php index abc15dc..5035a02 100644 --- a/pulls/api.php +++ b/pulls/api.php @@ -57,7 +57,7 @@ function verify_password_DEV($user, $pass) function ghpostcomment($pull, $comment) { - $post = json_encode(array("body" => "**Comment in behalf of $_SESSION[user] at php.net:**\n\n$comment")); + $post = json_encode(array("body" => "**Comment on behalf of $_SESSION[user] at php.net:**\n\n$comment")); $opts = array( commit 9aecf5bb00644717e28fdee3d32476b366f36506 Author: Johannes Schlueter <johannes@schlueters.de> Date: Sat Mar 3 01:51:56 2012 +0100 Store password a bit safer. diff --git a/pulls/api.php b/pulls/api.php index bd6c99c..abc15dc 100644 --- a/pulls/api.php +++ b/pulls/api.php @@ -3,7 +3,7 @@ const GITHUB_BASEURL = 'https://api.github.com/'; const GITHUB_ORG = 'php'; const GITHUB_USER = 'johannes'; -define('GITHUB_PASS', trim(file_get_contents('passwd'))); // FETCH FROM ENVIRONMENT +define('GITHUB_PASS', include('passwd.php')); // FETCH FROM ENVIRONMENT if ($_SERVER['SERVER_NAME'] === 'schlueters.de') { define('DEV', true); commit 68039495c021b84bd3934b53fa8531e4501c36cd Author: Johannes Schlueter <johannes@schlueters.de> Date: Sat Mar 3 01:47:37 2012 +0100 Add Pull Request UI to navigation. diff --git a/include/functions.php b/include/functions.php index ad0f819..b0eeb33 100644 --- a/include/functions.php +++ b/include/functions.php @@ -34,7 +34,8 @@ function common_header() { | <a href="/rc.php" class="head_links">Release Candidates</a> | <a href="/howtohelp.php" class="head_links">How to Help</a> | <a href="/handling-bugs.php" class="head_links">Handling Bug Reports</a> - | <a href="/reports/" class="head_links">Test reports</a> + | <a href="/reports/" class="head_links">Test Reports</a> + | <a href="/pulls/" class="head_links">Manage Pull Requests</a> </td> <td valign="bottom" align="right" class="head_links">&nbsp;</td> </tr> commit 122550c83b0c28e0b1e735b624e68a5f97a7dce1 Author: Johannes Schlueter <johannes@schlueters.de> Date: Sat Mar 3 01:41:14 2012 +0100 WS. diff --git a/pulls/index.php b/pulls/index.php index eed9c01..d0498f4 100644 --- a/pulls/index.php +++ b/pulls/index.php @@ -97,15 +97,15 @@ $ git push origin master # everything okay? good, let's push it function repoList(baseurl, org) { this.url = baseurl+'orgs/'+org+'/repos'; - this.data = {data:[]}; - var t = this; - $("#backToRepolist").click(function () { t.showList(); }); + this.data = {data:[]}; + var t = this; + $("#backToRepolist").click(function () { t.showList(); }); } repoList.prototype.showList = function() { $("#repolist").fadeIn(); - $("#backToRepolist").hide(); - $("#repoContent").fadeOut(); + $("#backToRepolist").hide(); + $("#repoContent").fadeOut(); repos.refreshView(); } @@ -117,12 +117,12 @@ $ git push origin master # everything okay? good, let's push it repoList.prototype.refreshView = function() { $("#repolist").html($("#repoListItemTemplate").render(this.data.data)); $("#mainContent").show(); - $("li", $("#repolist")).click(function() { loadRepo($(this).attr("repo")); }); + $("li", $("#repolist")).click(function() { loadRepo($(this).attr("repo")); }); } repoList.prototype.update = function() { var t = this; $("#loading").show(); - $.ajax({ dataType: 'jsonp', url: this.url, success: function(d) { t.setData(d); $("#loading").hide(); } }); + $.ajax({ dataType: 'jsonp', url: this.url, success: function(d) { t.setData(d); $("#loading").hide(); } }); } repoList.prototype.setData = function(data) { this.data = data; @@ -133,11 +133,11 @@ $ git push origin master # everything okay? good, let's push it var t = this; this.user = false; this.logindialog = $("#loginDialog").dialog({autoOpen: false}); - this.checkLoggedIn(); - $("#notloggedin").click(function() { + this.checkLoggedIn(); + $("#notloggedin").click(function() { t.logindialog.dialog("open"); - } ); - $("#loginBtn").click(function() { + } ); + $("#loginBtn").click(function() { t.logindialog.dialog("close"); t.login(); } ); @@ -145,35 +145,35 @@ $ git push origin master # everything okay? good, let's push it loginHandler.prototype.login = function() { var user = $("#userField").attr("value"); - var pass = $("#passField").attr("value"); - var t = this; + var pass = $("#passField").attr("value"); + var t = this; - $.ajax({ url: API_URL, type: "POST", data: { action: 'login', user: user, pass: pass }, success: function(d) { t.updateLoginState(d); } }); + $.ajax({ url: API_URL, type: "POST", data: { action: 'login', user: user, pass: pass }, success: function(d) { t.updateLoginState(d); } }); } loginHandler.prototype.checkLoggedIn = function() { var t = this; $("#checkinglogin").show(); - $("#loggedin").hide(); - $("#notloggedin").hide(); - $.ajax({ url: API_URL+'?action=loggedin', success: function(d) { t.updateLoginState(d); } }); + $("#loggedin").hide(); + $("#notloggedin").hide(); + $.ajax({ url: API_URL+'?action=loggedin', success: function(d) { t.updateLoginState(d); } }); } loginHandler.prototype.updateLoginState = function(d) { var t = this; if (d.success && d.user) { $("#checkinglogin").hide(); - $("#loggedin").html("Welcome "+d.user+" (<a href='#'>Logout</a>)").fadeIn(); - $("#notloggedin").hide(); - $("#loggedin a").click(function() { + $("#loggedin").html("Welcome "+d.user+" (<a href='#'>Logout</a>)").fadeIn(); + $("#notloggedin").hide(); + $("#loggedin a").click(function() { $.ajax({ url: API_URL+'?action=logout', success: function(d) { t.updateLoginState(d); } }); - }); - this.user = d.user; - } else { + }); + this.user = d.user; + } else { $("#checkinglogin").hide(); $("#loggedin").hide(); - $("#notloggedin").fadeIn(); - this.user = false; + $("#notloggedin").fadeIn(); + this.user = false; } } @@ -192,23 +192,23 @@ $ git push origin master # everything okay? good, let's push it $.ajax({ dataType: 'jsonp', url: GITHUB_BASEURL+'repos/'+GITHUB_ORG+"/"+repo+"/pulls", - success: function (data) { - repos.hideList(); - $("#loading").hide(); - $("#mainContent").hide(); - $("#repoContent").html( $("#repoOverviewTemplate").render([{repoName: repo, pullList: $("#pullRequestListItem").render(data.data)}])); - $(".pullinstructions").click(function() { - $('<div></div>').html($("#pullInstructionTemplate").render({ repo: repo, number: $(this).attr("number")})) - .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')', width: 800 }); - }); - $(".updatepullrequest").click(function() { - var dia = $('<div></div>').html($("#updatePullRequestTemplate").render({})) - .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')' }); - $("button", dia).click(function(r, n, dia) { return function() { updateRepo(r, n, dia); }}(repo, $(this).attr("number"), dia) ); - }); - $("#repoPullList").accordion({ autoHeight: false }); - $("#repoContent").show(); - } + success: function (data) { + repos.hideList(); + $("#loading").hide(); + $("#mainContent").hide(); + $("#repoContent").html( $("#repoOverviewTemplate").render([{repoName: repo, pullList: $("#pullRequestListItem").render(data.data)}])); + $(".pullinstructions").click(function() { + $('<div></div>').html($("#pullInstructionTemplate").render({ repo: repo, number: $(this).attr("number")})) + .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')', width: 800 }); + }); + $(".updatepullrequest").click(function() { + var dia = $('<div></div>').html($("#updatePullRequestTemplate").render({})) + .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')' }); + $("button", dia).click(function(r, n, dia) { return function() { updateRepo(r, n, dia); }}(repo, $(this).attr("number"), dia) ); + }); + $("#repoPullList").accordion({ autoHeight: false }); + $("#repoContent").show(); + } }); } commit 5b3af0fc444e5681a1a024d1991c53453fa4a89f Author: Johannes Schlueter <johannes@schlueters.de> Date: Sat Mar 3 01:28:49 2012 +0100 Add login stuff and update routines. diff --git a/pulls/index.php b/pulls/index.php index fe9f836..eed9c01 100644 --- a/pulls/index.php +++ b/pulls/index.php @@ -39,6 +39,11 @@ common_header(); color: #ffcc66; text-decoration: none; } + + #loginstatus { + width: 100%; + text-align: right; + } </style> <link href="http://code.jquery.com/ui/1.8.18/themes/ui-lightness/jquery-ui.css" rel="stylesheet" type="text/css"/> <script type="text/javascript" src="http://code.jquery.com/jquery-1.7.1.min.js"></script> @@ -62,7 +67,8 @@ common_header(); <div>{{=body}}</div> <div><a href="{{=html_url}}"><img src="https://github.com/favicon.ico"> On GitHub</a> | <a href="{{=diff_url}}">Diff</a> | - <a href="#" number="{{=number}}" state="{{=state}}" title="{{=title}}" class="pullinstructions">Show Pull Instructions</a> + <a href="#" number="{{=number}}" state="{{=state}}" title="{{=title}}" class="pullinstructions">Show Pull Instructions</a> | + <a href="#" number="{{=number}}" state="{{=state}}" title="{{=title}}" class="updatepullrequest">Update</a> </div> </div> </script> @@ -75,9 +81,19 @@ $ make test # you better don't forget that $ git push origin master # everything okay? good, let's push it </pre> </script> + <script id="updatePullRequestTemplate" type="text/x-jquery-tmpl"> + State: <select id="newState"> + <option>open</option> + <option>closed</option> + </select><br> + Please provide a comment for your change:<br> + <textarea id="comment"></textarea><br> + <button>Go</button> + </script> <script type="text/javascript"> var GITHUB_BASEURL = "https://api.github.com/"; var GITHUB_ORG = "php"; + var API_URL = "api.php"; function repoList(baseurl, org) { this.url = baseurl+'orgs/'+org+'/repos'; @@ -113,8 +129,59 @@ $ git push origin master # everything okay? good, let's push it this.refreshView(); } + function loginHandler() { + var t = this; + this.user = false; + this.logindialog = $("#loginDialog").dialog({autoOpen: false}); + this.checkLoggedIn(); + $("#notloggedin").click(function() { + t.logindialog.dialog("open"); + } ); + $("#loginBtn").click(function() { + t.logindialog.dialog("close"); + t.login(); + } ); + } + + loginHandler.prototype.login = function() { + var user = $("#userField").attr("value"); + var pass = $("#passField").attr("value"); + var t = this; + + $.ajax({ url: API_URL, type: "POST", data: { action: 'login', user: user, pass: pass }, success: function(d) { t.updateLoginState(d); } }); + } + + loginHandler.prototype.checkLoggedIn = function() { + var t = this; + $("#checkinglogin").show(); + $("#loggedin").hide(); + $("#notloggedin").hide(); + $.ajax({ url: API_URL+'?action=loggedin', success: function(d) { t.updateLoginState(d); } }); + } + + loginHandler.prototype.updateLoginState = function(d) { + var t = this; + if (d.success && d.user) { + $("#checkinglogin").hide(); + $("#loggedin").html("Welcome "+d.user+" (<a href='#'>Logout</a>)").fadeIn(); + $("#notloggedin").hide(); + $("#loggedin a").click(function() { + $.ajax({ url: API_URL+'?action=logout', success: function(d) { t.updateLoginState(d); } }); + }); + this.user = d.user; + } else { + $("#checkinglogin").hide(); + $("#loggedin").hide(); + $("#notloggedin").fadeIn(); + this.user = false; + } + } + var repos; + var login; + $(document).ready(function() { + login = new loginHandler(); repos = new repoList(GITHUB_BASEURL, GITHUB_ORG); repos.update(); repos.showList(); @@ -133,18 +200,50 @@ $ git push origin master # everything okay? good, let's push it $(".pullinstructions").click(function() { $('<div></div>').html($("#pullInstructionTemplate").render({ repo: repo, number: $(this).attr("number")})) .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')', width: 800 }); - }); + }); + $(".updatepullrequest").click(function() { + var dia = $('<div></div>').html($("#updatePullRequestTemplate").render({})) + .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')' }); + $("button", dia).click(function(r, n, dia) { return function() { updateRepo(r, n, dia); }}(repo, $(this).attr("number"), dia) ); + }); $("#repoPullList").accordion({ autoHeight: false }); $("#repoContent").show(); } }); - } + } + + function updateRepo(reponame, num, dia) { + var t = this; + if (!login.user) { + window.alert("Login First!"); + return; + } + $("#loading").show(); + $.ajax({ url: API_URL, type: "POST", data: { + action: 'ghupdate', + repo: reponame, + id: num, + state: $("select", dia).attr("value"), + comment: $("textarea", dia).attr("value") + }, success: function(d) { repos.update(); } }); + dia.dialog("destroy"); + } </script> + <div id="loginstatus"> + <span id="checkinglogin">(checking login state ...)</span> + <span id="loggedin"></span> + <span id="notloggedin"><a href="#">Login</span> + </div> <h1>Github Pull Requests</h1> <div id="backToRepolist"><a href="#">&lt;&lt;&lt-- Repos</a></div> <div id="mainContent"><ul id="repolist"></ul>The PHP project is using github to mirror its git repostories from <a href="http://git.php.net">git.php.net</a>.</div> <div id="repoContent"></div> + <div id="loginDialog" title="Login"> + Username: <br><input id="userField"><br> + Password: <br><input id="passField" type="password"><br> + <button id="loginBtn">Login</button> + </div> <div id="loading">Loading</div> </td> commit 03ed80422340d5ae56e27773392ecb6717e3684f Author: Johannes Schlueter <johannes@schlueters.de> Date: Fri Mar 2 23:49:37 2012 +0100 Add local API endpoint. This will be used by the JS frontend for doing user authentication and doing write operations on GitHub. diff --git a/pulls/api.php b/pulls/api.php new file mode 100644 index 0000000..bd6c99c --- /dev/null +++ b/pulls/api.php @@ -0,0 +1,190 @@ +<?php + +const GITHUB_BASEURL = 'https://api.github.com/'; +const GITHUB_ORG = 'php'; +const GITHUB_USER = 'johannes'; +define('GITHUB_PASS', trim(file_get_contents('passwd'))); // FETCH FROM ENVIRONMENT + +if ($_SERVER['SERVER_NAME'] === 'schlueters.de') { + define('DEV', true); + error_reporting(-1); + ini_set('display_errors', 1); +} else { + define('DEV', false); +} + +function verify_password($user, $pass) +{ + global $errors; + + $post = http_build_query( + array( + 'token' => getenv('AUTH_TOKEN'), + 'username' => $user, + 'password' => $pass, + ) + ); + + $opts = array( + 'method' => 'POST', + 'header' => 'Content-type: application/x-www-form-urlencoded', + 'content' => $post, + ); + + $ctx = stream_context_create(array('http' => $opts)); + + $s = @file_get_contents('https://master.php.net/fetch/cvsauth.php', false, $ctx); + + $a = @unserialize($s); + if (!is_array($a)) { + $errors[] = "Failed to get authentication information.\nMaybe master is down?\n"; + return false; + } + if (isset($a['errno'])) { + $errors[] = "Authentication failed: {$a['errstr']}\n"; + return false; + } + + return true; +} + +function verify_password_DEV($user, $pass) +{ + global $errors; + $errors[] = "Unknown user $user (DEV)"; + return $user === 'johannes'; +} + +function ghpostcomment($pull, $comment) +{ + $post = json_encode(array("body" => "**Comment in behalf of $_SESSION[user] at php.net:**\n\n$comment")); + + + $opts = array( + 'method' => 'POST', + 'content' => $post, + ); + + $ctx = stream_context_create(array('http' => $opts)); + + $url = str_replace('https://', 'https://'.GITHUB_USER.':'.GITHUB_PASS.'@', $pull->_links->comments->href); + $s = @file_get_contents($url, false, $ctx); + return (bool)$s; +} + +function ghchangestate($pull, $state) +{ + $content = json_encode(array("state" => $state)); + + $opts = array( + 'method' => 'PATCH', + 'content' => $content + ); + + $ctx = stream_context_create(array('http' => $opts)); + + $url = str_replace('https://', 'https://'.GITHUB_USER.':'.GITHUB_PASS.'@', $pull->_links->self->href); + $s = @file_get_contents($url, false, $ctx); + return (bool)$s; +} + +function login() +{ + global $errors; + + $func = DEV ? 'verify_password_DEV' : 'verify_password'; + if ($func($_POST['user'], $_POST['pass'])) { + $_SESSION['user'] = $_POST['user']; + die(json_encode(array('success' => true, 'user' => $_POST['user']))); + } else { + header('HTTP/1.0 401 Unauthorized'); + $_SESSION['user'] = false; + die(json_encode(array('success' => false, 'errors' => $errors))); + } +} + +function logout() +{ + session_destroy(); + die(json_encode(array('success' => true))); +} + +function loggedin() +{ + $result = array( + 'success' => !empty($_SESSION['user']) + ); + if (!empty($_SESSION['user'])) { + $result['user'] = $_SESSION['user']; + } + die(json_encode($result)); +} + +function ghupdate() +{ + if (empty($_SESSION['user'])) { + header('HTTP/1.0 401 Unauthorized'); + die(json_encode(array('success' => false, 'errors' => array('Unauthorized')))); + } + + if (empty($_POST['repo'])) { + header('HTTP/1.0 400 Bad Request'); + die(json_encode(array('success' => false, 'errors' => array("No repo provided")))); + } + + if (empty($_POST['id']) || !is_numeric($_POST['id'])) { + header('HTTP/1.0 400 Bad Request'); + die(json_encode(array('success' => false, 'errors' => array("No or inalid id provided")))); + } + + if (empty($_POST['comment']) || !($comment = trim($_POST['comment']))) { + header('HTTP/1.0 400 Bad Request'); + die(json_encode(array('success' => false, 'errors' => array("No comment provided")))); + } + + if (!empty($_POST['state']) && !in_array($_POST['state'], array('open', 'closed'))) { + header('HTTP/1.0 400 Bad Request'); + die(json_encode(array('success' => false, 'errors' => array("Invalid state")))); + } + + $pull_raw = @file_get_contents(GITHUB_BASEURL.'repos/'.GITHUB_ORG.'/'.urlencode($_POST['repo']).'/pulls/'.$_POST['id']); + $pull = $pull_raw ? json_decode($pull_raw) : false; + if (!$pull) { + header('HTTP/1.0 400 Bad Request'); + die(json_encode(array('success' => false, 'errors' => array("Failed to get data from GitHub")))); + } + + $comment = @get_magic_quotes_gpc() ? stripslashes($_POST['comment']) : $_POST['comment']; + + if (!ghpostcomment($pull, $comment)) { + header('500 Internal Server Error'); + die(json_encode(array('success' => false, 'errors' => array("Failed to add comment on GitHub")))); + } + + if (!empty($_POST['state'])) { + if (!ghchangestate($pull, $_POST['state'])) { + header('500 Internal Server Error'); + die(json_encode(array('success' => false, 'errors' => array("Failed to set new state")))); + } + } + + die(json_encode(array('success' => true))); +} + +header('Content-Type: application/json'); +session_start(); + +$accepted_actions = array( + 'login', + 'logout', + 'loggedin', + 'ghupdate' +); +if (in_array($_REQUEST['action'], $accepted_actions)) { + $action = $_REQUEST['action']; + $action(); +} else { + header('HTTP/1.0 400 Bad Request'); + die(json_encode(array('success' => false, 'errors' => array("Unknown method $_REQUEST[action]")))); +} + commit 74fca45cbcdf576e57e4e09af394efcae1e760d5 Author: Johannes Schlueter <johannes@schlueters.de> Date: Fri Mar 2 22:06:21 2012 +0100 Add initial version of the GitHub Pull Request management stuff diff --git a/pulls/index.php b/pulls/index.php new file mode 100644 index 0000000..fe9f836 --- /dev/null +++ b/pulls/index.php @@ -0,0 +1,157 @@ +<?php +include("../include/functions.php"); +include("../include/release-qa.php"); + +$TITLE = "PHP-QA: GitHub Pull Requests"; +$SITE_UPDATE = date("D M d H:i:s Y T", filectime(__FILE__)); + +common_header(); + +?> +<table width="70%" border="0" cellspacing="0" cellpadding="0"> + <tr> + <td width="10"><img src="../gfx/spacer.gif" width="10" height="1"></td> + <td width="100%"> + <style> + #loading { + position:fixed; + top:50%; + padding-top:-20px; + height:40px; + left:50%; + padding-left:-50px; + width:100px; + border:1px solid black; + background-color: black; + color: white; + text-align: center; + vertical-align: center; + } + + .ghuser { + border: 1px solid #ffcc66; + float: right; + margin-left: 5px; + margin-bottom: 5px; + } + + .ghuser a { + color: #ffcc66; + text-decoration: none; + } + </style> + <link href="http://code.jquery.com/ui/1.8.18/themes/ui-lightness/jquery-ui.css" rel="stylesheet" type="text/css"/> + <script type="text/javascript" src="http://code.jquery.com/jquery-1.7.1.min.js"></script> + <script type="text/javascript" src="http://code.jquery.com/ui/1.8.18/jquery-ui.min.js"></script> + <script type="text/javascript" src="jsrender.js"></script> + <script id="repoListItemTemplate" type="text/x-jquery-tmpl"> + <li repo="{{=name}}"><b><a href="#">{{=name}}:</a></b> {{=description}} ({{=open_issues}})</li> + </script> + <script id="repoOverviewTemplate" type="text/x-jquery-tmpl"> + <h2>{{=repoName}}</h2> + <div id="repoPullList"> + {{=pullList!}} + </div> + <!-- iframe style='width:100%; height:300px;border-width:1px;' src='' id='ghframe' name='ghframe'></iframe --> + </script> + <script id="pullRequestListItem" type="text/x-jquery-tmpl"> + <h3><a href='#'>{{=number}}: {{=title}} ({{=state}})</a></h3> + <div class="pullrequest"> + <div class="ghuser"><a href="{{=user.url}}"><img src="{{=user.avatar_url}}"><br>{{=user.login}}</a></div> + <div>Created: {{=created_at}}, LastUpdated: {{=updated_at}}</div> + <div>{{=body}}</div> + <div><a href="{{=html_url}}"><img src="https://github.com/favicon.ico"> On GitHub</a> | + <a href="{{=diff_url}}">Diff</a> | + <a href="#" number="{{=number}}" state="{{=state}}" title="{{=title}}" class="pullinstructions">Show Pull Instructions</a> + </div> + </div> + </script> + <script id="pullInstructionTemplate" type="text/x-jquery-tmpl"> + <pre> +$ git fetch git://github.com/php/{{=repo}} pull/{{=number}}/head:pull-request/{{=number}} +$ git log -p pull-request/{{=number}} # REVIEW IT +$ git merge pull-request/{{=number}} # Merge it, add a GOOD commit message +$ make test # you better don't forget that +$ git push origin master # everything okay? good, let's push it + </pre> + </script> + <script type="text/javascript"> + var GITHUB_BASEURL = "https://api.github.com/"; + var GITHUB_ORG = "php"; + + function repoList(baseurl, org) { + this.url = baseurl+'orgs/'+org+'/repos'; + this.data = {data:[]}; + var t = this; + $("#backToRepolist").click(function () { t.showList(); }); + } + + repoList.prototype.showList = function() { + $("#repolist").fadeIn(); + $("#backToRepolist").hide(); + $("#repoContent").fadeOut(); + repos.refreshView(); + } + + repoList.prototype.hideList = function() { + $("#repolist").fadeOut(); + $("#backToRepolist").fadeIn(); + } + + repoList.prototype.refreshView = function() { + $("#repolist").html($("#repoListItemTemplate").render(this.data.data)); + $("#mainContent").show(); + $("li", $("#repolist")).click(function() { loadRepo($(this).attr("repo")); }); + } + repoList.prototype.update = function() { + var t = this; + $("#loading").show(); + $.ajax({ dataType: 'jsonp', url: this.url, success: function(d) { t.setData(d); $("#loading").hide(); } }); + } + repoList.prototype.setData = function(data) { + this.data = data; + this.refreshView(); + } + + var repos; + $(document).ready(function() { + repos = new repoList(GITHUB_BASEURL, GITHUB_ORG); + repos.update(); + repos.showList(); + }); + + function loadRepo(repo) { + $("#loading").show(); + $.ajax({ + dataType: 'jsonp', + url: GITHUB_BASEURL+'repos/'+GITHUB_ORG+"/"+repo+"/pulls", + success: function (data) { + repos.hideList(); + $("#loading").hide(); + $("#mainContent").hide(); + $("#repoContent").html( $("#repoOverviewTemplate").render([{repoName: repo, pullList: $("#pullRequestListItem").render(data.data)}])); + $(".pullinstructions").click(function() { + $('<div></div>').html($("#pullInstructionTemplate").render({ repo: repo, number: $(this).attr("number")})) + .dialog({title: $(this).attr("number")+': '+$(this).attr("title")+' ('+$(this).attr("state")+')', width: 800 }); + }); + $("#repoPullList").accordion({ autoHeight: false }); + $("#repoContent").show(); + } + }); + + } + </script> + <h1>Github Pull Requests</h1> + <div id="backToRepolist"><a href="#">&lt;&lt;&lt-- Repos</a></div> + <div id="mainContent"><ul id="repolist"></ul>The PHP project is using github to mirror its git repostories from <a href="http://git.php.net">git.php.net</a>.</div> + <div id="repoContent"></div> + <div id="loading">Loading</div> + + </td> + <td width="10"><img src="http://qa.php.net/gfx/spacer.gif" width="10" height="1"></td> + </tr> + </table> +<?php + +common_footer(); + diff --git a/pulls/jsrender.js b/pulls/jsrender.js new file mode 100644 index 0000000..5998650 --- /dev/null +++ b/pulls/jsrender.js @@ -0,0 +1,573 @@ +/*! JsRender v1.0pre - (jsrender.js version: does not require jQuery): http://github.com/BorisMoore/jsrender */ +/* + * Optimized version of jQuery Templates, fosr rendering to string, using 'codeless' markup. + * + * Copyright 2011, Boris Moore + * Released under the MIT License. + */ +window.JsViews || window.jQuery && jQuery.views || (function( window, undefined ) { + +var $, _$, JsViews, viewsNs, tmplEncode, render, rTag, registerTags, registerHelpers, extend, + FALSE = false, TRUE = true, + jQuery = window.jQuery, document = window.document, + htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /, + rPath = /^(true|false|null|[\d\.]+)|(\w+|\$(view|data|ctx|(\w+)))([\w\.]*)|((['"])(?:\\\1|.)*\7)$/g, + rParams = /(\$?[\w\.\[\]]+)(?:(\()|\s*(===|!==|==|!=|<|>|<=|>=)\s*|\s*(\=)\s*)?|(\,\s*)|\\?(\')|\\?(\")|(\))|(\s+)/g, + rNewLine = /\r?\n/g, + rUnescapeQuotes = /\\(['"])/g, + rEscapeQuotes = /\\?(['"])/g, + rBuildHash = /\x08([^\x08]+)\x08/g, + autoName = 0, + escapeMapForHtml = { + "&": "&amp;", + "<": "&lt;", + ">": "&gt;" + }, + htmlSpecialChar = /[\x00"&'<>]/g, + slice = Array.prototype.slice; + +if ( jQuery ) { + + //////////////////////////////////////////////////////////////////////////////////////////////// + // jQuery is loaded, so make $ the jQuery object + $ = jQuery; + + $.fn.extend({ + // Use first wrapped element as template markup. + // Return string obtained by rendering the template against data. + render: function( data, context, parentView, path ) { + return render( data, this[0], context, parentView, path ); + }, + + // Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template. + template: function( name, context ) { + return $.template( name, this[0], context ); + } + }); + +} else { + + //////////////////////////////////////////////////////////////////////////////////////////////// + // jQuery is not loaded. Make $ the JsViews object + + // Map over the $ in case of overwrite + _$ = window.$; + + window.JsViews = JsViews = window.$ = $ = { + extend: function( target, source ) { + var name; + for ( name in source ) { + target[ name ] = source[ name ]; + } + return target; + }, + isArray: Array.isArray || function( obj ) { + return Object.prototype.toString.call( obj ) === "[object Array]"; + }, + noConflict: function() { + if ( window.$ === JsViews ) { + window.$ = _$; + } + return JsViews; + } + }; +} + +extend = $.extend; + +//================= +// View constructor +//================= + +function View( context, path, parentView, data, template ) { + // Returns a view data structure for a new rendered instance of a template. + // The content field is a hierarchical array of strings and nested views. + + parentView = parentView || { viewsCount:0, ctx: viewsNs.helpers }; + + var parentContext = parentView && parentView.ctx; + + return { + jsViews: "v1.0pre", + path: path || "", + // inherit context from parentView, merged with new context. + itemNumber: ++parentView.viewsCount || 1, + viewsCount: 0, + tmpl: template, + data: data || parentView.data || {}, + // Set additional context on this view (which will modify the context inherited from the parent, and be inherited by child views) + ctx : context && context === parentContext + ? parentContext + : (parentContext ? extend( extend( {}, parentContext ), context ) : context||{}), + // If no jQuery, extend does not support chained copies - so limit to two parameters + parent: parentView + }; +} +extend( $, { + views: viewsNs = { + templates: {}, + tags: { + "if": function() { + var ifTag = this, + view = ifTag._view; + view.onElse = function( presenter, args ) { + var i = 0, + l = args.length; + while ( l && !args[ i++ ]) { + // Only render content if args.length === 0 (i.e. this is an else with no condition) or if a condition argument is truey + if ( i === l ) { + return ""; + } + } + view.onElse = undefined; // If condition satisfied, so won't run 'else'. + return render( view.data, presenter.tmpl, view.ctx, view); + }; + return view.onElse( this, arguments ); + }, + "else": function() { + var view = this._view; + return view.onElse ? view.onElse( this, arguments ) : ""; + }, + each: function() { + var i, + self = this, + result = "", + args = arguments, + l = args.length, + content = self.tmpl, + view = self._view; + for ( i = 0; i < l; i++ ) { + result += args[ i ] ? render( args[ i ], content, self.ctx || view.ctx, view, self._path, self._ctor ) : ""; + } + return l ? result + // If no data parameter, use the current $data from view, and render once + : result + render( view.data, content, view.ctx, view, self._path, self.tag ); + }, + "=": function( value ) { + return value; + }, + "*": function( value ) { + return value; + } + }, + helpers: { + not: function( value ) { + return !value; + } + }, + allowCode: FALSE, + debugMode: TRUE, + err: function( e ) { + return viewsNs.debugMode ? ("<br/><b>Error:</b> <em> " + (e.message || e) + ". </em>"): '""'; + }, + +//=============== +// setDelimiters +//=============== + + setDelimiters: function( openTag, closeTag ) { + // Set or modify the delimiter characters for tags: "{{" and "}}" + var firstCloseChar = closeTag.charAt( 0 ), + secondCloseChar = closeTag.charAt( 1 ); + openTag = "\\" + openTag.charAt( 0 ) + "\\" + openTag.charAt( 1 ); + closeTag = "\\" + firstCloseChar + "\\" + secondCloseChar; + + // Build regex with new delimiters + // {{ + rTag = openTag + // # tag (followed by space,! or }) or equals or code + + "(?:(?:(\\#)?(\\w+(?=[!\\s\\" + firstCloseChar + "]))" + "|(?:(\\=)|(\\*)))" + // params + + "\\s*((?:[^\\" + firstCloseChar + "]|\\" + firstCloseChar + "(?!\\" + secondCloseChar + "))*?)" + // encoding + + "(!(\\w*))?" + // closeBlock + + "|(?:\\/([\\w\\$\\.\\[\\]]+)))" + // }} + + closeTag; + + // Default rTag: # tag equals code params encoding closeBlock + // /\{\{(?:(?:(\#)?(\w+(?=[\s\}!]))|(?:(\=)|(\*)))((?:[^\}]|\}(?!\}))*?)(!(\w*))?|(?:\/([\w\$\.\[\]]+)))\}\}/g; + + rTag = new RegExp( rTag, "g" ); + }, + + +//=============== +// registerTags +//=============== + + // Register declarative tag. + registerTags: registerTags = function( name, tagFn ) { + var key; + if ( typeof name === "object" ) { + for ( key in name ) { + registerTags( key, name[ key ]); + } + } else { + // Simple single property case. + viewsNs.tags[ name ] = tagFn; + } + return this; + }, + +//=============== +// registerHelpers +//=============== + + // Register helper function for use in markup. + registerHelpers: registerHelpers = function( name, helper ) { + if ( typeof name === "object" ) { + // Object representation where property name is path and property value is value. + // TODO: We've discussed an "objectchange" event to capture all N property updates here. See TODO note above about propertyChanges. + var key; + for ( key in name ) { + registerHelpers( key, name[ key ]); + } + } else { + // Simple single property case. + viewsNs.helpers[ name ] = helper; + } + return this; + }, + +//=============== +// tmpl.encode +//=============== + + encode: function( encoding, text ) { + return text + ? ( tmplEncode[ encoding || "html" ] || tmplEncode.html)( text ) // HTML encoding is the default + : ""; + }, + + encoders: tmplEncode = { + "none": function( text ) { + return text; + }, + "html": function( text ) { + // HTML encoding helper: Replace < > & and ' and " by corresponding entities. + // Implementation, from Mike Samuel <msamuel@google.com> + return String( text ).replace( htmlSpecialChar, replacerForHtml ); + } + //TODO add URL encoding, and perhaps other encoding helpers... + }, + +//=============== +// renderTag +//=============== + + renderTag: function( tag, view, encode, content, tagProperties ) { + // This is a tag call, with arguments: "tag", view, encode, content, presenter [, params...] + var ret, ctx, name, + args = arguments, + presenters = viewsNs.presenters; + hash = tagProperties._hash, + tagFn = viewsNs.tags[ tag ]; + + if ( !tagFn ) { + return ""; + } + + content = content && view.tmpl.nested[ content - 1 ]; + tagProperties.tmpl = tagProperties.tmpl || content || undefined; + // Set the tmpl property to the content of the block tag, unless set as an override property on the tag + + if ( presenters && presenters[ tag ]) { + ctx = extend( extend( {}, tagProperties.ctx ), tagProperties ); + delete ctx.ctx; + delete ctx._path; + delete ctx.tmpl; + tagProperties.ctx = ctx; + tagProperties._ctor = tag + (hash ? "=" + hash.slice( 0, -1 ) : ""); + + tagProperties = extend( extend( {}, tagFn ), tagProperties ); + tagFn = viewsNs.tags.each; // Use each to render the layout template against the data + } + + tagProperties._encode = encode; + tagProperties._view = view; + ret = tagFn.apply( tagProperties, args.length > 5 ? slice.call( args, 5 ) : [view.data] ); + return ret || (ret === undefined ? "" : ret.toString()); // (If ret is the value 0 or false or null, will render to string) + } + }, + +//=============== +// render +//=============== + + render: render = function( data, tmpl, context, parentView, path, tagName ) { + // Render template against data as a tree of subviews (nested template), or as a string (top-level template). + // tagName parameter for internal use only. Used for rendering templates registered as tags (which may have associated presenter objects) + var i, l, dataItem, arrayView, content, result = ""; + + if ( arguments.length === 2 && data.jsViews ) { + parentView = data; + context = parentView.ctx; + data = parentView.data; + } + tmpl = $.template( tmpl ); + if ( !tmpl ) { + return ""; // Could throw... + } + + if ( $.isArray( data )) { + // Create a view item for the array, whose child views correspond to each data item. + arrayView = new View( context, path, parentView, data); + l = data.length; + for ( i = 0, l = data.length; i < l; i++ ) { + dataItem = data[ i ]; + content = dataItem ? tmpl( dataItem, new View( context, path, arrayView, dataItem, tmpl, this )) : ""; + result += viewsNs.activeViews ? "<!--item-->" + content + "<!--/item-->" : content; + } + } else { + result += tmpl( data, new View( context, path, parentView, data, tmpl )); + } + + return viewsNs.activeViews + // If in activeView mode, include annotations + ? "<!--tmpl(" + (path || "") + ") " + (tagName ? "tag=" + tagName : tmpl._name) + "-->" + result + "<!--/tmpl-->" + // else return just the string result + : result; + }, + +//=============== +// template +//=============== + + template: function( name, tmpl ) { + // Set: + // Use $.template( name, tmpl ) to cache a named template, + // where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc. + // Use $( "selector" ).template( name ) to provide access by name to a script block template declaration. + + // Get: + // Use $.template( name ) to access a cached template. + // Also $( selectorToScriptBlock ).template(), or $.template( null, templateString ) + // will return the compiled template, without adding a name reference. + // If templateString is not a selector, $.template( templateString ) is equivalent + // to $.template( null, templateString ). To ensure a string is treated as a template, + // include an HTML element, an HTML comment, or a template comment tag. + + if (tmpl) { + // Compile template and associate with name + if ( "" + tmpl === tmpl ) { // type string + // This is an HTML string being passed directly in. + tmpl = compile( tmpl ); + } else if ( jQuery && tmpl instanceof $ ) { + tmpl = tmpl[0]; + } + if ( tmpl ) { + if ( jQuery && tmpl.nodeType ) { + // If this is a template block, use cached copy, or generate tmpl function and cache. + tmpl = $.data( tmpl, "tmpl" ) || $.data( tmpl, "tmpl", compile( tmpl.innerHTML )); + } + viewsNs.templates[ tmpl._name = tmpl._name || name || "_" + autoName++ ] = tmpl; + } + return tmpl; + } + // Return named compiled template + return name + ? "" + name !== name // not type string + ? (name._name + ? name // already compiled + : $.template( null, name )) + : viewsNs.templates[ name ] || + // If not in map, treat as a selector. (If integrated with core, use quickExpr.exec) + $.template( null, htmlExpr.test( name ) ? name : try$( name )) + : null; + } +}); + +viewsNs.setDelimiters( "{{", "}}" ); + +//================= +// compile template +//================= + +// Generate a reusable function that will serve to render a template against data +// (Compile AST then build template function) + +function parsePath( all, comp, object, viewDataCtx, viewProperty, path, string, quot ) { + return object + ? ((viewDataCtx + ? viewProperty + ? ("$view." + viewProperty) + : object + :("$data." + object) + ) + ( path || "" )) + : string || (comp || ""); +} + +function compile( markup ) { + var newNode, + loc = 0, + stack = [], + topNode = [], + content = topNode, + current = [,,topNode]; + + function pushPreceedingContent( shift ) { + shift -= loc; + if ( shift ) { + content.push( markup.substr( loc, shift ).replace( rNewLine,"\\n")); + } + } + + function parseTag( all, isBlock, tagName, equals, code, params, useEncode, encode, closeBlock, index ) { + // rTag : # tagName equals code params encode closeBlock + // /\{\{(?:(?:(\#)?(\w+(?=[\s\}!]))|(?:(\=)|(\*)))((?:[^\}]|\}(?!\}))*?)(!(\w*))?|(?:\/([\w\$\.\[\]]+)))\}\}/g; + + // Build abstract syntax tree: [ tagName, params, content, encode ] + var named, + hash = "", + parenDepth = 0, + quoted = FALSE, // boolean for string content in double qoutes + aposed = FALSE; // or in single qoutes + + function parseParams( all, path, paren, comp, eq, comma, apos, quot, rightParen, space, index ) { + // path paren eq comma apos quot rtPrn space + // /(\$?[\w\.\[\]]+)(?:(\()|(===)|(\=))?|(\,\s*)|\\?(\')|\\?(\")|(\))|(\s+)/g + + return aposed + // within single-quoted string + ? ( aposed = !apos, (aposed ? all : '"')) + : quoted + // within double-quoted string + ? ( quoted = !quot, (quoted ? all : '"')) + : comp + // comparison + ? ( path.replace( rPath, parsePath ) + comp) + : eq + // named param + ? parenDepth ? "" :( named = TRUE, '\b' + path + ':') + : paren + // function + ? (parenDepth++, path.replace( rPath, parsePath ) + '(') + : rightParen + // function + ? (parenDepth--, ")") + : path + // path + ? path.replace( rPath, parsePath ) + : comma + ? "," + : space + ? (parenDepth + ? "" + : named + ? ( named = FALSE, "\b") + : "," + ) + : (aposed = apos, quoted = quot, '"'); + } + + tagName = tagName || equals; + pushPreceedingContent( index ); + if ( code ) { + if ( viewsNs.allowCode ) { + content.push([ "*", params.replace( rUnescapeQuotes, "$1" )]); + } + } else if ( tagName ) { + if ( tagName === "else" ) { + current = stack.pop(); + content = current[ 2 ]; + isBlock = TRUE; + } + params = (params + ? (params + " ") + .replace( rParams, parseParams ) + .replace( rBuildHash, function( all, keyValue, index ) { + hash += keyValue + ","; + return ""; + }) + : ""); + params = params.slice( 0, -1 ); + newNode = [ + tagName, + useEncode ? encode || "none" : "", + isBlock && [], + "{" + hash + "_hash:'" + hash + "',_path:'" + params + "'}", + params + ]; + + if ( isBlock ) { + stack.push( current ); + current = newNode; + } + content.push( newNode ); + } else if ( closeBlock ) { + current = stack.pop(); + } + loc = index + all.length; // location marker - parsed up to here + if ( !current ) { + throw "Expected block tag"; + } + content = current[ 2 ]; + } + markup = markup.replace( rEscapeQuotes, "\\$1" ); + markup.replace( rTag, parseTag ); + pushPreceedingContent( markup.length ); + return buildTmplFunction( topNode ); +} + +// Build javascript compiled template function, from AST +function buildTmplFunction( nodes ) { + var ret, node, i, + nested = [], + l = nodes.length, + code = "try{var views=" + + (jQuery ? "jQuery" : "JsViews") + + '.views,tag=views.renderTag,enc=views.encode,html=views.encoders.html,$ctx=$view && $view.ctx,result=""+\n\n'; + + for ( i = 0; i < l; i++ ) { + node = nodes[ i ]; + if ( node[ 0 ] === "*" ) { + code = code.slice( 0, i ? -1 : -3 ) + ";" + node[ 1 ] + ( i + 1 < l ? "result+=" : "" ); + } else if ( "" + node === node ) { // type string + code += '"' + node + '"+'; + } else { + var tag = node[ 0 ], + encode = node[ 1 ], + content = node[ 2 ], + obj = node[ 3 ], + params = node[ 4 ], + paramsOrEmptyString = params + '||"")+'; + + if( content ) { + nested.push( buildTmplFunction( content )); + } + code += tag === "=" + ? (!encode || encode === "html" + ? "html(" + paramsOrEmptyString + : encode === "none" + ? ("(" + paramsOrEmptyString) + : ('enc("' + encode + '",' + paramsOrEmptyString) + ) + : 'tag("' + tag + '",$view,"' + ( encode || "" ) + '",' + + (content ? nested.length : '""') // For block tags, pass in the key (nested.length) to the nested content template + + "," + obj + (params ? "," : "") + params + ")+"; + } + } + ret = new Function( "$data, $view", code.slice( 0, -1) + ";return result;\n\n}catch(e){return views.err(e);}" ); + ret.nested = nested; + return ret; +} + +//========================== Private helper functions, used by code above ========================== + +function replacerForHtml( ch ) { + // Original code from Mike Samuel <msamuel@google.com> + return escapeMapForHtml[ ch ] + // Intentional assignment that caches the result of encoding ch. + || ( escapeMapForHtml[ ch ] = "&#" + ch.charCodeAt( 0 ) + ";" ); +} + +function try$( selector ) { + // If selector is valid, return jQuery object, otherwise return (invalid) selector string + try { + return $( selector ); + } catch( e) {} + return selector; +} +})( window ); Thank you for your contribution.

« previous php.qa (#66124) next »