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" ) {
9 GitBrowser
._templates_url
="templates.html?v="+Math
.random();
12 if( typeof( InvisibleRequest
)=="undefined" ) {
13 alert( "javascript file is omitted (InvisibleRequest.js) - this page will not work properly" );
17 GitBrowser
.set_error_handler=function( handler
)
19 GitBrowser
._user_error_handler
=handler
;
21 GitBrowser
._user_error_handler=function( msg
)
25 GitBrowser
._error_handler=function( msg
, arg
)
27 GitBrowser
._user_error_handler( msg
);
30 GitBrowser
._next_call_server( arg
);
32 arg
.final_handler( arg
.final_handler_arg
);
35 GitBrowser
._server_handler=function( doc
, arg
)
37 if( doc
.error
!=null ) {
38 GitBrowser
._error_handler( doc
.error
, arg
);
40 arg
.handler( doc
.result
, arg
.chain
[arg
.chain_i
].handler_arg
);
42 GitBrowser
._next_call_server( arg
);
45 GitBrowser
._url_adjust=function( url
)
47 var base
=location
.protocol
+"//"+location
.host
;
48 var urlparts
=url
.match( /^[A-Za-z+.-]+:\/\/[^\s\/]+(\/.*)$/ );
50 return base
+urlparts
[1];
52 return base
+(url
[0]=="/"?"":"/")+url
;
55 cfg_gitweb_url
=GitBrowser
._url_adjust( cfg_gitweb_url
);
56 cfg_browsercgi_url
=GitBrowser
._url_adjust( cfg_browsercgi_url
);
57 if( typeof( cfg_home_url
)=="undefined" ) {
60 if( typeof( cfg_home_text
)=="undefined" ) {
63 if( typeof( cfg_bycommit_title
)=="undefined" ) {
64 cfg_bycommit_title
=null;
66 if( typeof( cfg_bydate_title
)=="undefined" ) {
67 cfg_bydate_title
=null;
69 if( cfg_home_url
!= null ) {
70 cfg_home_url
=GitBrowser
._url_adjust( cfg_home_url
);
72 GitBrowser
._g_server_url
=cfg_browsercgi_url
;
73 GitBrowser
._g_server_timeout_seconds
=132;
74 GitBrowser
._make_server_url=function( arg
)
76 var url
=GitBrowser
._g_server_url
+"?";
77 url
+="sub="+encodeURIComponent( arg
.sub
);
78 if( arg
.repo
!=null ) {
79 url
+="&repo="+encodeURIComponent( arg
.repo
);
81 if( arg
.sub_args
!=null ) {
83 for( sub_arg_name
in arg
.sub_args
) {
84 var sub_arg
=arg
.sub_args
[sub_arg_name
];
85 for( var sub_i
=0; sub_i
<sub_arg
.length
; ++sub_i
) {
86 var value
=sub_arg
[sub_i
];
87 if( value
!=null && !value
.match( /^\s*$/ ) ) {
88 url
+="&"+sub_arg_name
+"="+encodeURIComponent( value
);
95 GitBrowser
._next_call_server=function( arg
)
97 if( arg
.chain_i
<arg
.chain
.length
) {
98 if( arg
.before_handler
!=null ) {
99 arg
.before_handler( arg
.chain
[arg
.chain_i
].handler_arg
);
101 InvisibleRequest
.get( { url
: GitBrowser
._make_server_url( arg
.chain
[arg
.chain_i
] ),
102 handler
: GitBrowser
._server_handler
,
104 error_handler
: GitBrowser
._error_handler
,
105 timeout_seconds
: GitBrowser
._g_server_timeout_seconds
108 arg
.final_handler( arg
.final_handler_arg
);
112 // before_handler: called before each server request
113 // final_handler: called when all requests are finished
114 // final_handler_arg: the only argument to the previous
115 // 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:
117 // repo: repo_name (optional)
118 // handler_arg: second argument for handler
119 // sub_args: [array of sub arguments]
120 GitBrowser
.call_server=function( arg
)
122 var before_handler
=arg
.before_handler
;
123 if( arg
.before_handler
==null ) {
124 before_handler=function() {};
126 var final_handler
=arg
.final_handler
;
127 if( arg
.final_handler
==null ) {
128 final_handler=function() {};
132 chain
=[ { sub
: arg
.sub
, repo
: arg
.repo
, handler_arg
: arg
.handler_arg
, sub_args
: arg
.sub_args
} ];
134 GitBrowser
._next_call_server( { chain
: chain
, chain_i
: 0, handler
: arg
.handler
, before_handler
: before_handler
, final_handler
: final_handler
, final_handler_arg
: arg
.final_handler_arg
} );
138 // status_show, error_show
139 GitBrowser
._g_status_div
=null;
140 GitBrowser
._g_error_div
=null;
142 GitBrowser
.setup_status_error=function()
144 var status
=document
.createElement( "DIV" );
145 status
.style
.display
="none";
146 status
.style
.position
="absolute";
147 status
.style
.top
="0";
148 status
.style
.right
="3em";
149 status
.style
.fontSize
="10pt";
150 status
.style
.paddingTop
="2px";
151 status
.style
.paddingBottom
="2px";
152 status
.style
.paddingLeft
="5px";
153 status
.style
.paddingRight
="5px";
154 status
.style
.color
="#ffffff";
155 status
.style
.backgroundColor
="#090";
156 document
.body
.appendChild( status
);
157 GitBrowser
._g_status_div
=status
;
158 var error
=document
.createElement( "DIV" );
159 var error_close
=document
.createElement( "SPAN" );
160 error_close
.appendChild( document
.createTextNode( "close" ) );
161 error
.appendChild( error_close
);
162 error
.appendChild( document
.createElement( "SPAN" ) );
163 error
.style
.display
="none";
164 error
.style
.border
="1px solid #a00";
165 error
.style
.color
="#800";
166 error
.style
.backgroundColor
="#ffffff";
167 error
.style
.paddingTop
="3px";
168 error
.style
.paddingBottom
="3px";
169 error
.style
.paddingLeft
="5px";
170 error
.style
.paddingRight
="5px";
171 error
.style
.position
="absolute";
172 error
.style
.top
="3px";
173 error
.style
.left
="3px";
174 error
.style
.zIndex
="10";
175 error_close
.style
.color
="#ffffff";
176 error_close
.style
.backgroundColor
="#a22";
177 error_close
.style
.marginTop
="3px";
178 error_close
.style
.marginBottom
="3px";
179 error_close
.style
.marginLeft
="1em";
180 error_close
.style
.marginRight
="5px";
181 error_close
.style
.paddingTop
="0";
182 error_close
.style
.paddingBottom
="0";
183 error_close
.style
.paddingLeft
="3px";
184 error_close
.style
.paddingRight
="3px";
185 error_close
.style
.cursor
="pointer";
186 error_close
.onclick
=GitBrowser
.error_close
;
187 document
.body
.appendChild( error
);
188 GitBrowser
._g_error_div
=error
;
189 GitBrowser
.set_error_handler( GitBrowser
.error_show
);
191 GitBrowser
.status_show=function( msg
)
193 if( GitBrowser
._g_status_div
!=null ) {
194 if( msg
!=null && msg
!="" ) {
195 GitBrowser
._g_status_div
.innerHTML
="";
196 GitBrowser
._g_status_div
.appendChild( document
.createTextNode( msg
) );
197 GitBrowser
._g_status_div
.style
.display
="block";
199 GitBrowser
._g_status_div
.style
.display
="none";
203 GitBrowser
.error_show=function( msg
)
205 GitBrowser
.status_show();
206 GitBrowser
._g_error_div
.lastChild
.innerHTML
="";
207 GitBrowser
._g_error_div
.lastChild
.appendChild( document
.createTextNode( "Error: "+msg
) );
208 GitBrowser
._g_error_div
.style
.display
="block";
210 GitBrowser
.error_close=function()
212 GitBrowser
._g_error_div
.style
.display
="none";
215 // decode / encode selected repositories and refs as url parameters / text description
216 // repos={ repo_name => { all_heads: boolean, heads: [strings], tags: [strings] } }
217 GitBrowser
.repos_decode_location=function( location
)
220 var args
=location
.search
;
221 if( args
.charAt( 0 )=="?" ) {
222 args
=args
.slice( 1 );
224 if( args
.length
>0 ) {
225 args
=args
.split( "&" );
226 for( var arg_i
=0; arg_i
<args
.length
; ++arg_i
) {
227 var arg
=args
[arg_i
].split( "=" );
229 var repo_name
=decodeURIComponent(arg
[1]);
230 if( repos
[repo_name
]==null ) {
231 repos
[repo_name
]={ heads
: [], tags
: [] };
233 repos
[repo_name
].all_heads
=true;
234 }else if( arg
[0]=="h" || arg
[0]=="t" ) {
235 var ref
=arg
[1].split( "," );
236 var repo_name
=decodeURIComponent(ref
[0]);
237 var ref_name
=decodeURIComponent(ref
[1]);
238 if( repos
[repo_name
]==null ) {
239 repos
[repo_name
]={ heads
: [], tags
: [] };
242 repos
[repo_name
].heads
.push( ref_name
);
244 repos
[repo_name
].tags
.push( ref_name
);
251 GitBrowser
.repos_encode_url_param=function( repos
)
254 for( var repo_name
in repos
) {
255 var repo
=repos
[repo_name
];
256 if( repo
.all_heads
) {
257 params
.push( "r="+encodeURIComponent( repo_name
) );
259 for( var head_i
=0; head_i
<repo
.heads
.length
; ++head_i
) {
260 params
.push( "h="+encodeURIComponent( repo_name
)+","+encodeURIComponent( repo
.heads
[head_i
] ) );
262 for( var tag_i
=0; tag_i
<repo
.tags
.length
; ++tag_i
) {
263 params
.push( "t="+encodeURIComponent( repo_name
)+","+encodeURIComponent( repo
.tags
[tag_i
] ) );
266 return params
.join( "&" );
268 GitBrowser
.repos_encode_text=function( repos
)
271 for( var repo_name
in repos
) {
272 var repo
=repos
[repo_name
];
273 if( repo
.all_heads
) {
274 text
.push( "all "+repo_name
+" heads" );
276 if( repo
.heads
.length
>0 ) {
277 text
.push( repo_name
+" heads: "+repo
.heads
.join( " " ) );
279 if( repo
.tags
.length
>0 ) {
280 text
.push( repo_name
+" tags: "+repo
.tags
.join( " " ) );
283 return text
.join( "; " );
288 // dialog: HTML filter div element
289 // x, y: filter dialog absolute pos
290 // apply_handler: called when "reload" filter button is clicked. argument: { exclude_commits: [], paths: [] }
291 // apply_handler_context: second argument to apply_handler
292 // exclude_edit: HTML edit element for commits to exclude
293 // paths_edit: HTML edit element for paths to limit git-rev-list output
294 GitBrowser
._g_filter
={};
296 GitBrowser
._filter_dialog_close=function()
298 GitBrowser
._g_filter
.dialog
.style
.display
="none";
300 GitBrowser
._filter_dialog_apply=function()
302 var exclude_commits
=GitBrowser
._g_filter
.exclude_edit
.value
.split( " " );
303 var paths
=GitBrowser
._g_filter
.paths_edit
.value
.split( " " );
304 GitBrowser
._g_filter
.dialog
.style
.display
="none";
305 GitBrowser
._g_filter
.apply_handler( { exclude_commits
: exclude_commits
, paths
: paths
}, GitBrowser
._g_filter
.apply_handler_context
);
307 GitBrowser
._filter_dialog_clear=function()
309 GitBrowser
._g_filter
.exclude_edit
.value
="";
310 GitBrowser
._g_filter
.paths_edit
.value
="";
312 GitBrowser
._filter_dialog_show=function()
314 if( GitBrowser
._g_filter
.dialog
.style
.display
=="none" ) {
315 GitBrowser
._g_filter
.dialog
.style
.display
="";
316 var y
=GitBrowser
._g_filter
.y
;
317 if( y
>500 ) { // XXX it's random
318 y
-=GitBrowser
._g_filter
.dialog
.clientHeight
;
320 Motion
.set_page_coords( GitBrowser
._g_filter
.dialog
, GitBrowser
._g_filter
.x
, y
);
322 GitBrowser
._g_filter
.dialog
.style
.display
="none";
325 GitBrowser
._filter_dialog_loaded=function( template
, arg
)
329 _process: function( n
) { GitBrowser
._g_filter
.dialog
=n
; },
331 filterexclude
: { _process: function( n
) { GitBrowser
._g_filter
.exclude_edit
=n
; } },
332 filterpath
: { _process: function( n
) { GitBrowser
._g_filter
.paths_edit
=n
; } }
334 filterreload
: { _process: function( n
) { n
.onclick
=GitBrowser
._filter_dialog_apply
; n
.href
="#"; } },
335 filterclear
: { _process: function( n
) { n
.onclick
=GitBrowser
._filter_dialog_clear
; n
.href
="#"; } },
336 filterclose
: { _process: function( n
) { n
.onclick
=GitBrowser
._filter_dialog_close
; n
.href
="#"; } }
339 DomTemplate
.apply( template
, data
, document
.body
);
340 GitBrowser
._g_filter
.x
=arg
.x
;
341 GitBrowser
._g_filter
.y
=arg
.y
;
342 GitBrowser
._g_filter
.apply_handler
=arg
.apply_handler
;
343 GitBrowser
._g_filter
.apply_handler_context
=arg
.apply_handler_context
;
344 arg
.show_button
.onclick
=GitBrowser
._filter_dialog_show
;
347 // show_button: its onclick will show filter
348 // x, y: filter dialog pos
349 // apply_handler: called when "reload" filter button is clicked. argument: { exclude_commits: [], paths: [] }
350 GitBrowser
.filter_dialog_init=function( arg
)
352 InvisibleRequest
.get_element( { url
: GitBrowser
._templates_url
, element_id
: "filterdialogtemplate",
353 handler
: GitBrowser
._filter_dialog_loaded
, handler_arg
: arg
,
354 error_handler
: GitBrowser
.error_show
} );
359 GitBrowser
._g_title
={};
360 GitBrowser
._title_loaded=function( template
, arg
)
362 var selected_text
=GitBrowser
.repos_encode_text( arg
.repos
);
363 if( selected_text
=="" ) {
364 selected_text
="none selected";
368 _process: function( n
) { arg
.title_div
=n
; },
369 selectedtext
: selected_text
,
370 selectother
: { _process: function( n
) { arg
.select_other_btn
=n
} },
371 commitcount
: { _process: function( n
) { GitBrowser
._g_title
.commitcount
=n
; } },
372 loadmore
: { _process: function( n
, context
) { GitBrowser
._g_title
.loadmore
=n
; arg
.load_more_button_init( n
); }, _process_arg
: arg
},
373 filtershow
: { _process: function( n
, context
) { n
.href
="#"; arg
.filter_button_init( n
, context
); }, _process_arg
: arg
},
374 home_btn
: { _process: function( n
, context
) { n
.removeAttribute( "id" ); GitBrowser
._home_btn_init( n
, context
) }, _process_arg
: arg
}
377 DomTemplate
.apply( template
, data
, document
.body
);
378 if( arg
.title_loaded_handler
!=null ) {
379 arg
.title_loaded_handler( arg
);
381 arg
.exclude_commits
=[];
383 GitBrowser
.commits_load_first( arg
);
385 GitBrowser
._home_btn_init=function( n
, context
)
387 if( cfg_home_url
!= null ) {
388 var url
=cfg_home_url
;
390 if ( context
.repos
!= null && typeof( context
.repos
)=="object" ) {
391 for ( var k
in context
.repos
) {
396 url
=url
.replace( /%n/g, encodeURIComponent(repo
) );
397 url
=url
.replace( /%2[bB]/g, "+" );
398 url
=url
.replace( /%2[fF]/g, "/" );
401 if( cfg_home_text
!= null ) {
402 var text_node
=n
.ownerDocument
.createTextNode( cfg_home_text
);
403 var first_child
=n
.firstChild
;
404 if( first_child
== null ) {
405 n
.appendChild( text_node
);
407 if( first_child
.nodeType
==3 ) { // it's a text node
408 n
.removeChild( first_child
);
409 first_child
=n
.firstChild
;
410 if( first_child
== null ) {
411 n
.appendChild( text_node
);
413 n
.insertBefore( text_node
, first_child
);
416 n
.insertBefore( text_node
, first_child
);
423 // load_more_button_init: function( b )
424 // filter_button_init: function( b )
425 // title_loaded_handler: called when the title is loaded into the document, takes title_div as an argument
426 // commits_first_loaded_handler: function( context )
427 // commits_more_loaded_handler: function( context )
429 // diagram: GitDiagram object
431 // repos: as returned by repos_decode_location
434 // exclude_commits: []
437 GitBrowser
.title_init=function( arg
)
439 var title
= arg
.title
;
440 if ( title
!= null && title
!= "" ) {
442 if ( arg
.repos
!= null && typeof( arg
.repos
)=="object" ) {
443 for ( var k
in arg
.repos
) {
448 title
=title
.replace( /%n/g, repo
);
449 document
.title
=title
;
451 InvisibleRequest
.get_element( { url
: GitBrowser
._templates_url
, element_id
: "titletemplate",
452 handler
: GitBrowser
._title_loaded
, handler_arg
: arg
,
453 error_handler
: GitBrowser
.error_show
} );
457 GitBrowser
.title_update=function( arg
)
459 GitBrowser
._g_title
.commitcount
.innerHTML
="";
460 var cmtcount
= arg
.diagram
.get_commit_count();
461 var cmtpl
= (cmtcount
!= 1) ? "s" : "";
462 GitBrowser
._g_title
.commitcount
.appendChild( document
.createTextNode( "Loaded "+cmtcount
+" commit"+cmtpl
+" " ) );
463 var need_more
=arg
.diagram
.get_start_more_ids().length
!=0;
464 GitBrowser
._g_title
.loadmore
.style
.visibility
= need_more
? "visible" : "hidden";
467 // diagram loading (calls only add_node)
468 GitBrowser
._add_refs_and_commits=function( data
, arg
)
470 if ( data
.master
!=null && data
.master
.length
>0 ) {
471 arg
.diagram
.add_master( arg
.repo_name
, data
.master
);
473 for( var i
=0; i
<data
.refs
.length
; ++i
) {
474 arg
.diagram
.add_label( data
.refs
[i
].id
, data
.refs
[i
].name
, arg
.repo_name
, data
.refs
[i
].type
);
476 GitBrowser
._add_commits( data
.commits
, arg
);
478 GitBrowser
._add_commits=function( commits
, arg
)
481 for( var commit_id
in commits
) {
482 tmp
.push( commit_id
);
485 for( var tmp_i
=0; tmp_i
<tmp
.length
; ++tmp_i
) {
486 var commit
=commits
[tmp
[tmp_i
]];
487 if( (commit
.committer_epoch
!=null || commit
.author_epoch
!=null) && commit
.id
!=null && commit
.author
!=null && commit
.parents
!=null ) {
488 var committer_time
=commit
.committer_epoch
==null ? null : commit
.committer_epoch
*1000;
489 var author_time
=commit
.author_epoch
==null ? null : commit
.author_epoch
*1000;
490 var comment
=commit
.comment
==null ? "" : commit
.comment
.join( " " );
491 arg
.diagram
.add_node( commit
.id
, committer_time
, author_time
, commit
.author
, comment
, commit
.parents
, arg
.repo_name
);
496 // repos: as returned by repos_decode_location
498 // exclude_commits: [], as passed to apply_handler first arg in filter_dialog
499 // paths: [], as passed to apply_handler first arg in filter_dialog
500 // commits_first_loaded_handler: function( arg )
501 GitBrowser
.commits_load_first=function( arg
)
504 for( var repo_name
in arg
.repos
) {
505 var repo
=arg
.repos
[repo_name
];
507 if( repo
.all_heads
) {
508 refs
.push( "r,all" );
510 for( var head_i
=0; head_i
<repo
.heads
.length
; ++head_i
) {
511 refs
.push( "h,"+repo
.heads
[head_i
] );
513 for( var tag_i
=0; tag_i
<repo
.tags
.length
; ++tag_i
) {
514 refs
.push( "t,"+repo
.tags
[tag_i
] );
517 chain
.push( { sub
: "commits_from_refs", repo
: repo_name
, handler_arg
: { diagram
: arg
.diagram
, repo_name
: repo_name
},
518 sub_args
: { ref
: refs
, x
: arg
.exclude_commits
, path
: arg
.paths
, shortcomment
: [arg
.shortcomment
] }
522 GitBrowser
.status_show( "loading..." );
523 GitBrowser
.call_server( { handler
: GitBrowser
._add_refs_and_commits
,
524 final_handler: function( arg
) { GitBrowser
.status_show( "" ); arg
.commits_first_loaded_handler( arg
); }, final_handler_arg
: arg
,
529 // exclude_commits: [], as passed to apply_handler first arg in filter_dialog
530 // paths: [], as passed to apply_handler first arg in filter_dialog
531 // commits_more_loaded_handler: function( arg )
532 GitBrowser
.commits_load_more=function( arg
)
535 var more_ids
=arg
.diagram
.get_start_more_ids();
536 for( var i
=0; i
<more_ids
.length
; ++i
) {
538 for( var repo_i
=0; repo_i
<id
.repos
.length
; ++repo_i
) {
539 var repo_name
=id
.repos
[repo_i
];
540 if( repo_map
[repo_name
]==null ) {
541 repo_map
[repo_name
]=[[]];
543 var ids
=repo_map
[repo_name
][repo_map
[repo_name
].length
-1];
544 if( ids
.length
>9 ) { // split to avoid too long urls - for now, the limit is 10 40-byte ids per url.
545 // since server does not keep track which commits were already sent to which client,
546 // splitting requests may cause redundant data to be transferred.
547 repo_map
[repo_name
].push( [] );
548 ids
=repo_map
[repo_name
][repo_map
[repo_name
].length
-1];
554 for( var repo_name
in repo_map
) {
555 var ids_a
=repo_map
[repo_name
];
556 for( var i
=0; i
<ids_a
.length
; ++i
) {
559 chain
.push( { sub
: "commits_from_ids", repo
: repo_name
, handler_arg
: { diagram
: arg
.diagram
, repo_name
: repo_name
},
560 sub_args
: { id
: ids
, x
: arg
.exclude_commits
, path
: arg
.paths
, shortcomment
: [arg
.shortcomment
] }
564 GitBrowser
.status_show( "loading..." );
565 GitBrowser
.call_server( { handler
: GitBrowser
._add_commits
,
566 final_handler: function( arg
) { GitBrowser
.status_show( "" ); arg
.commits_more_loaded_handler( arg
); }, final_handler_arg
: arg
,
570 // glue code that appears to be common between by-date.html and by-commits.html
571 // diagram ui handler. first argument should be ui handlers map: event_name=>handler
572 GitBrowser
.diagram_ui_handler=function()
574 var ui_map
=arguments
[0];
575 var event_name
=arguments
[1];
577 for( var i
=2; i
<arguments
.length
; ++i
) {
578 args
.push(arguments
[i
] );
580 var handler
=ui_map
[event_name
];
581 if( handler
!=null ) {
582 handler
.apply( this, args
);
586 GitBrowser
.filter_dialog_handler=function( arg
, context
)
588 context
.exclude_commits
=arg
.exclude_commits
;
589 context
.paths
=arg
.paths
;
590 context
.diagram
.clear();
591 GitBrowser
.commits_load_first( context
);
593 GitBrowser
.filter_dialog_create=function( filter_button
, context
)
595 var ref_pos
=Motion
.get_page_coords( filter_button
);
596 var x
=ref_pos
.x
+filter_button
.clientWidth
;
597 var y
=ref_pos
.y
+2+filter_button
.scrollHeight
;
598 GitBrowser
.filter_dialog_init( { show_button
: filter_button
, x
: x
, y
: y
, apply_handler
: GitBrowser
.filter_dialog_handler
, apply_handler_context
: context
} );
602 // repos: as as returned by repos_decode_location
603 // diagram: GitDiagram object
604 // title_loaded_handler
605 // commits_first_loaded_handler
606 // commits_more_loaded_handler
607 GitBrowser
.init=function( arg
)
609 GitBrowser
.setup_status_error();
610 arg
.load_more_button_init=function( b
) { b
.href
="#"; b
.onclick=function() { GitBrowser
.commits_load_more( arg
) }; };
611 arg
.filter_button_init
=GitBrowser
.filter_dialog_create
;
612 GitBrowser
.title_init( arg
);