2 Copyright (C) 2005, Artem Khodush <greenkaa@gmail.com>
4 This file is licensed under the GNU General Public License version 2.
7 if( typeof( GitBrowser )=="undefined" ) {
11 if( typeof( InvisibleRequest )=="undefined" ) {
12 alert( "javascript file is omitted (InvisibleRequest.js) - this page will not work properly" );
16 GitBrowser.set_error_handler=function( handler )
18 GitBrowser._user_error_handler=handler;
20 GitBrowser._user_error_handler=function( msg )
24 GitBrowser._error_handler=function( msg, arg )
26 GitBrowser._user_error_handler( msg );
29 GitBrowser._next_call_server( arg );
31 arg.final_handler( arg.final_handler_arg );
34 GitBrowser._server_handler=function( doc, arg )
36 if( doc.error!=null ) {
37 GitBrowser._error_handler( doc.error, arg );
39 arg.handler( doc.result, arg.chain[arg.chain_i].handler_arg );
41 GitBrowser._next_call_server( arg );
44 GitBrowser._url_adjust=function( url )
46 var base=location.protocol+"//"+location.host;
47 var urlparts=url.match( /^[A-Za-z+.-]+:\/\/[^\s\/]+(\/.*)$/ );
49 return base+urlparts[1];
51 return base+(url[0]=="/"?"":"/")+url;
54 cfg_gitweb_url=GitBrowser._url_adjust( cfg_gitweb_url );
55 cfg_browsercgi_url=GitBrowser._url_adjust( cfg_browsercgi_url );
56 GitBrowser._g_server_url=cfg_browsercgi_url;
57 GitBrowser._g_server_timeout_seconds=132;
58 GitBrowser._make_server_url=function( arg )
60 var url=GitBrowser._g_server_url+"?";
61 url+="sub="+encodeURIComponent( arg.sub );
62 if( arg.repo!=null ) {
63 url+="&repo="+encodeURIComponent( arg.repo );
65 if( arg.sub_args!=null ) {
67 for( sub_arg_name in arg.sub_args ) {
68 var sub_arg=arg.sub_args[sub_arg_name];
69 for( var sub_i=0; sub_i<sub_arg.length; ++sub_i ) {
70 var value=sub_arg[sub_i];
71 if( value!=null && !value.match( /^\s*$/ ) ) {
72 url+="&"+sub_arg_name+"="+encodeURIComponent( value );
79 GitBrowser._next_call_server=function( arg )
81 if( arg.chain_i<arg.chain.length ) {
82 if( arg.before_handler!=null ) {
83 arg.before_handler( arg.chain[arg.chain_i].handler_arg );
85 InvisibleRequest.get( { url: GitBrowser._make_server_url( arg.chain[arg.chain_i] ),
86 handler: GitBrowser._server_handler,
88 error_handler: GitBrowser._error_handler,
89 timeout_seconds: GitBrowser._g_server_timeout_seconds
92 arg.final_handler( arg.final_handler_arg );
96 // before_handler: called before each server request
97 // final_handler: called when all requests are finished
98 // final_handler_arg: the only argument to the previous
99 // chain: array of { sub: repo: handler_arg: sub_args: }. if null, its assumed to be the one-element chain with the following items specified directly:
101 // repo: repo_name (optional)
102 // handler_arg: second argument for handler
103 // sub_args: [array of sub arguments]
104 GitBrowser.call_server=function( arg )
108 chain=[ { sub: arg.sub, repo: arg.repo, handler_arg: arg.handler_arg, sub_args: arg.sub_args } ];
110 GitBrowser._next_call_server( { chain: chain, chain_i: 0, handler: arg.handler, before_handler: arg.before_handler, final_handler: arg.final_handler, final_handler_arg: arg.final_handler_arg } );
114 // status_show, error_show
115 GitBrowser._g_status_div=null;
116 GitBrowser._g_error_div=null;
118 GitBrowser.setup_status_error=function()
120 var status=document.createElement( "DIV" );
121 status.style.display="none";
122 status.style.position="absolute";
123 status.style.top="0";
124 status.style.right="3em";
125 status.style.fontSize="10pt";
126 status.style.paddingTop="2px";
127 status.style.paddingBottom="2px";
128 status.style.paddingLeft="5px";
129 status.style.paddingRight="5px";
130 status.style.color="#ffffff";
131 status.style.backgroundColor="#090";
132 document.body.appendChild( status );
133 GitBrowser._g_status_div=status;
134 var error=document.createElement( "DIV" );
135 var error_close=document.createElement( "SPAN" );
136 error_close.appendChild( document.createTextNode( "close" ) );
137 error.appendChild( error_close );
138 error.appendChild( document.createElement( "SPAN" ) );
139 error.style.display="none";
140 error.style.border="1px solid #a00";
141 error.style.color="#800";
142 error.style.backgroundColor="#ffffff";
143 error.style.paddingTop="3px";
144 error.style.paddingBottom="3px";
145 error.style.paddingLeft="5px";
146 error.style.paddingRight="5px";
147 error.style.position="absolute";
148 error.style.top="3px";
149 error.style.left="3px";
150 error.style.zIndex="10";
151 error_close.style.color="#ffffff";
152 error_close.style.backgroundColor="#a22";
153 error_close.style.marginTop="3px";
154 error_close.style.marginBottom="3px";
155 error_close.style.marginLeft="1em";
156 error_close.style.marginRight="5px";
157 error_close.style.paddingTop="0";
158 error_close.style.paddingBottom="0";
159 error_close.style.paddingLeft="3px";
160 error_close.style.paddingRight="3px";
161 error_close.style.cursor="pointer";
162 error_close.onclick=GitBrowser.error_close;
163 document.body.appendChild( error );
164 GitBrowser._g_error_div=error;
165 GitBrowser.set_error_handler( GitBrowser.error_show );
167 GitBrowser.status_show=function( msg )
169 if( GitBrowser._g_status_div!=null ) {
170 if( msg!=null && msg!="" ) {
171 GitBrowser._g_status_div.innerHTML="";
172 GitBrowser._g_status_div.appendChild( document.createTextNode( msg ) );
173 GitBrowser._g_status_div.style.display="block";
175 GitBrowser._g_status_div.style.display="none";
179 GitBrowser.error_show=function( msg )
181 GitBrowser.status_show();
182 GitBrowser._g_error_div.lastChild.innerHTML="";
183 GitBrowser._g_error_div.lastChild.appendChild( document.createTextNode( "Error: "+msg ) );
184 GitBrowser._g_error_div.style.display="block";
186 GitBrowser.error_close=function()
188 GitBrowser._g_error_div.style.display="none";
191 // decode / encode selected repositories and refs as url parameters / text description
192 // repos={ repo_name => { all_heads: boolean, heads: [strings], tags: [strings] } }
193 GitBrowser.repos_decode_location=function( location )
196 var args=location.search;
197 if( args.charAt( 0 )=="?" ) {
198 args=args.slice( 1 );
200 if( args.length>0 ) {
201 args=args.split( "&" );
202 for( var arg_i=0; arg_i<args.length; ++arg_i ) {
203 var arg=args[arg_i].split( "=" );
205 var repo_name=arg[1];
206 if( repos[repo_name]==null ) {
207 repos[repo_name]={ heads: [], tags: [] };
209 repos[repo_name].all_heads=true;
210 }else if( arg[0]=="h" || arg[0]=="t" ) {
211 var ref=arg[1].split( "," );
212 var repo_name=ref[0];
214 if( repos[repo_name]==null ) {
215 repos[repo_name]={ heads: [], tags: [] };
218 repos[repo_name].heads.push( ref_name );
220 repos[repo_name].tags.push( ref_name );
227 GitBrowser.repos_encode_url_param=function( repos )
230 for( var repo_name in repos ) {
231 var repo=repos[repo_name];
232 if( repo.all_heads ) {
233 params.push( "r="+encodeURIComponent( repo_name ) );
235 for( var head_i=0; head_i<repo.heads.length; ++head_i ) {
236 params.push( "h="+encodeURIComponent( repo_name )+","+encodeURIComponent( repo.heads[head_i] ) );
238 for( var tag_i=0; tag_i<repo.tags.length; ++tag_i ) {
239 params.push( "t="+encodeURIComponent( repo_name )+","+encodeURIComponent( repo.tags[tag_i] ) );
242 return params.join( "&" );
244 GitBrowser.repos_encode_text=function( repos )
247 for( var repo_name in repos ) {
248 var repo=repos[repo_name];
249 if( repo.all_heads ) {
250 text.push( "all "+repo_name+" heads" );
252 if( repo.heads.length>0 ) {
253 text.push( repo_name+" heads: "+repo.heads.join( " " ) );
255 if( repo.tags.length>0 ) {
256 text.push( repo_name+" tags: "+repo.tags.join( " " ) );
259 return text.join( "; " );
264 // dialog: HTML filter div element
265 // x, y: filter dialog absolute pos
266 // apply_handler: called when "reload" filter button is clicked. argument: { exclude_commits: [], paths: [] }
267 // apply_handler_context: second argument to apply_handler
268 // exclude_edit: HTML edit element for commits to exclude
269 // paths_edit: HTML edit element for paths to limit git-rev-list output
270 GitBrowser._g_filter={};
272 GitBrowser._filter_dialog_close=function()
274 GitBrowser._g_filter.dialog.style.display="none";
276 GitBrowser._filter_dialog_apply=function()
278 var exclude_commits=GitBrowser._g_filter.exclude_edit.value.split( " " );
279 var paths=GitBrowser._g_filter.paths_edit.value.split( " " );
280 GitBrowser._g_filter.dialog.style.display="none";
281 GitBrowser._g_filter.apply_handler( { exclude_commits: exclude_commits, paths: paths }, GitBrowser._g_filter.apply_handler_context );
283 GitBrowser._filter_dialog_clear=function()
285 GitBrowser._g_filter.exclude_edit.value="";
286 GitBrowser._g_filter.paths_edit.value="";
288 GitBrowser._filter_dialog_show=function()
290 if( GitBrowser._g_filter.dialog.style.display=="none" ) {
291 GitBrowser._g_filter.dialog.style.display="";
292 var y=GitBrowser._g_filter.y;
293 if( y>500 ) { // XXX it's random
294 y-=GitBrowser._g_filter.dialog.clientHeight;
296 Motion.set_page_coords( GitBrowser._g_filter.dialog, GitBrowser._g_filter.x, y );
298 GitBrowser._g_filter.dialog.style.display="none";
301 GitBrowser._filter_dialog_loaded=function( template, arg )
305 _process: function( n ) { GitBrowser._g_filter.dialog=n; },
307 filterexclude: { _process: function( n ) { GitBrowser._g_filter.exclude_edit=n; } },
308 filterpath: { _process: function( n ) { GitBrowser._g_filter.paths_edit=n; } }
310 filterreload: { _process: function( n ) { n.onclick=GitBrowser._filter_dialog_apply; n.href="#"; } },
311 filterclear: { _process: function( n ) { n.onclick=GitBrowser._filter_dialog_clear; n.href="#"; } },
312 filterclose: { _process: function( n ) { n.onclick=GitBrowser._filter_dialog_close; n.href="#"; } }
315 DomTemplate.apply( template, data, document.body );
316 GitBrowser._g_filter.x=arg.x;
317 GitBrowser._g_filter.y=arg.y;
318 GitBrowser._g_filter.apply_handler=arg.apply_handler;
319 GitBrowser._g_filter.apply_handler_context=arg.apply_handler_context;
320 arg.show_button.onclick=GitBrowser._filter_dialog_show;
323 // show_button: its onclick will show filter
324 // x, y: filter dialog pos
325 // apply_handler: called when "reload" filter button is clicked. argument: { exclude_commits: [], paths: [] }
326 GitBrowser.filter_dialog_init=function( arg )
328 InvisibleRequest.get_element( { url: "templates.html", element_id: "filterdialogtemplate",
329 handler: GitBrowser._filter_dialog_loaded, handler_arg: arg,
330 error_handler: GitBrowser.error_show } );
335 GitBrowser._g_title={};
336 GitBrowser._title_loaded=function( template, arg )
338 var selected_text=GitBrowser.repos_encode_text( arg.repos );
339 if( selected_text=="" ) {
340 selected_text="none selected";
344 _process: function( n ) { arg.title_div=n; },
345 selectedtext: selected_text,
346 selectother: { _process: function( n ) { arg.select_other_btn=n } },
347 commitcount: { _process: function( n ) { GitBrowser._g_title.commitcount=n; } },
348 loadmore: { _process: function( n, context ) { GitBrowser._g_title.loadmore=n; arg.load_more_button_init( n ); }, _process_arg: arg },
349 filtershow: { _process: function( n, context ) { n.href="#"; arg.filter_button_init( n, context ); }, _process_arg: arg }
352 DomTemplate.apply( template, data, document.body );
353 if( arg.title_loaded_handler!=null ) {
354 arg.title_loaded_handler( arg );
356 arg.exclude_commits=[];
358 GitBrowser.commits_load_first( arg );
362 // load_more_button_init: function( b )
363 // filter_button_init: function( b )
364 // title_loaded_handler: called when the title is loaded into the document, takes title_div as an argument
365 // commits_first_loaded_handler: function( context )
366 // commits_more_loaded_handler: function( context )
368 // diagram: GitDiagram object
370 // repos: as returned by repos_decode_location
373 // exclude_commits: []
376 GitBrowser.title_init=function( arg )
378 InvisibleRequest.get_element( { url: "templates.html", element_id: "titletemplate",
379 handler: GitBrowser._title_loaded, handler_arg: arg,
380 error_handler: GitBrowser.error_show } );
384 GitBrowser.title_update=function( arg )
386 GitBrowser._g_title.commitcount.innerHTML="";
387 GitBrowser._g_title.commitcount.appendChild( document.createTextNode( "Loaded "+arg.diagram.get_commit_count()+" commits " ) );
388 var need_more=arg.diagram.get_start_more_ids().length!=0;
389 GitBrowser._g_title.loadmore.style.visibility= need_more ? "visible" : "hidden";
392 // diagram loading (calls only add_node)
393 GitBrowser._add_refs_and_commits=function( data, arg )
395 for( var i=0; i<data.refs.length; ++i ) {
396 arg.diagram.add_label( data.refs[i].id, data.refs[i].name, arg.repo_name, data.refs[i].type );
398 GitBrowser._add_commits( data.commits, arg );
400 GitBrowser._add_commits=function( commits, arg )
403 for( var commit_id in commits ) {
404 tmp.push( commit_id );
407 for( var tmp_i=0; tmp_i<tmp.length; ++tmp_i ) {
408 var commit=commits[tmp[tmp_i]];
409 if( (commit.committer_epoch!=null || commit.author_epoch!=null) && commit.id!=null && commit.author!=null && commit.parents!=null ) {
410 var committer_time=commit.committer_epoch==null ? null : commit.committer_epoch*1000;
411 var author_time=commit.author_epoch==null ? null : commit.author_epoch*1000;
412 var comment=commit.comment==null ? "" : commit.comment.join( " " );
413 arg.diagram.add_node( commit.id, committer_time, author_time, commit.author, comment, commit.parents, arg.repo_name );
418 // repos: as returned by repos_decode_location
420 // exclude_commits: [], as passed to apply_handler first arg in filter_dialog
421 // paths: [], as passed to apply_handler first arg in filter_dialog
422 // commits_first_loaded_handler: function( arg )
423 GitBrowser.commits_load_first=function( arg )
426 for( var repo_name in arg.repos ) {
427 var repo=arg.repos[repo_name];
429 if( repo.all_heads ) {
430 refs.push( "r,all" );
432 for( var head_i=0; head_i<repo.heads.length; ++head_i ) {
433 refs.push( "h,"+repo.heads[head_i] );
435 for( var tag_i=0; tag_i<repo.tags.length; ++tag_i ) {
436 refs.push( "t,"+repo.tags[tag_i] );
439 chain.push( { sub: "commits_from_refs", repo: repo_name, handler_arg: { diagram: arg.diagram, repo_name: repo_name },
440 sub_args: { ref: refs, x: arg.exclude_commits, path: arg.paths, shortcomment: [arg.shortcomment] }
444 GitBrowser.status_show( "loading..." );
445 GitBrowser.call_server( { handler: GitBrowser._add_refs_and_commits,
446 final_handler: function( arg ) { GitBrowser.status_show( "" ); arg.commits_first_loaded_handler( arg ); }, final_handler_arg: arg,
451 // exclude_commits: [], as passed to apply_handler first arg in filter_dialog
452 // paths: [], as passed to apply_handler first arg in filter_dialog
453 // commits_more_loaded_handler: function( arg )
454 GitBrowser.commits_load_more=function( arg )
457 var more_ids=arg.diagram.get_start_more_ids();
458 for( var i=0; i<more_ids.length; ++i ) {
460 for( var repo_i=0; repo_i<id.repos.length; ++repo_i ) {
461 var repo_name=id.repos[repo_i];
462 if( repo_map[repo_name]==null ) {
463 repo_map[repo_name]=[[]];
465 var ids=repo_map[repo_name][repo_map[repo_name].length-1];
466 if( ids.length>9 ) { // split to avoid too long urls - for now, the limit is 10 40-byte ids per url.
467 // since server does not keep track which commits were already sent to which client,
468 // splitting requests may cause redundant data to be transferred.
469 repo_map[repo_name].push( [] );
470 ids=repo_map[repo_name][repo_map[repo_name].length-1];
476 for( var repo_name in repo_map ) {
477 var ids_a=repo_map[repo_name];
478 for( var i=0; i<ids_a.length; ++i ) {
481 chain.push( { sub: "commits_from_ids", repo: repo_name, handler_arg: { diagram: arg.diagram, repo_name: repo_name },
482 sub_args: { id: ids, x: arg.exclude_commits, path: arg.paths, shortcomment: [arg.shortcomment] }
486 GitBrowser.status_show( "loading..." );
487 GitBrowser.call_server( { handler: GitBrowser._add_commits,
488 final_handler: function( arg ) { GitBrowser.status_show( "" ); arg.commits_more_loaded_handler( arg ); }, final_handler_arg: arg,
492 // glue code that appears to be common between by-date.html and by-commits.html
493 // diagram ui handler. first argument should be ui handlers map: event_name=>handler
494 GitBrowser.diagram_ui_handler=function()
496 var ui_map=arguments[0];
497 var event_name=arguments[1];
499 for( var i=2; i<arguments.length; ++i ) {
500 args.push(arguments[i] );
502 var handler=ui_map[event_name];
503 if( handler!=null ) {
504 handler.apply( this, args );
508 GitBrowser.filter_dialog_handler=function( arg, context )
510 context.exclude_commits=arg.exclude_commits;
511 context.paths=arg.paths;
512 context.diagram.clear();
513 GitBrowser.commits_load_first( context );
515 GitBrowser.filter_dialog_create=function( filter_button, context )
517 var ref_pos=Motion.get_page_coords( filter_button );
518 var x=ref_pos.x+filter_button.clientWidth;
519 var y=ref_pos.y+2+filter_button.scrollHeight;
520 GitBrowser.filter_dialog_init( { show_button: filter_button, x: x, y: y, apply_handler: GitBrowser.filter_dialog_handler, apply_handler_context: context } );
524 // repos: as as returned by repos_decode_location
525 // diagram: GitDiagram object
526 // title_loaded_handler
527 // commits_first_loaded_handler
528 // commits_more_loaded_handler
529 GitBrowser.init=function( arg )
531 GitBrowser.setup_status_error();
532 arg.load_more_button_init=function( b ) { b.href="#"; b.onclick=function() { GitBrowser.commits_load_more( arg ) }; };
533 arg.filter_button_init=GitBrowser.filter_dialog_create;
534 GitBrowser.title_init( arg );